redhat-developer / redhat-developer/rhdh-fullsend

Claude Code scoped disallowedTools rules remove Bash from --agent sessions

Open
#23 5 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Dockerfile
Stars
2
Forks
7
Avg merge
1d 13h
Merged PRs (30d)
8

Description

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

  • File a new upstream Claude Code issue with the minimal scoped-disallowedTools reproduction.
  • Implement and track permissions.deny support in fullsend#303.
  • Coordinate agent-definition cleanup through fullsend#4437 and fullsend-ai/agents.
  • Update ADR-0027 from fullsend PR #293: current Claude Code and its documentation apply agent tool restrictions to --agent sessions.
  • Close redhat-developer/rhdh-plugins#3723 because agent: "" inherits the base value and cannot fix the problem.
  • Merge redhat-developer/rhdh-agentic#91 as the interim workaround.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with fullsend#303, then inspect the harness schema and GenerateClaudeSettings() that produce /sandbox/workspace/.claude/settings.json. The related work is split across the upstream report, permissions.deny support, and agent-definition cleanup; completion is tracked by those separate items rather than by this issue alone.

Written by the indexing model from the issue text.

Assessment

Tech stack
json, shell, yaml
Domain
cli, developer-experience, tooling
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.