Repository navigation
Conversation
A Code Mode program has no timers, so a promise settles only through a tool or extension call or the program's own steps. A program awaiting a promise that nothing can settle, such as `await new Promise(r => r)`, waited forever, and with no execute timeout the session held its turn until inactivity eviction interrupted it an hour later. The interpreter now counts its steps and the host calls in flight, including their hooks. A new optional `stallMs` limit fails the program with a `Stalled` diagnostic once a full window passes with no step and no call running. OpenCode's execute tool sets it to 30 seconds and tells the model not to use execute to wait or poll. Closes anomalyco#54340
|
The following comment was made by an LLM, it may be inaccurate: |
There was a problem hiding this comment.
Looks good. I reproduced the hang on the base branch: in the TUI with the simulated model, an execute call with await new Promise(r => r); return 1 was still running after 50s. With this PR it fails after about 30s with the Execution stalled… message, and the model gets the error and the turn ends. The codemode and core tool-execute tests pass, and so do both packages' typechecks. The new TestClock test times out when run against the base code, so it really does catch the bug.
I checked how stalls are detected: every tool call (including search) and every extension call is counted as running while in flight, along with its before/after hooks. Slow calls and slow permission hooks therefore can't be mistaken for a stall. After return, the stall check stops and cleanup is still bounded only by timeoutMs.
Public API: @opencode/codemode gains an optional ExecutionLimits.stallMs, a new Stalled value in DiagnosticKind, and a new stallMs field on ResolvedExecutionLimits. All are additions and backwards compatible; only code that matches every DiagnosticKind value exhaustively would need to handle Stalled. Config, CLI, HTTP API and SDK are unchanged.


Issue for this PR
Closes #54340
Type of change
What does this PR do?
Code Mode has no timers, so a promise settles only through a tool or extension call, or through the program's own code. A program like
await new Promise(r => r)therefore waits forever, and becauseexecutehas no time limit, the session holds its turn until inactivity eviction interrupts it an hour later.The interpreter now counts its own steps and the host calls in flight, including their hooks. A new optional
stallMslimit fails the program with aStalleddiagnostic once a whole window passes with no step taken and no call running. In that state nothing can ever settle the promise, so failing is always right. Slow tool calls and busy loops never trip it. WithoutstallMs, behavior is unchanged.The
executetool setsstallMsto 30 s and tells the model not to useexecuteto wait or poll. I chose this over a wall-clock timeout because a timeout would also cut off legitimate long tool calls.The hot-path cost is one integer increment per statement or expression. I measured no difference on a 400k-iteration loop.
How did you verify your code works?
packages/codemode/test/codemode.test.tscover the never-settling forms, logs and calls kept before the stall, slow calls and hooks, busy loops, un-awaited promises at return, and how the stall interacts withtimeoutMs. A newTestClocktest inpackages/core/test/tool-execute.test.tsfails without the fix.Execution stalled…after 30 s.bun run lintpasses, typecheck passes for 36/36 packages ondev, and codemode tests are 1408 pass.Screenshots / recordings
N/A
Checklist
Added by the opencode bot (AI-generated).
Related: #54220, #51223
Public API: adds the optional
stallMsexecution limit and theStalleddiagnostic kind to @opencode/codemode (backwards compatible)