2.1.275: switching a bypassPermissions session to plan mode routes every read-only Bash call to canUseTool (regression from 2.1.246)

Status Open
Reported on v2.1.275
Maintainer reply None cached
Activity 0 comments · opened Sep 20, 2026

Summary

Since 2.1.275, a session that starts in bypassPermissions and is later switched to plan (via the Agent SDK's query.setPermissionMode("plan") / the set_permission_mode control request) no longer gets plan mode's automatic allow for read-only Bash. Every Bash call — git status --short, git log --oneline -3, grep … — is routed to canUseTool (i.e. a permission prompt). 2.1.246 does not do this. Starting in acceptEdits and switching to plan is fine on both versions.

Any host that lets the user toggle modes on a live session (Paseo does; Shift+Tab in the CLI presumably too) hits this: plan mode becomes "ask for everything".

Repro (Agent SDK 0.3.246)

import { query } from "@anthropic-ai/claude-agent-sdk";

const startMode = process.argv[2]; // "bypassPermissions" or "acceptEdits"
const calls = [];
const user = (content) => ({ type: "user", message: { role: "user", content }, session_id: "", parent_tool_use_id: null });
let go2, end; const gate2 = new Promise(r => go2 = r); const gateEnd = new Promise(r => end = r);
async function* prompts() {
  yield user("Reply 'ok' only. Use no tools.");
  await gate2;
  yield user("Read-only investigation for a plan: run Bash `git status --short`, then `git log --oneline -3`. Report one line. Don't ask questions or write a plan file.");
  await gateEnd;
}
const q = query({ prompt: prompts(), options: {
  cwd: process.cwd(), model: "claude-sonnet-5",
  permissionMode: startMode, allowDangerouslySkipPermissions: true,
  pathToClaudeCodeExecutable: process.env.CLAUDE_BIN, // 2.1.275 vs 2.1.246
  canUseTool: async (name, input) => { calls.push(`${name} ${JSON.stringify(input).slice(0, 60)}`); return { behavior: "allow", updatedInput: input }; },
}});
let phase = 1;
for await (const m of q) if (m.type === "result") {
  if (phase++ === 1) { await q.setPermissionMode("plan"); go2(); continue; }
  console.log(`start=${startMode} -> plan, canUseTool calls: ${calls.length}`); calls.forEach(c => console.log("  " + c)); end(); break;
}

Results

| start mode → plan | CLI 2.1.246 | CLI 2.1.275 |
|---|---|---|
| bypassPermissions | 0 canUseTool calls | 3 calls (git status --short, git log --oneline -3, grep …) |
| acceptEdits | 0 | 0 |

Plan mode is genuinely active in the failing case (the model refuses Write outside the plan file, ExitPlanMode is offered) — it is only the read-only Bash auto-allow that is lost. Also reproduced with acceptEdits → bypassPermissions → plan, so the trigger is "has been in bypass at any point before plan", not the launch flag alone. The CLI was launched with --permission-mode bypassPermissions --allow-dangerously-skip-permissions --permission-prompt-tool stdio.

Expected

After setPermissionMode("plan"), read-only Bash should be auto-allowed exactly as it is when the session was never in bypass.

Environment

  • Claude Code 2.1.275 (native install), Linux x86_64
  • @anthropic-ai/claude-agent-sdk 0.3.246
  • Same behaviour observed through Paseo 0.8.0, which drives the SDK the same way

View original on GitHub ↗