Repository navigation
feat(workflows): let a synchronous run continue in the background after a wait timeout - #9464
waleedlatif1 wants to merge 3 commits into
Conversation
…er a wait timeout A synchronous run, including a manual draft run (--manual, --trigger, --from-block, --stop-after), could only be waited on until it settled or hit the sync execution limit. Manual runs refuse async, so a caller had no way to get the run id back from a long or stuck draft run and decide to keep waiting, watch it, or cancel it. The v2 execute body takes waitTimeoutSeconds. Past it the response answers 202 with the run receipt (NDJSON: a `detached` frame) and the run keeps going in this process exactly as it started: same captured draft state, entry point and pause persistence. Such a run is admitted like an async one (async rate bucket, reservation and timeout), takes no request abort signal so a dropped connection never cancels it, and registers an in-process aborter so runs cancel reaches it without Redis. Without the field nothing changes. The CLI exposes it as `workflows run --wait-timeout <seconds>`. A run that outlasts it is read back once and reported the way `workflows runs wait` reports an elapsed bound: the run record on stdout and exit 4 while it is still moving, so `workflows runs wait <runId>` continues it.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
All reported issues were addressed across 15 files
Reply with feedback, questions, or to request a fix.
Turn on auto-fix | Re-trigger cubic
[Medium impact] The PR appears safe to merge; no new blocking issues were found. SummaryAdds a bounded wait for synchronous workflow runs. Runs that outlast the wait continue in the background.
Both earlier, unnumbered findings are fixed: receipts wait for the log row, and
|
…an be read A detach could fire before the run's log row existed, so the runs resource answered 404 for the receipt just handed out. The response now waits for the logging session to start before detaching, and the wait timeout docs say how each transport carries the receipt.
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
…to the worker Sim now sends the workflow-run-wait-timeout client capability on chat turns, so the worker waits on a synchronous workflows run in slices only on a Sim whose CLI takes --wait-timeout there; an older Sim keeps its held run. The protocol copy is synced from the mothership contract that defines the token.
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
Summary
A synchronous
workflows runcould only be waited on until it settled or hit the sync execution limit. That includes the manual draft runs (--manual,--trigger,--mock-payload,--from-block/--source-run,--stop-after), and manual runs refuse--async. So a caller testing a long or stuck draft run had no way to get its run id back and decide whether to keep waiting, watch it, or cancel it. Published runs already had a way to do this:--asyncplusworkflows runs wait.This PR adds a wait bound to the synchronous run:
POST /api/v2/workflows/{id}/executetakes a newwaitTimeoutSecondsfield.202with the existing run receipt{ runId, statusUrl }. On the NDJSON result transport this arrives as a{ type: "detached", data: receipt }frame.0waits until the run settles. The field can't be combined withasyncorstream, and anonymous public-API callers get a400.executionTimeoutSecondsis now also accepted alongsidewaitTimeoutSeconds.workflows runs cancelreaches it without Redis as well as through the usual Redis signal.sim workflows run ... --wait-timeout <seconds>, parsed like every other--wait-timeout.workflows runs waitreports an elapsed bound: the run record on stdout and exit4while the run is still moving, plus a stderr line namingworkflows runs waitandworkflows runs cancel.--wait-timeoutwith--asyncor--followis refused locally. A manual--asyncnow points at--wait-timeout.waitTimeoutSeconds/--wait-timeout, request handling, output and exit codes are unchanged.workflow-run-wait-timeoutclient capability on chat turns, so the mothership worker only uses--wait-timeouton a Sim that supports it. Older Sims keep the held synchronous run. The protocol copy is synced from the mothership contract; the worker side is a companion change that is not yet pushed.Why not
--asyncfor manual runsAn async manual run would have to carry the whole draft state, and for
--from-blockthe source run's snapshot too, through the job queue payload. That would be a durable-payload change that queued jobs and rollbacks must tolerate. Async also refuses result shaping (--select-output, file inlining) and adds a queue hop to every draft test, though most finish in seconds.--wait-timeoutkeeps the fast path inline and returns a run id only when the run is actually long. Accepting--asyncfor manual runs could be added later on top of this, but nothing here needs it.Type of Change
Testing
execute/route.test.ts). Each new test failed against the code before this change, and the cancel and disconnect guards were checked by reverting each one and watching its test fail:detachedframe arrives at the deadline and the run keeps going (ticket and claim held until it settles)202with the receiptruns cancelstops itasync,streamand anonymous combinations are refusedexecutionTimeoutSecondsis accepted only on runs that can outlive the responsesync-result-stream+detachablewith no request signalworkflow-run-follow.test.ts): the request body, unchanged output when the run settles, exit 4 plus the run record when it detaches, a run that settled after detaching, a202on the JSON transport, rounding a fractional wait up and0, and refused combinations. Theworkflows runs waittests still pass after its loop was extracted intowaitForRun.bun run lint,type-check(apps/sim, sim-cli),check:audits,check:api-validation:strict,check:test-types, the sim-cli suite, and the apps/sim suites underapp/api/v2/workflows,lib/workflows,lib/execution,lib/mothership/agent-cli,app/api/workflows.generate:cli-api,generate:openapi,generate:cli-docs.Reviewers: please focus on
streamPendingRun(who closes the stream, and that only a non-detachable run is cancelled when the reader goes away) and on the admission and timeout class inexecuteWorkflowService.Checklist