Skip to content

Commit 1782a22

Browse files
authored
fix(mothership): settle a sim_cli wait that outlasted --wait-timeout as done, not failed (#9469)
A wait whose --wait-timeout elapsed with the work still going (workflows runs wait, a detached synchronous workflows run, a workspace operation wait) exits 4 and prints the record to continue from. The sim_cli handler mapped every non-zero exit to a failed call, so each still-running slice the worker issues for a long draft run was recorded as a failed async tool call, published to the chat as an errored row (titled 'Running workflow' / 'Waiting for workflow run' instead of the past tense, and dropping the activity's completed title), and counted as outcome=error in copilot.tool.calls. The CLI now names that status (STILL_RUNNING_EXIT_CODE, exported from sim/embed) at both producers, and the handler settles it as success while keeping exitCode 4 in the output the worker branches on.
1 parent 5facc5f commit 1782a22

6 files changed

Lines changed: 36 additions & 7 deletions

File tree

‎apps/sim/lib/mothership/tools/handlers/sim-cli-bridge.test.ts‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ vi.mock('@/lib/execution/remote-sandbox/session-files', () => ({
1818
writeSessionSandboxFile: mockWrite,
1919
resolveSessionPath: (path: string) => (path.startsWith('/') ? path : `/home/user/${path}`),
2020
}))
21-
vi.mock('sim/embed', () => ({
21+
vi.mock('sim/embed', async (importOriginal) => ({
22+
...(await importOriginal<typeof import('sim/embed')>()),
2223
runEmbeddedCli: mockRunEmbeddedCli,
2324
createEmbeddedClient: vi.fn(),
2425
}))
@@ -202,4 +203,17 @@ describe('sim-cli handler executes the worker-built request', () => {
202203
expect(mockWrite).not.toHaveBeenCalled()
203204
expect(result.success).toBe(false)
204205
})
206+
207+
it('settles a wait that outlasted --wait-timeout as done, keeping its exit status for the worker', async () => {
208+
const stderr = 'Run run-1 is still running after 240s and continues in the background.'
209+
mockRunEmbeddedCli.mockResolvedValue({ exitCode: 4, stdout: '{"status":"running"}', stderr })
210+
const result = await executeSimCli(
211+
cli(['workflows', 'runs', 'wait', 'run-1', '--workflow', 'wf-1', '--wait-timeout', '240']),
212+
context
213+
)
214+
expect(result).toEqual({
215+
success: true,
216+
output: { exitCode: 4, stdout: '{"status":"running"}', stderr },
217+
})
218+
})
205219
})

‎apps/sim/lib/mothership/tools/handlers/sim-cli.ts‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createLogger } from '@sim/logger'
22
import { getErrorMessage } from '@sim/utils/errors'
3+
import { STILL_RUNNING_EXIT_CODE } from 'sim/embed'
34
import { executeAgentCliRequest } from '@/lib/mothership/agent-cli'
45
import { messageForCopilotApplicationError } from '@/lib/mothership/application/errors'
56
import { AgentCliRequest } from '@/lib/mothership/generated/agent-cli'
@@ -15,6 +16,9 @@ const logger = createLogger('MothershipSimCli')
1516
* (`request`) beside the model's raw argv (`args`, kept for display and the log); only
1617
* the request is executed — this side never re-parses tokens. The worker folds
1718
* exitCode/stdout/stderr into the model window and applies its own output budget.
19+
* A wait whose `--wait-timeout` elapsed with the work still going settles the call as
20+
* done: the worker continues it in further calls, so the chat, the tool record and the
21+
* tool metrics must not read it as a failure.
1822
*/
1923
export async function executeSimCli(
2024
params: Record<string, unknown>,
@@ -63,12 +67,12 @@ export async function executeSimCli(
6367
sink: parsed.data.sink?.kind ?? 'none',
6468
stdoutBytes: result.stdout.length,
6569
})
70+
if (result.exitCode === 0 || result.exitCode === STILL_RUNNING_EXIT_CODE)
71+
return { success: true, output: result }
6672
return {
67-
success: result.exitCode === 0,
73+
success: false,
6874
output: result,
69-
...(result.exitCode === 0
70-
? {}
71-
: { error: result.stderr.split('\n')[0] || `sim CLI exited with code ${result.exitCode}` }),
75+
error: result.stderr.split('\n')[0] || `sim CLI exited with code ${result.exitCode}`,
7276
}
7377
} catch (error) {
7478
return {

‎packages/sim-cli/src/commands/protocol/workflow-run-wait.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { hasProgressTerminal, styles } from '#sim-cli/output/presentation'
66
import { CLI_CONTRACT } from '../../contract/commands'
77
import type { CommandSpec } from '../../contract/types'
88
import { setSoftExitCode } from '../../embed-context'
9+
import { STILL_RUNNING_EXIT_CODE } from '../../exit-codes'
910
import { V2_OPERATIONS } from '../../generated/v2-api'
1011
import { SimApiError } from '../../http/client'
1112
import { apiCommand, type Connection } from '../../runtime/called-operations'
@@ -39,7 +40,7 @@ const WAIT_EXIT_CODES = {
3940
failed: 1,
4041
cancelled: 2,
4142
paused: 3,
42-
timeout: 4,
43+
timeout: STILL_RUNNING_EXIT_CODE,
4344
} as const
4445

4546
type WaitOutcome = keyof typeof WAIT_EXIT_CODES

‎packages/sim-cli/src/commands/protocol/workspace-operation-wait.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { sleep } from '@sim/utils/helpers'
22
import type { Command } from 'commander'
33
import { CLI_CONTRACT } from '../../contract/commands'
4+
import { STILL_RUNNING_EXIT_CODE } from '../../exit-codes'
45
import type { GetWorkspaceOperationResponse } from '../../generated/v2-api'
56
import { SimApiError } from '../../http/client'
67
import { apiCommand, type OperationClient } from '../../runtime/called-operations'
@@ -55,7 +56,7 @@ export function assertWorkspaceOperationOutcome(
5556
report: WorkspaceOperation,
5657
timedOut = false
5758
): void {
58-
const exitCode = timedOut ? 4 : EXIT_CODES[report.status]
59+
const exitCode = timedOut ? STILL_RUNNING_EXIT_CODE : EXIT_CODES[report.status]
5960
if (!exitCode) return
6061
throw new SimApiError(
6162
timedOut

‎packages/sim-cli/src/embed.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { buildProgram } from './program'
3434
*/
3535

3636
export type { EmbeddedCliIdentity, EmbeddedFileSnapshot } from './embed-context'
37+
export { STILL_RUNNING_EXIT_CODE } from './exit-codes'
3738
export type {
3839
ExportWorkflowResponse,
3940
ListFilesResponse,

‎packages/sim-cli/src/exit-codes.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* The exit status of a wait whose `--wait-timeout` elapsed while the work it watches
3+
* keeps going: `workflows runs wait`, a synchronous `workflows run` that detached, and a
4+
* workspace operation wait. The command did what it was asked and printed the record to
5+
* continue from, so a host that runs the CLI for an agent treats it as a finished call,
6+
* not a failed one; a script still branches on the non-zero status.
7+
*/
8+
export const STILL_RUNNING_EXIT_CODE = 4

0 commit comments

Comments
 (0)