Skip to content

Support plan mode and all other permission modes#40

Merged
agu-z merged 8 commits intomainfrom
permission-modes
Sep 9, 2025
Merged

Support plan mode and all other permission modes#40
agu-z merged 8 commits intomainfrom
permission-modes

Conversation

@agu-z
Copy link
Copy Markdown
Contributor

@agu-z agu-z commented Sep 9, 2025

Exposes all Claude Code permission modes as ACP Session modes. This allows ACP clients to build mode selectors and to be notified when the mode changes as a result of ExitPlanMode.

See a demo in the Zed PR.

Closes #31

@cla-bot cla-bot bot added the cla-signed label Sep 9, 2025
@agu-z agu-z merged commit 0289dd9 into main Sep 9, 2025
2 checks passed
@agu-z agu-z deleted the permission-modes branch September 9, 2025 14:48
@alistairstead
Copy link
Copy Markdown

🙇

AObuchow pushed a commit to AObuchow/claude-code-acp that referenced this pull request Jan 12, 2026
)

Exposes all Claude Code permission modes as [ACP Session
modes](agentclientprotocol/agent-client-protocol#67).
This allows ACP clients to build mode selectors and to be notified when
the mode changes as a result of `ExitPlanMode`.

See a demo in the [Zed
PR](zed-industries/zed#37632).

Closes agentclientprotocol#31

---------

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
Co-authored-by: Richard Feldman <oss@rtfeldman.com>
Co-authored-by: Bennet <bennet@zed.dev>
KUKUShuang pushed a commit to KUKUShuang/claude-code-acp that referenced this pull request Feb 11, 2026
benbrandt pushed a commit that referenced this pull request Mar 24, 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.plan` → `content` mapping in
`toolInfoFromToolUse()` so the plan markdown is included in the
`tool_call` and `request_permission` events.

**Before (broken):**
```typescript
case "ExitPlanMode": {
  return {
    title: "Ready to code?",
    kind: "switch_mode",
    content: [],
  };
}
```

**After (fixed):**
```typescript
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](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

- [x] Added test: plan text is included in content when `input.plan` is
provided
- [x] Added test: content is empty when `input.plan` is not provided
- [x] All existing tests pass

Fixes #450
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.

Plan mode

2 participants