You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
codemode: interrupting execute discards results of nested calls that already finished #54222
When an execute call is interrupted, the model only gets Tool execution interrupted. Results from nested tool calls that had already finished, and anything the script logged, are discarded, even though the stored tool part lists those calls as completed.
Environment
opencode version: 0.0.0-dev-20788 (V2, v2 at 8eff035)
Interrupt the session while the second call is running.
Expected Behavior
The interrupted (or timed out) tool result still contains the finished nested results, or a truncated preview of each, along with the logs. The next turn can then use them without redoing the work.
Actual Behavior
The tool part ends as { type: "aborted", message: "Tool execution interrupted" } with no output. metadata.toolCalls shows the first call as completed, but its result and the console.log line are gone:
On interrupt, the program re-raises the interruption (execute.ts#L92-L95), so logs and the results of finished calls never reach formatResult.
The step runner then fails the unsettled tool with the generic error (step.ts#L221-L228).
Proposal
Keep a bounded preview of each nested result, plus timestamps, in the execute progress metadata.
When execute is interrupted or times out, build the tool error message from those previews and the logs, for example: "Interrupted after 2 of 3 calls. Finished results: …".
Thanks for the detailed report. I reproduced this from source on the latest v2 (8eff035bf4, the same commit you reported).
What I did: I registered a dynamic slow_wait tool and had the simulated model run your script through execute. The first nested call finished with waited 1s: FIRST_RESULT_42. I interrupted (Esc) while the 600s call was running, then sent a follow-up prompt.
The recording below shows the TUI marking the first slow_wait call as finished. However, the follow-up request sends the model only this for the execute call:
The first result and the console.log output are both missing. A standalone test in packages/codemode gave the same result: the call hooks report the first call as success, but execute ends as a bare interruption, so logs and the finished results never reach the result. That fits your reading of execute.ts. The script below reproduces it with opencode-drive.
recording.mp4
Reproduction script (drive.ts)
import{Effect,Stream}from"effect"import{Llm,OpenCodeDriver}from"opencode-drive"constcode=`const first = await tools.slow_wait({ seconds: 1 })console.log("first:", first)await tools.slow_wait({ seconds: 600 })return first`exportdefaultOpenCodeDriver.use({
...(process.env.OC_DEV ? {opencode: {dev: process.env.OC_DEV}} : {}),tui: {recording: true,keypressOverlay: true},},({ ui, llm, tools })=>Effect.gen(function*(){yield*tools.attach({tools: [{name: "slow_wait",description: "Wait for the given number of seconds",inputSchema: {type: "object",properties: {seconds: {type: "number"}},required: ["seconds"]},outputSchema: {type: "object",properties: {waited: {type: "number"}},required: ["waited"]},},],})constbodies: Array<unknown>=[]yield*llm.serve((request,index)=>{bodies.push(request.body)returnindex===0
? Stream.make(Llm.toolCall({index: 0,id: "call_exec",name: "execute",input: { code }}),Llm.finish("tool-calls"))
: Stream.make(Llm.text("Follow-up turn."))})yield*ui.submit("Run the slow script")constfirst=yield*tools.take()yield*Effect.log(`first nested call: ${JSON.stringify(first.input)}`)yield*Effect.sleep(1000)yield*first.finish({structured: {waited: 1},content: [{type: "text",text: "waited 1s: FIRST_RESULT_42"}]})constsecond=yield*tools.take()yield*Effect.log(`second nested call: ${JSON.stringify(second.input)}`)yield*Effect.sleep(1500)yield*ui.screenshot("before-interrupt")yield*ui.press("escape")yield*Effect.sleep(300)yield*ui.press("escape")yield*Effect.sleep(2500)yield*ui.screenshot("after-interrupt")// Ask a follow-up so we can see what the model receives for the interrupted execute call.yield*ui.submit("What did the first call return?")yield*ui.waitFor("Follow-up turn.")yield*ui.screenshot("follow-up")constfollow=JSON.stringify(bodies.at(-1))constat=follow.indexOf("call_exec",follow.indexOf("call_exec")+1)constmessages=(bodies.at(-1)as{messages?: Array<{role: string;tool_call_id?: string;content?: unknown}>}).messages??[]consttoolMsg=JSON.stringify(messages.find((m)=>m.role==="tool"&&m.tool_call_id==="call_exec")?.content??null)yield*Effect.log(`execute result sent to the model on the next turn:\n${follow.slice(Math.max(0,at-200),at+600)}`)yield*Effect.log(`execute tool result contains FIRST_RESULT_42: ${toolMsg.includes("FIRST_RESULT_42")}; contains the logged "first:" line: ${toolMsg.includes("first:")}\n${toolMsg}`)}),)
Run it with opencode-drive: opencode-drive run ./drive.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.
Activity
opencode-agent commentedon Oct 9, 2026
Thanks for the detailed report. I reproduced this from source on the latest
v2(8eff035bf4, the same commit you reported).What I did: I registered a dynamic
slow_waittool and had the simulated model run your script throughexecute. The first nested call finished withwaited 1s: FIRST_RESULT_42. I interrupted (Esc) while the 600s call was running, then sent a follow-up prompt.The recording below shows the TUI marking the first
slow_waitcall as finished. However, the follow-up request sends the model only this for the execute call:The first result and the
console.logoutput are both missing. A standalone test inpackages/codemodegave the same result: the call hooks report the first call assuccess, butexecuteends as a bare interruption, sologsand the finished results never reach the result. That fits your reading ofexecute.ts. The script below reproduces it with opencode-drive.recording.mp4
Reproduction script (drive.ts)
Run it with opencode-drive:
opencode-drive run ./drive.ts. It drivesopencodefrom yourPATH; setOC_DEVto the path of an OpenCode checkout to run that from source instead.