fix(server): Claude plan mode never shows Implement - #7263
Conversation
Plan-mode turns that skip ExitPlanMode now emit turn.proposed.completed from the success result (assistant text as fallback) so Implement can arm. Implemented with grok-4.6 in T3 Code.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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 |
ApprovabilityVerdict: Needs human review This PR adds new runtime behavior to Claude's plan mode, enabling 'Implement' to appear when ExitPlanMode is skipped. While the fix is well-tested, it modifies core agent adapter logic and is the author's first contribution to this file. You can customize Macroscope's approvability policy. Learn more. |
Fixes #2519
What Changed
ClaudeAdapter.sendTurnsetsinPlanModeon the existinginteractionModeplan/default branches. AbsentinteractionModeleaves the previous value unchanged.completeTurnthen calls the existingemitProposedPlanCompletedhelper when the SDK result is present, status iscompleted, it is not a refusal,inPlanModeis true, the turn is not synthetic,capturedProposedPlanKeysis empty, and no user input is pending. Plan text comes fromresult.result, then assistantfallbackText. Raw source staysclaude.sdk.message; the method isclaude/result/plan-text.Why
Claude Opus in plan mode often writes the plan as markdown and skips
ExitPlanMode.proposedPlansstays empty, so the Implement control never arms. Users type "go". Confirmed on the issue through 2026-08-05.Implement already renders once
turn.proposed.completedlands (ChatView.tsx:2301,hasActionableProposedPlanatsession-logic.ts:678). Claude only filled that event fromExitPlanMode(canUseToolatClaudeAdapter.ts:3997, assistant snapshot at:2978).completeTurnalready owns successful turn end. One extra call through the existing helper, gated bycapturedProposedPlanKeys.size(ExitPlanMode keys aretool:${id}, so the helper's markdown key would not collide), is the smallest way to arm the same control. Codex already emits this from a native plan item (CodexAdapter.ts:1117); Cursor fromcursor/create_plan(CursorAdapter.ts:626).Synthetic auto-close, interrupt, failed turns, refusals, pending AskUserQuestion, and later background turns stay out.
Test
prefers result text over assistant text when ExitPlanMode is skippedfailed onupstream/mainbefore the fix (AssertionError: expected undefined to equal 'turn.proposed.completed'). It passes oncecompleteTurnemits through the existing helper.stop_reason: "refusal", ExitPlanMode already captured, pending user input, later synthetic turn.vp test run apps/server/src/provider/Layers/ClaudeAdapter.test.ts— 77 passed.no-useless-spreadwarning atClaudeAdapter.ts:3708, untouched).vp run --filter t3 typecheck: ok (pre-existing suggestions in unrelated files).UI Changes
No UI change.
Deliberately not included
ComposerPrimaryActions.tsx:191andChatView.tsx:5718.claude.sdk.text-fallbackRuntimeEventRawSource(packages/contracts/src/providerRuntime.ts:21). Existingclaude.sdk.messageplusrawMethodis enough.looksLikePlanheading/list scoring. The gate is plan mode plus non-empty result/assistant text (ClaudeAdapter.ts:2373).startSessionpermission mapping (ClaudeAdapter.ts:4134, fix(server): start Claude full access without forbidden permission modes (#4927) #7246).ClaudeAdapter.ts:1802, fix(server): key assistant text blocks by message id instead of stream position #7143).ClaudeAdapter.ts:1920, fix(server): backfill assistant text from the snapshot when a stalled stream never closes its block #7142).ClaudeAdapter.ts:3125, fix(server): refresh Claude context meter after compact #7249).ClaudeAdapter.ts:659, fix(server): point Claude resume cursor at reverted transcript #7198).ClaudeAdapter.ts:1488, fix(server): persist late Claude tool results after turn finalize #7192).session-logic.ts:678,threadPresentation.ts:112).CodexAdapter.ts:1117,CursorAdapter.ts:626). Grok and OpenCode have noturn.proposedpath.Checklist
Implemented with grok-4.6 in T3 Code.
Note
Fix Claude plan mode to emit proposed plan when
ExitPlanModetool-use is skippedExitPlanModetool-use, the adapter now emits aturn.proposed.completedevent using result text (falling back to assistant text) via a newplanMarkdownFromSkippedExithelper in ClaudeAdapter.ts.inPlanModeflag onClaudeSessionContexttracks whether the current turn is in plan mode, set bysendTurnbased oninteractionMode.ExitPlanMode, and turns with pending user input.Macroscope summarized 92ec3da.
Note
Low Risk
Scoped to Claude adapter turn completion and plan-mode gating; reuses existing emit helper with dedupe guards and no UI or API contract changes.
Overview
Fixes plan mode where Implement never appeared because Claude often finished with markdown in the SDK result instead of calling ExitPlanMode.
ClaudeAdapter now tracks
inPlanModeon the session (set fromsendTurnwheninteractionModeisplanordefault). On a successful turn completion, if the turn is in plan mode and no plan was already captured from ExitPlanMode, it emitsturn.proposed.completedthrough the existing helper, usingplanMarkdownFromSkippedExit(SDKresulttext first, then assistant text fallback) with raw methodclaude/result/plan-text.Emission is skipped for non–plan-mode turns, refusals, empty text, duplicate capture after ExitPlanMode, pending AskUserQuestion, and synthetic follow-up turns. Tests cover the happy path and these guardrails.
Reviewed by Cursor Bugbot for commit 92ec3da. Bugbot is set up for automated code reviews on this repo. Configure here.