fix: report accurate is_running after trader update to stop redundant start

Live verification of the relaunch (update) branch surfaced this: the update
response omitted is_running, so the launcher fired a redundant start that got
400 'Trader is already running', showing an error toast and skipping the
dashboard navigation even though the relaunch succeeded.

- PUT /traders/:id now reports is_running (a running trader is restarted with
  the new config, so it stays running).
- The already-running rejection carries error_key trader.start.already_running
  and the shared launcher treats it as success (launch is idempotent).
This commit is contained in:
tinkle-community
2026-07-05 23:36:53 +09:00
parent a744328313
commit 9b70455e7f
3 changed files with 45 additions and 3 deletions

View File

@@ -129,6 +129,33 @@ describe('launchAutopilot', () => {
)
})
it('treats a racing already-running rejection as success', async () => {
mocks.runLaunchPreflight.mockResolvedValue(readyPreflight())
mocks.api.getTraders.mockResolvedValue([
{ trader_id: 't-1', trader_name: 'NOFX Autopilot', is_running: false },
])
mocks.api.updateTrader.mockResolvedValue({
trader_id: 't-1',
is_running: false,
})
mocks.api.startTrader.mockRejectedValue(
new ApiError(
'Trader is already running',
'trader.start.already_running',
undefined,
400
)
)
const outcome = await launchAutopilot({
ensureStrategy: vi.fn().mockResolvedValue('strat-1'),
})
expect(outcome).toEqual(
expect.objectContaining({ ok: true, traderId: 't-1' })
)
})
it('surfaces the server-side preflight result when start is rejected', async () => {
mocks.runLaunchPreflight.mockResolvedValue(readyPreflight())
mocks.api.startTrader.mockRejectedValue(

View File

@@ -101,7 +101,16 @@ export async function launchAutopilot(
: await api.createTrader(traderRequest)
if (!autopilot.is_running) {
await api.startTrader(autopilot.trader_id)
try {
await api.startTrader(autopilot.trader_id)
} catch (err) {
// Launch is idempotent: the update path restarts a running trader
// asynchronously, so a racing "already running" rejection is success.
const alreadyRunning =
err instanceof ApiError &&
err.errorKey === 'trader.start.already_running'
if (!alreadyRunning) throw err
}
}
return {