Skip to content

feat(codemode): return logs and calls when a run is stopped - #59978

Open
rekram1-node wants to merge 1 commit into
devfrom
interrupted-partial-output
Open

rekram1-node wants to merge 1 commit into
devfrom
interrupted-partial-output

Conversation

@rekram1-node

@rekram1-node rekram1-node commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Part of #54222

Problem

When a Code Mode run is interrupted, the Effect just ends interrupted and returns nothing. The logs and the calls the program already made are thrown away, so a host has no way to show what ran before the stop.

Change

I added an optional stop signal. When it completes, the program is stopped the same way an interrupt stops it (running calls are interrupted and their after hooks see interrupted), and then the run returns its normal Result instead of being interrupted:

const result = yield* runtime.execute(code, bindings, { stop: Deferred.await(stopped) })
// or CodeMode.execute({ code, tools, stop })

// { ok: false, error: { kind: "Interrupted", message: "Execution was interrupted." },
//   logs: ["starting"], toolCalls: [{ name: "lookup" }, { name: "wait" }] }
  • stop goes in a new optional third argument (RunOptions), so runtime.execute(code, bindings) keeps its signature and existing callers don't change. Core compiles untouched.
  • Interrupted is a new DiagnosticKind.
  • The result is bounded by maxOutputBytes like any other failure.
  • An outside interrupt without stop, or a call that interrupts itself, still ends the run interrupted exactly as before.
  • It lives at the Effect boundary in interpreter/execute.ts (a raceFirst with stop). The evaluator is untouched.

I skipped the Visual section since this is a package API with nothing user-facing yet.

Out of scope

  • Wiring core's execute tool to stop so the model sees the logs and calls after an interrupt. That's the follow-up.
  • shell output on interrupt.

Verification

From packages/codemode:

  • bun run script/sync-test262.ts ~/development/test262
  • bun run typecheck: no type errors
  • bun run typecheck:tsc: clean
  • bun test: 14832 pass, 1678 skip, 0 fail

From packages/core (no changes there):

  • bun run typecheck: no type errors
  • bun run typecheck:tsc: clean
  • bun run script/test.ts test/tool-execute.test.ts test/codemode: 35 pass, 0 fail

From the repo root: bun run check passes.

New tests: stop mid-program returns Interrupted with the log and both calls and the blocked call's after hook sees interrupted; logs of a stopped run are bounded; an outside interrupt or a self-interrupting call still ends interrupted with stop set; a program that finishes first returns its normal result. The two stop result tests fail without the change.


Added by the opencode bot (AI-generated).

Fixes #39565

Related: #54222, #39565

Public API: @opencode/codemode: adds optional stop (RunOptions, third argument of Runtime.execute and on ExecuteOptions) and the Interrupted diagnostic kind (backwards compatible)

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change works. I drove the TUI with the simulated model against both checkouts, running an execute (one console.log, then a fetch that never returns) alongside a foreground shell that prints two lines and then sleeps, and interrupted both.

  • Base: both tool results in the next model request had "content":[].
  • This PR: the execute result carries Tool calls:\n- fetch: interrupted\n\nLogs:\nchecked the cache, and the shell result carries the two printed lines plus Interrupted before completion. The error message is still Tool execution interrupted, and the TUI's execute row now shows the recorded content.

The core tests for execute, shell and session-runner pass. The new tests fail on the base, as they should. typecheck:tsc is clean in both packages.

Public API: @opencode/codemode gets an optional "execution.interrupted" hook on Hooks and a new exported InterruptedExecution type. The session.tool.failed event's existing optional content field is now filled in when an interrupted execute or shell call produced output. Both are additive and backwards compatible.

No blocking concerns.

Fixes #39565

Before

After

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only new commit raises the wait in the shell interrupt test helper from 10s to 20s for slow PowerShell starts on Windows. That still fits within the core test runner's 30s per-test timeout. Source code is unchanged, so my earlier verification and API notes still apply. No new concerns.

@rekram1-node
rekram1-node force-pushed the interrupted-partial-output branch 2 times, most recently from 56ee1be to 692dc6b Compare October 30, 2026 03:31
@rekram1-node rekram1-node changed the title fix(core): keep partial output when execute or shell is interrupted fix(core): keep execute's logs and calls when it is interrupted Oct 30, 2026

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reworked approach works for execute. I ran an execute in the TUI with the simulated model and interrupted it while a fetch was hanging. On the base the next model request carries content: []. With this PR it carries Execution was interrupted., the program's log line and the call list (fetch: interrupted), and the error is still Tool execution interrupted. The core session-runner, tool-execute and codemode tests and the codemode package tests pass, and typecheck:tsc is clean in both packages.

Public API (@opencode/codemode is published):

  • Runtime.execute(code, bindings?) → Runtime.execute(code, options?: RunOptions). This is not backwards compatible: callers that pass bindings as the second argument must switch to { bindings } (see inline comment).
  • New: the RunOptions type (bindings, stop), stop on ExecuteOptions, and the Interrupted value in DiagnosticKind (code that handles every kind may need a new case).
  • session.tool.failed now fills its existing optional content field for interrupted execute calls, which is backwards compatible.

Since the shell part was dropped, an interrupted shell still loses its output. #39565 is no longer fixed by this PR, so the Fixes #39565 my first review added no longer applies.

  • packages/codemode/src/codemode.ts:148: This changes the published Runtime.execute signature from (code, bindings?) to (code, options?). TypeScript callers that pass bindings positionally will stop compiling, and JS callers will silently lose their bindings because the record is read as RunOptions. If that break isn't intended, stop could go in a third argument (or both shapes could be accepted) so existing callers keep working. Otherwise it's worth calling out as a breaking change for the package.

Before

After

@rekram1-node
rekram1-node force-pushed the interrupted-partial-output branch from 692dc6b to 5ea1245 Compare October 31, 2026 17:05
@rekram1-node rekram1-node changed the title fix(core): keep execute's logs and calls when it is interrupted feat(codemode): return logs and calls when a run is stopped Oct 31, 2026

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My earlier point on the Runtime.execute signature is addressed: stop now goes in a new optional third argument, so execute(code, bindings) callers are unaffected. The stop signal works as described. The new codemode tests pass, and the two stop result tests fail on the base. Core's execute and codemode tests pass, and typecheck:tsc is clean in both packages.

Public API (@opencode/codemode), all additions and backwards compatible:

  • Runtime.execute(code, bindings?) → execute(code, bindings?, options?: RunOptions).
  • New RunOptions type with stop?: Effect<void>, also accepted by ExecuteOptions.
  • New "Interrupted" value in DiagnosticKind. Code that handles every kind may need a new case; nothing in this repository does.

No new concerns.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

core: interrupted tools cannot retain partial results

1 participant