Feature request: exclusive-allowlist mode for --allowedTools (currently additive on top of built-in default safe-list)

Resolved 💬 2 comments Opened May 26, 2026 by pacifik80 Closed May 30, 2026

Problem

--allowedTools is documented and intuitive as a whitelist, but at runtime it
behaves additively — it appends to the Claude CLI's default safe-list of
built-in tools rather than replacing it. The only way to truly restrict a
subprocess to a specific tool set today is to enumerate everything else in
--disallowedTools, which means tracking every new built-in tool Claude Code
ships.

Reproduction

Spawn a Claude Code subprocess with:

claude --print --output-format stream-json \
  --allowedTools Write \
  --disallowedTools Bash,PowerShell,Edit,MultiEdit,NotebookEdit,Glob,Grep,Task,ToolSearch,WebFetch,WebSearch \
  --strict-mcp-config --mcp-config <per-run-config>.json \
  --model claude-opus-4-7 \
  ...

The intent is "this subprocess should only call Write." In the
agent_session_init event, the subprocess actually has 20 built-in tools
available:

AskUserQuestion, CronCreate, CronDelete, CronList,
EnterPlanMode, EnterWorktree, ExitPlanMode, ExitWorktree,
ListMcpResourcesTool, Monitor, PushNotification, Read,
ReadMcpResourceTool, RemoteTrigger, ScheduleWakeup,
Skill, TaskOutput, TaskStop, TodoWrite, Write

In our case the agent auto-invoked Skill('init') (the built-in /init
skill that generates CLAUDE.md from codebase analysis), then re-read its
own output file twice, ballooning a 130 s / \$0.85 step into 255 s / \$1.94
with 9 turns instead of the expected 3.

Use case context

We run Claude Code as a non-interactive subprocess from a Python orchestrator
("agent step in a multi-step pipeline"). Each step has a narrow contract —
e.g., "read inline-injected inputs, emit two files, exit." We've already done:

  • Fake HOME with only .credentials.json copied (no ~/.claude.json, no

user skills, no CLAUDE.md memory)

  • Scrubbed CLAUDE_* and CLAUDECODE env vars
  • --strict-mcp-config with a per-run config listing only the MCP servers

the step needs

  • --allowedTools listing only what the step's prompt requires

User-installed state (skills, MCP servers, settings) is correctly isolated.
But built-in tools ship inside the claude binary itself — filesystem
isolation can't strip them, so they leak into every subprocess.

Proposed solution

A flag like:

--strict-tools          # symmetric to --strict-mcp-config

Semantics: --allowedTools becomes an exclusive whitelist. Built-in tools
not in --allowedTools are unavailable (same effect as listing them in
--disallowedTools), without callers having to enumerate them.

Alternatively, a complementary flag:

--default-tools none    # disable all built-in tools by default

So callers can opt out of the default safe-list once instead of subtracting
each tool by name.

Why this matters

Without it, every Claude Code release that adds a built-in tool potentially
breaks downstream agentic harnesses. We've already hit this twice in the past
month — Skill and ScheduleWakeup showed up in subprocesses that hadn't
asked for them. The maintenance burden falls on each caller to keep their
--disallowedTools list in sync with the CLI's tool catalogue across versions.

A strict-mode flag would let callers express the constraint they actually
mean ("only these tools, please") and stay forward-compatible.

Workaround (today)

Enumerate every current built-in in --disallowedTools:

Bash,PowerShell,Edit,MultiEdit,NotebookEdit,Glob,Grep,Task,ToolSearch,
WebFetch,WebSearch,Read,Skill,TodoWrite,AskUserQuestion,EnterPlanMode,
ExitPlanMode,EnterWorktree,ExitWorktree,ScheduleWakeup,CronCreate,
CronDelete,CronList,Monitor,PushNotification,RemoteTrigger,TaskOutput,
TaskStop,ListMcpResourcesTool,ReadMcpResourceTool

This works but is fragile — every new built-in is silently opt-in until it
breaks something.

Related

  • --strict-mcp-config already exists and solves the exact same problem for

MCP servers; the same pattern for built-in tools would be a natural
extension.

Thanks for considering!

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗