Skip to content

fix: restore plan content in ExitPlanMode tool call#451

Merged
benbrandt merged 2 commits intoagentclientprotocol:mainfrom
hancengiz:fix/exit-plan-mode-content
Mar 24, 2026
Merged

fix: restore plan content in ExitPlanMode tool call#451
benbrandt merged 2 commits intoagentclientprotocol:mainfrom
hancengiz:fix/exit-plan-mode-content

Conversation

@hancengiz
Copy link
Copy Markdown
Contributor

@hancengiz hancengiz commented Mar 22, 2026

Summary

The plan text was accidentally dropped from the ExitPlanMode content array during the switch to built-in Claude Code tools (#316, commit be618f5). This meant ACP clients could not display the plan when asking the user for approval — they only saw the title "Ready to code?" with no plan body.

We noticed this in the Fabriqa app where our plan approval UI was rendering an empty card.

What changed

Restored the original input.plancontent mapping in toolInfoFromToolUse() so the plan markdown is included in the tool_call and request_permission events.

Before (broken):

case "ExitPlanMode": {
  return {
    title: "Ready to code?",
    kind: "switch_mode",
    content: [],
  };
}

After (fixed):

case "ExitPlanMode": {
  const planInput = toolUse.input as { plan?: string };
  return {
    title: "Ready to code?",
    kind: "switch_mode",
    content:
      planInput?.plan
        ? [{ type: "content" as const, content: { type: "text" as const, text: planInput.plan } }]
        : [],
  };
}

Where does input.plan come from?

The Claude Agent SDK passes tool inputs to the canUseTool callback as Record<string, unknown> (see Options.canUseTool in @anthropic-ai/claude-agent-sdk/sdk.d.ts). When Claude calls ExitPlanMode, the model includes the plan text in the input.

The formal ExitPlanModeInput type (in @anthropic-ai/claude-agent-sdk/sdk-tools.d.ts) only declares allowedPrompts, but it has a [k: string]: unknown index signature that allows additional properties. The model consistently sends plan (string) and planFilePath (string). SDK v0.2.76 explicitly added planFilePath to the tool input for hooks and SDK consumers, confirming these fields are intentional.

The original implementation (commit 0289dd9, "Support plan mode and all other permission modes" #40) relied on input.plan and worked correctly until the refactor in #316 dropped it.

SDK history confirms input.plan is reliable

The Claude Agent SDK itself had a regression where ExitPlanMode input was empty (anthropics/claude-code#12288):

Version Status
Claude Code 2.0.34 input.plan populated
Claude Code 2.0.51 Regression — input: {} empty
Agent SDK 0.1.54 Fixed — input.plan restored
Agent SDK 0.2.76 (current) Working — also added planFilePath

The SDK team treated the empty input as a critical bug and fixed it promptly. The current SDK (0.2.76) reliably sends input.plan, so this adapter fix is safe to depend on.

Test plan

  • Added test: plan text is included in content when input.plan is provided
  • Added test: content is empty when input.plan is not provided
  • All existing tests pass

Fixes #450

The plan text was accidentally dropped from the ExitPlanMode content
array during the switch to built-in Claude Code tools (agentclientprotocol#316). This
meant ACP clients could not display the plan when asking the user
for approval — they only saw the title "Ready to code?" with no
plan body.

Restore the original `input.plan` → content mapping so the plan
markdown is included in the tool_call and request_permission events.

Fixes agentclientprotocol#450
@cla-bot cla-bot bot added the cla-signed label Mar 22, 2026
@benbrandt benbrandt merged commit 8349654 into agentclientprotocol:main Mar 24, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ExitPlanMode no longer sends plan content to ACP client

4 participants