Skip to content

fix(session): exclude interrupted orphan tool parts from run-loop continuation#1120

Merged
Astro-Han merged 2 commits into
devfrom
claude/fix-26178-runloop-interrupted-orphan
Jun 3, 2026
Merged

fix(session): exclude interrupted orphan tool parts from run-loop continuation#1120
Astro-Han merged 2 commits into
devfrom
claude/fix-26178-runloop-interrupted-orphan

Conversation

@Astro-Han

Copy link
Copy Markdown
Owner

Summary

The session run loop could fire an extra, wasteful LLM request after a tool call was interrupted. When the interrupt cleanup marks an abandoned tool part as state.status: "error" with metadata.interrupted: true, the loop's hasToolCalls check still counted it as a live tool call, so an assistant turn that finished with a non-tool-calls reason (e.g. "stop") but still carried such an orphan kept hasToolCalls = true. That suppressed the !hasToolCalls loop-exit guard, so instead of stopping the loop did another assistant prefill request.

This excludes interrupted orphans from hasToolCalls (new isOrphanedInterruptedTool helper) and logs a diagnostic warn when the loop exits with one present.

No existing PawWork issue tracked this; it was found by mining upstream.

Why

packages/opencode/src/session/prompt.ts:2006 computed hasToolCalls as "any non-provider-executed tool part exists". The interrupt cleanup at processor.ts:1097-1110 / session.ts independently stamps interrupted tool parts as status: "error", metadata.interrupted: true. An errored/interrupted tool is abandoned work, not a pending tool call awaiting a result, so it must not keep the loop alive — otherwise every interrupt that leaves a trailing non-tool-calls finish costs one needless model request.

Related Issue

None. Semantic port of upstream anomalyco/opencode #26178 (748fcb7ebd) — thanks to André Cruz and Aiden Cline. dev and upstream/dev share no common ancestor, so this is a re-implementation adapted to PawWork's Zod MessageV2 and the public prompt.loop(), not a cherry-pick.

Human Review Status

Pending

Review Focus

The hasToolCalls predicate change and the orphan-detection helper in prompt.ts. Confirm an interrupted orphan must not be treated as pending work, and that the exit-guard semantics are otherwise unchanged for normal tool-call turns.

Risk Notes

Low. Pure narrowing of one loop-exit predicate; only affects sessions whose last assistant message carries an interrupted/errored tool part with no newer user message — exactly the case that previously caused an unwanted extra request. No schema, storage, or API surface changed.

Conditional checklist items left unticked:

  • Visible UI/copy: none changed (engine-only).
  • macOS/Windows platform surface: none touched.
  • Docs/release-notes/deps/permissions/credentials: none touched.

How To Verify

bun test test/session/prompt.test.ts -t "interrupted orphan"
  - before fix: FAIL — loop made an extra request and returned a newly-created assistant message
  - after fix:  PASS — 1 pass, 2 expect() calls; loop returns the seeded assistant, 0 LLM requests
bun test test/session/prompt.test.ts   -> 12 pass, 0 fail (11 existing + 1 new regression)
bun run typecheck (tsgo --noEmit, packages/opencode) -> clean

Screenshots or Recordings

N/A — no visible UI change.

Checklist

  • Type label — this PR carries exactly one of bug, enhancement, task, documentation.
  • Routing labels — this PR carries at least one of app, ui, platform, harness, ci. (Confirm bot choice after open.)
  • Priority label — this PR carries exactly one of P0, P1, P2, P3. (Confirm bot suggestion after open.)
  • Human Review Status above is set to Pending, Approved by @<reviewer>, or Not required: <reason>.
  • I linked the related issue, or stated in Summary why there is no issue.
  • I described the review focus and any meaningful risks.
  • I replaced the example block in How To Verify with the real verification steps and the key result for each.
  • I did not introduce unrelated refactors, dependencies, generated files, or file changes beyond the stated scope.
  • (conditional) I manually checked visible UI or copy changes when needed, with screenshots or recordings. Leave unticked only if no visible UI or copy changed.
  • (conditional) I considered macOS and Windows impact for platform, packaging, updater, signing, paths, shell, or permissions changes. Leave unticked only if no platform/packaging surface was touched.
  • (conditional) I called out docs, release notes, dependencies, permissions, credentials, deletion behavior, generated content, or local file changes when relevant. Leave unticked only if none of those surfaces was touched.
  • I reviewed the final diff for unrelated changes and suspicious dependency changes.
  • I am targeting dev, and my PR title and commit messages use Conventional Commits in English.

…tinuation

After a tool call is interrupted/aborted, the cleanup path marks its tool part as state.status "error" with metadata.interrupted=true (processor.ts / session.ts). The run loop's hasToolCalls check counted any non-provider-executed tool part as a live tool call, so an assistant turn that finished with a non "tool-calls" reason (e.g. "stop") but still carries such an orphan kept hasToolCalls=true. The loop-exit guard (!hasToolCalls) was suppressed and the loop fired an extra, wasteful LLM prefill request instead of stopping.

Exclude interrupted orphans from hasToolCalls via isOrphanedInterruptedTool(), and log a diagnostic warn when the loop exits with one present. Adds a regression test that seeds a finished assistant turn carrying an interrupted error tool part and asserts loop() returns that message with zero LLM requests.

Semantic port of upstream anomalyco/opencode #26178 (748fcb7ebd) — thanks to André Cruz and Aiden Cline. dev and upstream/dev share no ancestor, so this is a re-implementation adapted to PawWork's Zod MessageV2 and public prompt.loop().
@Astro-Han Astro-Han added the bug Something isn't working label Jun 3, 2026
@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@Astro-Han, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 5 minutes and 42 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c83edbf8-6754-4489-8daa-7f9eb26b87ef

📥 Commits

Reviewing files that changed from the base of the PR and between 4f55a10 and 85f2d2b.

📒 Files selected for processing (2)
  • packages/opencode/src/session/prompt.ts
  • packages/opencode/test/session/prompt.test.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/fix-26178-runloop-interrupted-orphan

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added harness Model harness, prompts, tool descriptions, and session mechanics P2 Medium priority labels Jun 3, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested priority: P2 (includes non-doc, non-test paths outside the low-risk bucket).

P1/P0 are reserved for maintainer confirmation. Please relabel manually if this is a release blocker, security issue, data-loss risk, or updater/runtime failure.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a helper function isOrphanedInterruptedTool to identify abandoned tool calls marked as interrupted during cleanup. It updates the session prompt loop to ignore these orphaned tool calls, preventing them from being treated as live tool calls and incorrectly re-triggering the loop. A warning log is also added when exiting the loop with an orphaned tool, and a new regression test verifies this behavior. There are no review comments, so I have no feedback to provide.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Adds the mixed case from upstream #26178's coverage notes (raised in Codex review of this PR): an interrupted orphan alongside a real completed tool part must still keep the loop alive, guarding against the orphan exclusion ever over-suppressing a legitimate continuation.
@Astro-Han Astro-Han merged commit f65ff2f into dev Jun 3, 2026
33 checks passed
@Astro-Han Astro-Han deleted the claude/fix-26178-runloop-interrupted-orphan branch June 3, 2026 09:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working harness Model harness, prompts, tool descriptions, and session mechanics P2 Medium priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant