fix(session): exclude interrupted orphan tool parts from run-loop continuation#1120
Conversation
…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().
|
Warning Review limit reached
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 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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.
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"withmetadata.interrupted: true, the loop'shasToolCallscheck still counted it as a live tool call, so an assistant turn that finished with a non-tool-callsreason (e.g."stop") but still carried such an orphan kepthasToolCalls = true. That suppressed the!hasToolCallsloop-exit guard, so instead of stopping the loop did another assistant prefill request.This excludes interrupted orphans from
hasToolCalls(newisOrphanedInterruptedToolhelper) and logs a diagnosticwarnwhen 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:2006computedhasToolCallsas "any non-provider-executed tool part exists". The interrupt cleanup atprocessor.ts:1097-1110/session.tsindependently stamps interrupted tool parts asstatus: "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-callsfinish costs one needless model request.Related Issue
None. Semantic port of upstream
anomalyco/opencode#26178 (748fcb7ebd) — thanks to André Cruz and Aiden Cline.devandupstream/devshare no common ancestor, so this is a re-implementation adapted to PawWork's ZodMessageV2and the publicprompt.loop(), not a cherry-pick.Human Review Status
Pending
Review Focus
The
hasToolCallspredicate change and the orphan-detection helper inprompt.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:
How To Verify
Screenshots or Recordings
N/A — no visible UI change.
Checklist
bug,enhancement,task,documentation.app,ui,platform,harness,ci. (Confirm bot choice after open.)P0,P1,P2,P3. (Confirm bot suggestion after open.)Pending,Approved by @<reviewer>, orNot required: <reason>.dev, and my PR title and commit messages use Conventional Commits in English.