Skip to content

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

Description

@pacifik80

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!

Metadata

Metadata

Assignees

No one assigned

    Labels

    duplicateThis issue or pull request already exists

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions