Skip to content

fix: remove stopReason short-circuit from auto-continue - #15

Open
JWebCoder wants to merge 2 commits into
khalilgharbaoui:masterfrom
JWebCoder:fix/auto-continue-stop-reason
Open

fix: remove stopReason short-circuit from auto-continue#15
JWebCoder wants to merge 2 commits into
khalilgharbaoui:masterfrom
JWebCoder:fix/auto-continue-stop-reason

Conversation

@JWebCoder

Copy link
Copy Markdown

Problem

The stopReason guard in shouldAutoContinueIncompleteTurn (added in v0.4.17) blocked auto-continue whenever Claude CLI emitted ANY protocol-level stop_reason — even when the model was actively generating tool calls in the same turn.

This caused the common "text + tool_calls" pattern to terminate prematurely:

  1. User sends a complex task
  2. Claude generates intermediate text ("I'll analyse...") + tool calls (task/sub-agents)
  3. Plugin sees stop_reason: end_turnblocks auto-continue immediately
  4. Turn closes before tool calls can execute
  5. User sees partial response, has to manually send "Continua então."

Root Cause

The stopReason check ran before the hadActivity check. When stop_reason was present (which it always is in modern Claude CLI), auto-continue was short-circuited regardless of whether the model was actively working.

The hadActivity check already covers the "model did nothing" case — if there was no reasoning, tool activity, or proxy activity, auto-continue stops correctly. The stopReason guard was redundant and counterproductive.

Fix

  1. Removed the stopReason guard from shouldAutoContinueIncompleteTurn. The hadActivity check already handles the "model did nothing" case correctly.

  2. Restructured the drain path at the result boundary: instead of calling drainNow() (which closes the controller via finishWithToolCalls), we now inline the tool-call enqueue and fall through to the auto-continue check. This prevents the controller from being closed before auto-continue gets a chance to fire.

Test Changes

Updated 8 tests in test-auto-continue.ts to reflect the new behaviour:

  • end_turn + tool activity → now continues (was: stopped)
  • max_tokens + tool activity → now continues (was: stopped)
  • stop_sequence + reasoning → now continues (was: stopped)
  • pause_turn + reasoning → now continues (was: stopped)
  • tool_use + tool activity → now continues (was: stopped)
  • refusal + no activity → still stops (via no-activity)
  • Unknown stop_reason + no activity → still stops (via no-activity)
  • end_turn + max-attempts → still stops (via max-attempts)

All 203 tests pass.

Impact

This fixes the premature turn termination users experience with Claude Opus 4.7 when generating sub-agent task calls. The model no longer needs manual "Continua então." prompts to keep working after generating tool calls.

The `stopReason` guard in `shouldAutoContinueIncompleteTurn`
(short-circuited since v0.4.17) blocked auto-continue whenever
Claude CLI emitted ANY protocol-level stop_reason, even when the
model was actively generating tool calls in the same turn.

This caused the common "text + tool_calls" pattern to terminate
prematurely: Claude would generate an intermediate response like
"I'll analyse..." and the plugin would close the turn before the
tool calls could execute.

The `hadActivity` check (line 466) already covers the "model did
nothing" case — if there was no reasoning, tool activity, or proxy
activity, auto-continue stops correctly. The stopReason guard was
redundant and counterproductive.

Also restructured the drain path at the result boundary: instead of
calling `drainNow()` (which closes the controller via
`finishWithToolCalls`), we now inline the tool-call enqueue and
fall through to the auto-continue check. This prevents the
controller from being closed before auto-continue gets a chance to
fire.

Fixes: premature turn termination with Claude Opus 4.7 when
generating sub-agent task calls.

@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 removes the stopReason guard from the auto-continue heuristic to prevent premature termination when tool calls are generated in the same turn. It also updates the language model to manually enqueue tool-call events rather than closing the controller immediately, and updates the corresponding tests. Feedback on these changes highlights a critical deadlock risk where failing to close the stream with pending proxy calls blocks both the CLI and opencode. Additionally, the reviewer notes a misleading version history comment and several outdated test names that contradict their updated assertions.

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.

Comment thread src/claude-code-language-model.ts Outdated
Comment thread src/claude-code-language-model.ts Outdated
Comment thread test-auto-continue.ts
- Reverted drain path at turn-result boundary to use drainNow()+return
  (prevents deadlock when proxy calls are pending)
- Fixed misleading version comment (removed guard was added in v0.4.17)
- Updated 8 test names to reflect new behavior (stopReason removed)
@JWebCoder

Copy link
Copy Markdown
Author

Updated per review:

  1. Drain path reverted - restored drainNow()+return to prevent deadlock when proxy calls are pending
  2. Comment fixed - now correctly reads Removed the stopReason guard which was added in v0.4.17
  3. Test names updated - 8 tests renamed to reflect new behavior e.g. end_turn with tool activity continues

All 203 tests pass, build clean.

@khalilgharbaoui

Copy link
Copy Markdown
Owner

Thanks for the deep analysis and the patience — this deserved a maintainer response much sooner, and the symptom you describe (text + tool_calls turns closing early, needing a manual "continue") is clearly real.

Two thoughts before merging this as-is:

  1. The root cause may be elsewhere. Reliable OpenCode subagents by default, --resume session continuation, JSONC configs, and session-handoff race fixes #18 found and fixed a result-before-broker ordering race in the Task proxy path where a result event arriving before the broker subscriber attached could drop the tool call and close the turn — which produces exactly the premature-termination pattern you're fixing here, and the author flagged it as likely the same class of failure as this PR. Could you retest your repro on top of Reliable OpenCode subagents by default, --resume session continuation, JSONC configs, and session-handoff race fixes #18's branch (or on master once it merges, targeted at v0.10.0)? If the race fix resolves your symptom, we get the cure without touching auto-continue semantics.

  2. Why I'm cautious about removing the stopReason guard globally: auto-continue has bitten us before — the AskUserQuestion self-answer bug (Questions are skipped #8) came from exactly this kind of "the model looked unfinished, so nudge it" logic firing when it shouldn't. Making end_turn + tool-activity always continue widens the surface where a legitimately finished turn gets nudged. If the Reliable OpenCode subagents by default, --resume session continuation, JSONC configs, and session-handoff race fixes #18 fix doesn't fully resolve your case, I'd rather scope the exception narrowly (e.g. only when the turn ended with unresolved tool calls in flight) than drop the guard entirely.

Leaving this open pending your retest — and thanks again for the excellent writeup, it made the failure mode easy to understand.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants