Skip to content

fix(grok): show Grok plan-mode plans and handle plan approvals over ACP#4233

Open
Emmanuek5 wants to merge 5 commits into
pingdotgg:mainfrom
Emmanuek5:fix/grok-plan-mode
Open

fix(grok): show Grok plan-mode plans and handle plan approvals over ACP#4233
Emmanuek5 wants to merge 5 commits into
pingdotgg:mainfrom
Emmanuek5:fix/grok-plan-mode

Conversation

@Emmanuek5

@Emmanuek5 Emmanuek5 commented Jul 21, 2026

Copy link
Copy Markdown

Problem

Grok's plan mode was broken in T3 Code. When the Grok CLI finishes planning it intercepts its exit_plan_mode tool and sends a reverse request _x.ai/exit_plan_mode ({sessionId, toolCallId, planContent}) to the ACP client for approval. T3 Code had no handler for it, so the request failed with method-not-found, which Grok surfaces as:

Plan approval could not be completed because the client disconnected. Plan mode remains active; the approval will reappear on reconnect.

The plan never displayed in the UI and the Grok session stayed stuck in plan mode.

Wire protocol (discovered by live-probing grok agent stdio)

  • {outcome: "approved"} → Grok exits plan mode (current_mode_updatedefault) and implements the plan.
  • Any unrecognized outcome (e.g. "rejected") with a feedback string → "The user wants to revise the plan." + feedback; the turn ends and plan mode stays active.
  • There is no client-side way to toggle plan mode over ACP (no session modes advertised; _x.ai/toggle_plan_mode is rejected). Plan mode is only entered via the agent's own enter_plan_mode tool.

Fix

  • XAiAcpExtension.ts: schema for the request (plain + wrapped forms) and response builders.
  • GrokAdapter.ts:
    • Registers handlers for x.ai/exit_plan_mode / _x.ai/exit_plan_mode.
    • First presentation: emits turn.proposed.completed with the plan markdown (same proposed-plan card flow as Claude/Cursor) and answers with a revise-and-wait response so Grok ends its turn cleanly.
    • Once a plan is captured, the next turn whose interactionMode is not "plan" (the implementation turn started from the plan card) answers approved, so Grok exits plan mode and builds. Plan-mode refinement turns capture the revised plan again.
    • Tracks current_mode_update and, when a turn arrives with interactionMode: "plan" while the Grok session is not in plan mode, prepends an instruction steering Grok into enter_plan_mode - previously T3 Code's plan toggle silently did nothing for Grok.
    • Race recovery: grok-4.5 sometimes emits the plan-file write and exit_plan_mode in one batched response; the CLI's plan-file read then races its own write and planContent arrives null. The adapter remembers the plan-file path from the enter_plan_mode tool output (rawOutput.Entered.plan_file_path) and poll-reads the file when planContent is empty.
  • acp-mock-agent.ts + tests: mock support for the ext request (including the empty-planContent recovery path), adapter tests for capture → refine → approve, and schema unit tests.

Verification

  • Drove the real Grok CLI through the adapter in a temporary integration test: plan captured as a proposed plan on turn 1, "Implement the plan." on turn 2 auto-approved the parked approval, current_mode_update flipped plandefault, and Grok edited the target file. (Test removed before commit - it requires an authenticated local Grok install.)
  • vp test run src/provider/acp/XAiAcpExtension.test.ts - 14/14 pass.
  • New GrokAdapter mock tests follow the existing wrapper pattern (they cannot run on this Windows dev machine - the .sh mock wrapper fails to spawn for all pre-existing GrokAdapter tests too - so they rely on CI).
  • tsgo --noEmit and vp check clean.

Note

Medium Risk
Touches Grok session lifecycle and auto-approval of plan exit over ACP; incorrect gating could approve or block plans wrongly, though logic is covered by new tests and path validation limits file-read abuse.

Overview
Fixes broken Grok plan mode where unhandled _x.ai/exit_plan_mode reverse requests left sessions stuck and plans invisible in the UI.

ACP extension and adapter: Adds XAiExitPlanModeRequest parsing and approved/rejected response builders in XAiAcpExtension.ts. GrokAdapter registers handlers for x.ai/exit_plan_mode and _x.ai/exit_plan_mode, emits turn.proposed.completed with plan markdown (same card flow as Claude/Cursor), and answers with a revise-and-wait response so Grok ends the turn cleanly. shouldAutoApproveGrokPlan gates auto-approval on a captured plan plus a newer non-plan user prompt (promptSerial / planCapture), so same-turn re-presentations and plan-mode refinement turns capture again instead of approving on the user's behalf.

Plan recovery and steering: Session state tracks planFilePath from EnterPlanMode tool updates and poll-reads the plan file when planContent is empty (CLI write/read race). When the thread is in plan mode but the Grok session is not, prompts get a leading instruction to call enter_plan_mode. extractGrokPlanFilePath only accepts absolute, NUL-free paths.

Tests: Mock agent env flags for exit-plan-mode scenarios; adapter tests for capture → refine → approve, same-turn double presentation, and file recovery; unit tests for extension helpers and approval logic.

Reviewed by Cursor Bugbot for commit 8f927f2. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add Grok plan-mode plan capture and approval handling over ACP

  • The GrokAdapter now intercepts _x.ai/exit_plan_mode extension requests and captures the proposed plan markdown, emitting a turn.proposed.completed event and responding with a rejected outcome to keep Grok in plan mode until the user acts.
  • Plans are auto-approved only after a fresh non-plan user prompt has occurred since the plan was captured, determined by the new shouldAutoApproveGrokPlan helper.
  • If planContent is missing from the request, the adapter recovers the plan by reading the file path previously reported via EnterPlanMode tool call updates.
  • New helpers in XAiAcpExtension.ts parse both bare and wrapped exit_plan_mode requests and construct approval or capture responses.
  • The ACP mock agent gains T3_ACP_EMIT_XAI_EXIT_PLAN_MODE and related flags to simulate Grok plan-mode exit flows for local testing.

Macroscope summarized 8f927f2.

When Grok finishes planning it sends a reverse request
_x.ai/exit_plan_mode to the ACP client. T3 Code had no handler, so the
request failed method-not-found and Grok reported "Plan approval could
not be completed because the client disconnected" - the plan never
displayed and plan mode stayed stuck.

GrokAdapter now registers handlers for x.ai/exit_plan_mode and
_x.ai/exit_plan_mode: the first presentation is captured as a
turn.proposed.completed event (same plan-card flow as Claude/Cursor)
with a revise-and-wait response so Grok ends its turn; once a plan is
captured, the next non-plan turn (the implementation turn) answers
"approved" so Grok exits plan mode and builds. The adapter also tracks
current_mode_update and nudges Grok into plan mode when a turn arrives
with interactionMode "plan" while the session is not in plan mode.

grok-4.5 sometimes batches the plan-file write and exit_plan_mode in a
single response; the CLI then races its own plan read and planContent
arrives empty. The adapter remembers the plan-file path from the
enter_plan_mode tool output and recovers the plan by polling the file.

Verified end to end against the real grok CLI (plan captured on turn 1,
approved + implemented on turn 2, current_mode_update plan -> default).
Copilot AI review requested due to automatic review settings July 21, 2026 14:16
@github-actions github-actions Bot added size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Jul 21, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b1798b070f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +720 to +722
if (ctx?.planCaptured === true && ctx.activeInteractionMode !== "plan") {
ctx.planCaptured = false;
return makeXAiExitPlanModeApprovedResponse();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Require a subsequent user turn before approving a captured plan

When Grok enters plan mode during a normal (default) turn, the first exit_plan_mode request sets planCaptured, but activeInteractionMode remains default. If Grok issues another exit_plan_mode request in that same prompt—for example after processing the rejection feedback and revising the plan—this condition immediately returns approved, allowing implementation before the user has reviewed or accepted the proposed-plan card. Track the turn that captured the plan (or a later user submission) rather than using only the interaction-mode value.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 28fd2a6 and refined in 0e7464e: plan captures now record a monotonic prompt serial, and approval requires at least one new non-plan user prompt after the capture. A plan re-presented by Grok within the same prompt is captured again, never auto-approved. Regression tests: same-prompt double presentation (both captured) plus unit coverage of shouldAutoApproveGrokPlan.

Copilot AI 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.

Pull request overview

Fixes Grok “plan mode” integration over ACP by handling the reverse _x.ai/exit_plan_mode extension request, surfacing the plan as a proposed-plan card in the UI, and correctly responding with approval on the subsequent implementation turn.

Changes:

  • Add ACP extension schema/helpers for exit_plan_mode (plain + wrapped) plus response builders.
  • Update GrokAdapter to capture plans via turn.proposed.completed, track plan/interaction mode state, and recover missing planContent by reading the plan file path from enter_plan_mode.
  • Extend the ACP mock agent and add tests covering capture → refine → approve and the empty-planContent recovery path.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
apps/server/src/provider/Layers/GrokAdapter.ts Registers/handles Grok exit_plan_mode, tracks plan-mode state, and recovers plans from disk when needed.
apps/server/src/provider/Layers/GrokAdapter.test.ts Adds adapter-level tests for plan capture/refine/approve sequencing and plan-file recovery.
apps/server/src/provider/acp/XAiAcpExtension.ts Introduces schema + helpers for Grok’s exit_plan_mode reverse request and response builders.
apps/server/src/provider/acp/XAiAcpExtension.test.ts Adds unit tests for exit_plan_mode decoding, plan extraction, and response shapes.
apps/server/scripts/acp-mock-agent.ts Adds mock emission of _x.ai/exit_plan_mode (including empty planContent + plan-file-path race simulation).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +233 to +239
/**
* Grok interprets the response outcome as: `approved` exits plan mode and
* tells the agent to implement the plan; `abandoned` keeps plan mode active
* and tells the agent the user does not want to exit; any other outcome is
* treated as "revise" — the tool completes with "The user wants to revise the
* plan." plus the feedback text, and plan mode stays active.
*/

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 28fd2a6 - the doc now states that abandoned exists in Grok's protocol but is never sent by T3 Code, so the type models only the produced outcomes.


it("extracts the plan from wrapped _x.ai exit_plan_mode payloads", () => {
const decoded = decodeXAiExitPlanModeRequest({
method: "x.ai/exit_plan_mode",

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 28fd2a6 - the wrapped-payload test now uses the _x.ai/exit_plan_mode method.

// Re-read per attempt: the enter_plan_mode update that
// carries the path is processed on a concurrent fiber.
const planFilePath = ctx.planFilePath;
if (planFilePath) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added in 28fd2a6 (tightened in f392222): the agent-supplied path must be NUL-free and rooted at a drive letter or POSIX "/" before the adapter reads it.

Comment thread apps/server/src/provider/Layers/GrokAdapter.ts Outdated
Comment thread apps/server/src/provider/Layers/GrokAdapter.ts
Comment thread apps/server/src/provider/Layers/GrokAdapter.ts
@macroscopeapp

macroscopeapp Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces significant new Grok plan-mode handling capability (620+ new lines), not a simple fix. Additionally, there's an unresolved P1 review comment identifying a potential bug where plans could be auto-approved before user review. Both the scope and the open correctness concern warrant human review.

You can customize Macroscope's approvability policy. Learn more.

- Only auto-approve a captured plan when exit_plan_mode arrives on a
  later non-plan turn: planCapture records the capturing turn id, so a
  plan re-presented within the same turn (e.g. right after revise
  feedback) is captured again instead of approved on the user's behalf
  (Codex P1 / Bugbot high).
- Guard plan-file recovery against stale reads: contents matching the
  previously captured plan are treated as a rewrite in flight and only
  used as a fallback after polling (Bugbot medium).
- Stop waiting early when no plan-file path is known instead of holding
  the exit_plan_mode response for the full poll window (Bugbot medium).
- Validate the agent-supplied plan_file_path (absolute, NUL-free)
  before reading it from disk (Copilot).
- Clarify the exit_plan_mode response doc (abandoned exists in Grok's
  protocol but is never sent) and use the _x.ai method in the wrapped
  payload test (Copilot).
- Run the plan-mode adapter tests with TestClock.withLive: the recovery
  loop sleeps on the live clock, matching the other prompt-flow tests.

Re-verified end to end against the real grok CLI, including the batched
write+exit_plan_mode race (plan recovered from the plan file, captured
once, approved on the implementation turn, file edited).
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 983116e7-42ce-48b9-82f2-49a9617db992

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Comment thread apps/server/src/provider/Layers/GrokAdapter.ts Outdated
Comment thread apps/server/src/provider/Layers/GrokAdapter.ts
A bare leading backslash is a relative path on POSIX, so treating it as
absolute let a malformed agent-supplied plan_file_path resolve against
the server working directory. Reject it.
if (
planFilePath.length > 0 &&
!planFilePath.includes("\0") &&
/^(?:[A-Za-z]:[\\/]|\/)/.test(planFilePath)

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.

Windows absolute paths rejected

Low Severity

The absolute-path check now requires a drive letter or a leading /, so Windows UNC paths and current-drive roots that start with \ are dropped. When planContent is empty, plan-file recovery never runs and the UI falls back to a placeholder plan.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f392222. Configure here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Deliberate trade-off after the sibling Macroscope finding (backslash roots are relative on POSIX): Grok stores plan files under the user's home directory on a lettered drive, so UNC/current-drive-relative roots do not occur in practice, and this path only gates the empty-planContent recovery fallback - rejecting it degrades to the placeholder card, never breaks approval. Happy to add platform-aware UNC support if maintainers prefer.

Gating approval on a different turn id broke the implement click that
steers into the still-running capturing turn (steers reuse the active
turn id), leaving Grok stuck in plan mode. Track a monotonic prompt
serial instead: approval requires a plan captured earlier plus at least
one new non-plan user prompt since the capture - a new turn and a steer
both qualify, while Grok re-presenting on its own within the same
prompt still gets captured. Decision extracted as
shouldAutoApproveGrokPlan with unit coverage.

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

There are 3 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 0e7464e. Configure here.

Comment thread apps/server/src/provider/Layers/GrokAdapter.ts Outdated
Comment thread apps/server/src/provider/Layers/GrokAdapter.ts Outdated
Two follow-up Bugbot findings on the prompt-serial gate:

- promptSerial now increments only after prompt preparation succeeds,
  right before the RPC dispatches. A prompt that failed validation or
  was interrupted before reaching Grok no longer counts as the fresh
  user prompt that unlocks plan auto-approval.
- The exit_plan_mode handler snapshots the serial at request entry and
  records that value on capture, so an implement steer landing while
  the capture is being processed (e.g. during plan-file recovery
  polling) still counts as arriving after the capture and can approve.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants