Summary
When a named Claude Code agent contains a scoped Bash rule in its frontmatter, such as:
disallowedTools: Bash(sed *)
Claude Code removes the entire Bash tool from the top-level --agent session. The problem is not the --agent flag by itself.
The resulting error is:
Error: No such tool available: Bash. Bash exists but is not enabled in this context.
Confirmed with Claude Code v2.1.156 in the fullsend sandbox and v2.1.207 locally.
Validated root cause
Controlled tests on v2.1.207:
| Agent configuration |
Tool count |
Bash available? |
No --agent |
24 |
Yes |
| Named agent, no tool restrictions |
24 |
Yes |
disallowedTools: Write, Edit |
22 |
Yes |
disallowedTools: Bash(sed) |
23 |
No |
disallowedTools: Bash(sed *) |
23 |
No |
| Full code-agent Bash deny patterns |
23 |
No |
The same result occurs with --bare, which disables hooks, plugins, custom settings, and other integrations.
Claude's documentation says a scoped rule such as Bash(rm *) should keep Bash visible and deny only matching commands. Named sessions launched with --agent are documented to use the agent's tool restrictions. The observed behavior therefore appears to be a Claude Code bug in scoped disallowedTools handling.
Documentation:
Minimal reproduction
Run a named agent with a scoped Bash deny rule:
claude \
--agents '{"bash-test":{"description":"Minimal test agent","prompt":"Reply OK.","disallowedTools":["Bash(sed *)"]}}' \
--agent bash-test \
--print --verbose --output-format stream-json \
'Reply exactly OK without tools.' |
jq 'select(.type == "system" and .subtype == "init") |
{tool_count: (.tools | length), has_bash: (.tools | index("Bash") != null)}'
Actual result:
{"tool_count":23,"has_bash":false}
Control case: remove disallowedTools from the inline agent definition. Bash remains present and the tool count is 24.
Fullsend impact
Fullsend launches named sessions with:
claude --agent '<name>' --dangerously-skip-permissions
The upstream code and fix agents currently contain multiple scoped Bash rules, including Bash(sed *), Bash(awk *), and Bash(git push *). As a result, those top-level agents start without Bash and delegate shell work to subagents.
The first observed production example was:
This confirms significant overhead, although the exact incremental cost and slowdown require a controlled comparison with an equivalent fixed run.
How we discovered it: the agent self-recovers through subagents
The failure did not initially look like a hard runtime error because the top-level agent worked around it autonomously. The transcript from the cited run shows the complete recovery path:
| Time (UTC) |
Event |
| 09:35:09 |
The top-level code agent attempts its required Bash setup command and receives No such tool available: Bash. |
| 09:35:28 |
It explicitly recognizes the limitation: “I don't have direct Bash access in this context” and decides to spawn a subagent. |
| 09:35:36 |
It spawns another subagent_type: code to perform the implementation. That agent inherits the same broken scoped disallowedTools rules, attempts Bash twice, and also fails. |
| 09:39:36 |
The top-level agent probes a generic subagent_type: claude with echo, pwd, and git status. Bash works immediately. |
| 09:40:11 |
Having identified a working agent type, it delegates the complete implementation to a generic claude subagent. |
| 10:08:53 |
The generic subagent completes the implementation after 75 tool calls, including 49 Bash calls. |
| 10:08:59 |
The top-level agent spawns a second generic claude subagent to verify the branch, commit, diff, and output file using six more Bash calls. |
| 10:09:46 |
The top-level agent reports success. |
The resulting session tree was:
| Session |
Role |
Tool calls |
Bash calls |
Bash availability errors |
Top-level code agent |
Orchestration |
12 |
1 |
1 |
Spawned code agent |
First implementation attempt |
47 |
2 |
2 |
Generic claude agent |
Bash availability probe |
1 |
1 |
0 |
Generic claude agent |
Actual implementation |
75 |
49 |
0 |
Generic claude agent |
Final verification |
6 |
6 |
0 |
This behavior is important for diagnosis:
- The run succeeds instead of failing fast, so the underlying tool-inventory bug is easy to miss.
- Bash works in the same sandbox when a generic
claude agent is used, ruling out the sandbox image and OpenShell policy as the cause.
- A spawned
code agent fails in the same way as the top-level code agent, tying the failure to that agent definition.
- The workaround multiplies contexts and tool calls: the top-level session performed only 12 tool calls, while the complete agent tree performed 141.
- The successful result therefore masks substantial latency and token cost caused by automatic delegation.
The evidence is in the fullsend-code artifact attached to:
Findings ruled out
--agent alone: a named agent without tool restrictions retains Bash.
- Security hooks: reproduced with
--bare; hooks cannot explain the missing init tool.
- Managed agent definitions: the minimal inline-agent reproduction does not depend on them.
- Built-in
code agent: Claude Code has no built-in agent with that name.
agent: "" harness override: fullsend composition treats an empty scalar as unset and inherits the base agent, so this does not clear the agent field.
Workarounds and proper fix
Interim workaround
Remove scoped disallowedTools: Bash(...) entries from the code and fix agent frontmatter. This restores Bash access.
Implemented for rhdh-agentic in:
Do not rely on a frontmatter Bash allowlist with fullsend
A definition such as tools: Bash(gh,echo) keeps Bash in the inventory. However, fullsend uses --dangerously-skip-permissions; under that mode, testing showed that an unlisted sed command still executed. This is not suitable for command-level enforcement in fullsend.
Recommended fullsend fix
Use permissions.deny in the generated project .claude/settings.json.
Validated on v2.1.207 with --dangerously-skip-permissions:
echo executed successfully.
sed remained a Bash tool call but was denied specifically.
- Bash stayed present in the init tool inventory.
Fullsend already generates /sandbox/workspace/.claude/settings.json for hooks, but the generated structure currently has no permissions block. Extend the harness schema and settings generator to support scoped deny rules, then remove the broken frontmatter rules.
Related upstream reports
These are related tool-inventory bugs, but they are not the same minimal reproduction:
Related fullsend-ai work
A search of open and closed issues and pull requests in fullsend-ai/fullsend found the following related work:
| Item |
Status |
Relationship |
| fullsend#303 |
Open |
Direct implementation issue for adding permissions.deny to generated .claude/settings.json. This is the primary fullsend-side fix. |
| fullsend#4437 |
Open |
Direct workaround issue for removing broken disallowedTools entries from code/fix agents. The actual agent definitions now live in fullsend-ai/agents. |
| fullsend#558 |
Open |
Related linting proposal for agent frontmatter. Its premise that tools and disallowedTools are inert under --agent is outdated and should be corrected. |
| fullsend#3874 |
Closed |
Earlier production evidence of the same recovery pattern: a code agent without Bash falls back to a generic claude subagent, adding time, tool calls, and cost. |
| fullsend PR #293 |
Merged |
Added ADR-0027 and selected permissions.deny as the intended steering mechanism. The ADR's claim that agent frontmatter is inert under --agent is now stale. |
| fullsend PR #2098 |
Merged |
Fixed adjacent subagent-dispatch and agent-definition problems, but does not fix the top-level scoped-disallowedTools bug. |
| fullsend#2826 |
Open |
Adjacent work on separating permission profiles between inference and deterministic post-run automation. |
No open pull request currently implements permissions.deny, a permissions_deny harness field, or an extension to GenerateClaudeSettings().
Work-item split
- This issue: Claude Code bug, minimal reproduction, and production evidence.
- fullsend#303: generate and install scoped
permissions.deny rules for top-level agent sessions.
- fullsend#4437 / fullsend-ai/agents: remove the broken code/fix frontmatter rules and eliminate per-repository agent forks.
Action items
Summary
When a named Claude Code agent contains a scoped Bash rule in its frontmatter, such as:
Claude Code removes the entire Bash tool from the top-level
--agentsession. The problem is not the--agentflag by itself.The resulting error is:
Confirmed with Claude Code v2.1.156 in the fullsend sandbox and v2.1.207 locally.
Validated root cause
Controlled tests on v2.1.207:
--agentdisallowedTools: Write, EditdisallowedTools: Bash(sed)disallowedTools: Bash(sed *)The same result occurs with
--bare, which disables hooks, plugins, custom settings, and other integrations.Claude's documentation says a scoped rule such as
Bash(rm *)should keep Bash visible and deny only matching commands. Named sessions launched with--agentare documented to use the agent's tool restrictions. The observed behavior therefore appears to be a Claude Code bug in scopeddisallowedToolshandling.Documentation:
Minimal reproduction
Run a named agent with a scoped Bash deny rule:
Actual result:
{"tool_count":23,"has_bash":false}Control case: remove
disallowedToolsfrom the inline agent definition. Bash remains present and the tool count is 24.Fullsend impact
Fullsend launches named sessions with:
The upstream code and fix agents currently contain multiple scoped Bash rules, including
Bash(sed *),Bash(awk *), andBash(git push *). As a result, those top-level agents start without Bash and delegate shell work to subagents.The first observed production example was:
This confirms significant overhead, although the exact incremental cost and slowdown require a controlled comparison with an equivalent fixed run.
How we discovered it: the agent self-recovers through subagents
The failure did not initially look like a hard runtime error because the top-level agent worked around it autonomously. The transcript from the cited run shows the complete recovery path:
codeagent attempts its required Bash setup command and receivesNo such tool available: Bash.subagent_type: codeto perform the implementation. That agent inherits the same broken scopeddisallowedToolsrules, attempts Bash twice, and also fails.subagent_type: claudewithecho,pwd, andgit status. Bash works immediately.claudesubagent.claudesubagent to verify the branch, commit, diff, and output file using six more Bash calls.The resulting session tree was:
codeagentcodeagentclaudeagentclaudeagentclaudeagentThis behavior is important for diagnosis:
claudeagent is used, ruling out the sandbox image and OpenShell policy as the cause.codeagent fails in the same way as the top-levelcodeagent, tying the failure to that agent definition.The evidence is in the
fullsend-codeartifact attached to:Findings ruled out
--agentalone: a named agent without tool restrictions retains Bash.--bare; hooks cannot explain the missing init tool.codeagent: Claude Code has no built-in agent with that name.agent: ""harness override: fullsend composition treats an empty scalar as unset and inherits the base agent, so this does not clear the agent field.Workarounds and proper fix
Interim workaround
Remove scoped
disallowedTools: Bash(...)entries from the code and fix agent frontmatter. This restores Bash access.Implemented for rhdh-agentic in:
Do not rely on a frontmatter Bash allowlist with fullsend
A definition such as
tools: Bash(gh,echo)keeps Bash in the inventory. However, fullsend uses--dangerously-skip-permissions; under that mode, testing showed that an unlistedsedcommand still executed. This is not suitable for command-level enforcement in fullsend.Recommended fullsend fix
Use
permissions.denyin the generated project.claude/settings.json.Validated on v2.1.207 with
--dangerously-skip-permissions:echoexecuted successfully.sedremained a Bash tool call but was denied specifically.Fullsend already generates
/sandbox/workspace/.claude/settings.jsonfor hooks, but the generated structure currently has nopermissionsblock. Extend the harness schema and settings generator to support scoped deny rules, then remove the broken frontmatter rules.Related upstream reports
These are related tool-inventory bugs, but they are not the same minimal reproduction:
tools:array silently drops first and last positions at spawn time anthropics/claude-code#60237 — closed completedRelated fullsend-ai work
A search of open and closed issues and pull requests in
fullsend-ai/fullsendfound the following related work:permissions.denyto generated.claude/settings.json. This is the primary fullsend-side fix.disallowedToolsentries from code/fix agents. The actual agent definitions now live infullsend-ai/agents.toolsanddisallowedToolsare inert under--agentis outdated and should be corrected.codeagent without Bash falls back to a genericclaudesubagent, adding time, tool calls, and cost.permissions.denyas the intended steering mechanism. The ADR's claim that agent frontmatter is inert under--agentis now stale.disallowedToolsbug.No open pull request currently implements
permissions.deny, apermissions_denyharness field, or an extension toGenerateClaudeSettings().Work-item split
permissions.denyrules for top-level agent sessions.Action items
disallowedToolsreproduction.permissions.denysupport in fullsend#303.fullsend-ai/agents.--agentsessions.agent: ""inherits the base value and cannot fix the problem.