[Bug] Subagents unable to write files and call MCP tools silently

Status Open
Maintainer reply None cached
Activity 13 comments · opened Dec 13, 2025

Bug Description

Bug Report: Subagents Cannot Write Files or Call MCP Tools (Silent Failures)

Summary

Subagents spawned via the Task tool have lost the ability to write files and call MCP tools. These failures are silent - the subagent believes the operation succeeded, but nothing actually happens. The main conversation agent can perform these operations successfully.

Environment

OS: macOS (Darwin 24.6.0)
Claude Code Version: 2.0.68
Date Issue Started: December 12-13, 2025 (overnight)

Reproduction Steps

Issue 1: Write Tool Silent Failure

Start a conversation with Claude Code

Spawn a background subagent:

Task tool with:

  • subagent_type: "general-purpose"
  • run_in_background: true
  • prompt: "Create a file at ./test-subagent-write.md with the content 'Hello World'"

Wait for subagent to complete

Check if file exists: ls -la ./test-subagent-write.md

Expected: File is created
Actual: File does NOT exist, but subagent reports success

Issue 2: MCP Tool Call Failure

Create a subagent with MCP tools in its tools list:

tools:

  • Read
  • mcp__some-server__some_tool

Spawn the subagent via Task tool

Instruct it to call the MCP tool

Expected: MCP tool is called
Actual: Subagent fails to call MCP tool (may skip it or error)

Workaround

For Write tool: Have subagents return content to main agent, which then writes files.
For MCP tools: No known workaround - must use main agent directly.

Additional Notes

This appears to be a regression - both features worked before December 12-13, 2025
Failures are silent making debugging extremely difficult
Affects both run_in_background: true and potentially foreground subagents
May be related to sandboxing/permission changes in a recent Claude Code update

Environment Info

  • Platform: darwin
  • Terminal: ghostty
  • Version: 2.0.68
  • Feedback ID: fe203b0c-8eee-4427-9ec0-4a00cbf9b2a2

View original on GitHub ↗

13 Comments

dsull111 · 8 months ago

Seeing the same issue. Synchronous agents work fine. Asynchronous (background) agents cannot call MCP tools, even when they are explicitly defined as available in the agents definition, and available in the parent. I have had to resort to using synchronous agents only.

johnnyk1090 · 8 months ago

A. Subagents Write bug:

  1. Keep these tools for the subagent: Bash, Write (always both of them).
  2. Instruct the subagent (with robust language) to use Bash instead of Write to generate (NOT WRITE - GENERATE - WORD SENSITIVE) a JSON report for the orchestrator (the main Claude Code function).
  3. Tell the orchestrator (the main Claude Code function) to Write the report it received from the subagent to a folder (that way it will persist on disk).

B. MCP tool usage via subagents:

  1. Keep these tools for the subagent: Bash, Write (always both of them) AND MCPSearch AND mcp__context7__resolve-library-id (example of the mcp function of context7)
  2. Create a prompt for the subagent:

YOU HAVE DIRECT ACCESS TO MCP TOOLS
Direct MCP Tool Calls
mcp__context7__resolve-library-id(libraryName="pandas")

IMPORTANT: PREREQUISITE: YOUR claude.json will need to have the configuration for the MCP SERVER (API KEY, TYPE, URL, etc.)

zot · 7 months ago

I had this problem with one of my agents and it was because Claude put complex code-fenced, unescaped, markdown into the agent file. I think the subagent barfed on it when it started up. It couldn't read or write but it thought it could and it hallucinated the contents of files based on their names.

I fixed it by using nested markdown.

Before:

````markdown

# heading
example stuff
```sh
echo hello
````

To fix it, I changed from using code-fenced markdown to nested headings like:

````markdown
### Here's an example (headings increased 3 levels)
#### heading
example stuff

example code in a code-fenced block

````
You can also use 4 ticks for your outer code-fence but I like to see the syntax highlight properly.
zot · 7 months ago

And/or bad frontmatter -- had to fix that as well

socketopp · 7 months ago

Have you guys revised this after the v.2.1.0?

johnnyk1090 · 7 months ago
Have you guys revised this after the v.2.1.0?

Not yet. Just using the Workarounds (A + B)..

bawoodruff · 7 months ago

How do we tag someone at Anthropic for visibility - this really breaks background subagents. The bash workaround doesn't cover many use cases.

illia-fliplet · 6 months ago

The Workaround That Works for Me

I've been experiencing the same issue with subagents not being able to write files or call MCP tools. After extensive testing, I found a workaround that completely solves both problems.

Environment:

  • OS: Windows 11
  • Claude Code: v2.1.39

---

Root Cause I Discovered

When I had the tools: field in my agent.md YAML frontmatter:

  • ❌ Agent switched to explicit allowlist mode
  • ❌ Only listed tools existed in the agent's runtime
  • MCP tools failed (not available even when explicitly listed!)
  • Write tool failed silently
  • ❌ CLI /agents interface couldn't expand the tool list

Through systematic testing, I discovered:

  1. Agents WITH tools: field → Explicit mode → MCP tools unavailable ❌
  2. Agents WITHOUT tools: field → Implicit mode → All tools available ✅
  3. Implicit mode agents successfully call MCP tools AND write files! ✅

---

My Workaround Solution

Why This Works (The Technical Explanation)

Claude Code has two tool access modes:

  1. Explicit Mode (when tools: field exists in agent.md):
  • Claude Code creates a hard allowlist from the tools: field
  • ONLY those exact tools are registered in the agent's runtime
  • CRITICAL: Even if you explicitly list mcp__jira__jira_get in the tools: field, Claude Code doesn't see/load those MCP tools
  • The subagent subprocess doesn't receive MCP server connections
  • The tools: field acts as a runtime restriction that locks the tool registry to only non-MCP tools
  • Result: MCP tools are invisible to the agent, even when listed
  1. Implicit Mode (when tools: field is absent):
  • Claude Code defaults to "all available tools"
  • Agent inherits ALL tools that exist in the session/runtime
  • MCP server connections ARE available because there's no restriction layer
  • Tools can be loaded dynamically (via ToolSearch or direct call)

The key insight: The tools: field isn't just documentation - it's a runtime enforcement mechanism that creates a sandboxed tool environment. But this sandboxing breaks MCP tool access for subagents (likely a bug in how MCP connections are passed to subprocesses).

By removing the tools: field, we opt out of the sandboxing and get full tool access.

Three-Part Fix

  1. Remove tools: field from agent.md → Switches to implicit mode → MCP tools accessible
  2. Keep allowed-tools: in SKILL.md frontmatter → Documents which tools agent should use (for permissions bypass)
  3. Add Tool Gate procedure in SKILL.md → Enforces tool restrictions via agent instructions (procedural, not runtime)

Architecture

┌─────────────────────────────────────────────────────────────┐
│ agent.md (NO tools: field)                                  │
├─────────────────────────────────────────────────────────────┤
│ ---                                                         │
│ name: integrator                                            │
│ model: sonnet                                               │
│ skills:                                                     │
│   - framework-rules                                         │
│   - integrator                                              │
│ ---                                                         │
│                                                             │
│ Result: Agent runs in IMPLICIT mode                        │
│         → All tools available (including MCP)              │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ SKILL.md (allowed-tools: + Tool Gate)                      │
├─────────────────────────────────────────────────────────────┤
│ ---                                                         │
│ allowed-tools:  # Single source of truth                    │
│   - Read                                                    │
│   - Write                                                   │
│   - mcp__jira__jira_get                                     │
│   - mcp__jira__jira_post                                    │
│   ...                                                       │
│ ---                                                         │
│                                                             │
│ ## Tool Gate (Non-Negotiable)                              │
│                                                             │
│ Before ANY tool call:                                       │
│ 1. Check if tool is in allowed-tools: above                │
│ 2. If NOT allowed:                                          │
│    - DO NOT call the tool                                   │
│    - Output: TOOL_REQUEST: [tool] - [reason]              │
│    - Return STATUS: BLOCKED                                 │
│ 3. If allowed → proceed                                     │
│                                                             │
│ Result: Procedural enforcement via agent instructions       │
└─────────────────────────────────────────────────────────────┘

---

Implementation Steps

Step 1: Update agent.md

Before:

---
name: integrator
tools: Read, Glob, Grep, Write, mcp__jira__jira_get, ...
model: sonnet
---

After:

---
name: integrator
model: sonnet
skills:
  - framework-rules
  - integrator
---

Step 2: Update SKILL.md Frontmatter

Ensure allowed-tools: is complete:

---
name: integrator
allowed-tools:
  - Read
  - Glob
  - Grep
  - Write
  - WebFetch
  - ToolSearch
  - Bash
  - mcp__jira__jira_get
  - mcp__jira__jira_post
  - mcp__jira__jira_put
  - mcp__github__pull_request_read
  - mcp__github__list_pull_requests
  - mcp__fliplet-knowledge__search_fliplet_docs
---

Step 3: Add Tool Gate to SKILL.md Body

Add after frontmatter, before main procedure:

## Tool Gate (Non-Negotiable)

**Before ANY tool call**:
1. Identify the exact tool you intend to call
2. Check if it's in the `allowed-tools:` list in the frontmatter above
3. If NOT in `allowed-tools:`:
   - ❌ **DO NOT call the tool**
   - ✅ Output: `TOOL_REQUEST: [tool_name] - [reason needed]`
   - ✅ Return `STATUS: BLOCKED`
4. If in `allowed-tools:` → proceed with tool call

**MCP Tool Loading**: Use `ToolSearch("select:mcp__[tool-name]")` to load MCP tools BEFORE checking the gate.

Step 4: Restart Claude Code

Changes to agent.md require Claude Code restart to take effect.

---

My Test Results

Here's what I tested to confirm this works:

| Agent | Test | Tool | In allowed-tools? | Result |
|-------|------|------|------------------|--------|
| Integrator | 1 | mcp__jira__jira_get | ✅ Yes | ✅ Called successfully |
| Integrator | 2 | Edit | ❌ No | ✅ BLOCKED with TOOL_REQUEST |
| Strategist | 1 | mcp__fliplet-knowledge__search_fliplet_docs | ✅ Yes | ✅ Called successfully |
| Analyst | 1 | mcp__jira__jira_get | ✅ Yes | ✅ Called successfully |
| Analyst | 2 | Bash | ❌ No | ✅ BLOCKED with TOOL_REQUEST |
| Analyst | 3 | Write (file creation) | ✅ Yes | ✅ File created successfully |

Key Findings

  1. MCP tools work - All MCP tool calls succeeded (Jira, GitHub, Fliplet Knowledge)
  2. Write tool works - File creation succeeded (no silent failure)
  3. Tool Gate enforced - Non-allowed tools properly blocked
  4. Portable - Solution works across multiple agent types
  5. Auditable - TOOL_REQUEST output provides visibility

---

How It Works

Implicit Mode vs Explicit Mode

| Mode | Trigger | Tool Availability | MCP Works? | Write Works? |
|------|---------|------------------|------------|--------------|
| Explicit | tools: field present | Only listed tools | ❌ No | ❌ No |
| Implicit | tools: field absent | All available tools | ✅ Yes | ✅ Yes |

Tool Gate Mechanism

The Tool Gate is procedural enforcement, not runtime restriction:

  1. Runtime level (implicit mode): All tools available
  2. Procedural level (Tool Gate): Agent checks instructions before calling
  3. Permission level (allowed-tools:): Bypasses permission prompts

Flow:

Agent needs tool
    ↓
Checks allowed-tools: in SKILL.md
    ↓
    ├─ In list? → Call tool ✅
    └─ Not in list? → Output TOOL_REQUEST, return BLOCKED ❌

---

Benefits

  1. Solves GitHub Issue #13890
  • ✅ MCP tools accessible to subagents
  • ✅ Write tool functional
  1. Maintains Control
  • ✅ Tool restrictions enforced via procedure
  • ✅ Single source of truth (allowed-tools: in SKILL.md)
  • ✅ Auditable (TOOL_REQUEST output)
  1. Portable
  • ✅ Works when repo is cloned (file-based configuration)
  • ✅ No local-only state
  • ✅ Consistent behavior across environments
  1. Future-Proof
  • ✅ When Claude Code fixes the issue, can revert easily
  • ✅ Tool Gate can remain as additional safety

---

Limitations

  1. Procedural enforcement - Relies on agent following instructions (not runtime restriction)
  2. Requires restart - Changes to agent.md need Claude Code restart
  3. Workaround - Not the official fix, may break in future versions

---

Rollout Checklist

For each agent:

  • [ ] Remove tools: field from .claude/agents/<agent>.md
  • [ ] Verify allowed-tools: in .claude/skills/<agent>/SKILL.md is complete
  • [ ] Add Tool Gate section to SKILL.md body
  • [ ] Restart Claude Code
  • [ ] Test with allowed tool (should work)
  • [ ] Test with non-allowed tool (should block)

---

Example: Complete Integrator Setup

.claude/agents/integrator.md

---
name: integrator
description: "External tool integration for QA workflow..."
model: sonnet
color: red
---

<role>
You are the Integrator agent for Fliplet S.Q.U.A.D...
</role>

.claude/skills/integrator/SKILL.md

---
name: integrator
allowed-tools:
  - Read
  - Glob
  - Grep
  - Write
  - WebFetch
  - ToolSearch
  - Bash
  - mcp__jira__jira_get
  - mcp__jira__jira_post
  - mcp__jira__jira_put
  - mcp__github__pull_request_read
  - mcp__github__pull_request_review_write
  - mcp__github__search_code
  - mcp__github__list_pull_requests
  - mcp__github__add_issue_comment
  - mcp__fliplet-knowledge__search_fliplet_docs
  - mcp__fliplet-knowledge__search_fliplet_code
---

# Integrator Procedure — External Integration & Knowledge Persistence

## Tool Gate (Non-Negotiable)

**Before ANY tool call**:
1. Identify the exact tool you intend to call
2. Check if it's in the `allowed-tools:` list in the frontmatter above
3. If NOT in `allowed-tools:`:
   - ❌ **DO NOT call the tool**
   - ✅ Output: `TOOL_REQUEST: [tool_name] - [reason needed]`
   - ✅ Return `STATUS: BLOCKED`
4. If in `allowed-tools:` → proceed with tool call

**MCP Tool Loading**: Use `ToolSearch("select:mcp__[tool-name]")` to load MCP tools BEFORE checking the gate.

## Step 1: Load Inputs
...

---

TL;DR

The Fix:

  1. Remove tools: field from .claude/agents/<agent>.md files
  2. Keep allowed-tools: in .claude/skills/<agent>/SKILL.md frontmatter
  3. Add a "Tool Gate" procedure in SKILL.md to enforce tool restrictions

Why it works:

  • No tools: field = implicit mode = all tools available (including MCP!)
  • Tool Gate = procedural enforcement via agent instructions
  • Result: MCP tools work ✅, Write works ✅, restrictions maintained ✅

Tested successfully with:

  • ✅ Jira MCP tools
  • ✅ GitHub MCP tools
  • ✅ Fliplet Knowledge MCP tools
  • ✅ Write tool (file creation)
  • ✅ Tool restriction enforcement

Hope this helps anyone else hitting this issue! Happy to answer questions.

---

When Claude Code Fixes the Issue

Once the official fix is released:

  1. Option A: Keep the workaround
  • Tool Gate provides additional safety
  • No changes needed
  1. Option B: Revert to explicit mode
  • Add tools: back to agent.md (copying from SKILL.md allowed-tools:)
  • Remove Tool Gate from SKILL.md
  • Test to ensure MCP tools still work

The workaround is non-destructive and can coexist with future fixes.

chuck-renaissance · 6 months ago

Additional context: orchestrator perspective in multi-agent workflows

I'm hitting this same issue from the orchestrator side, using a multi-agent workflow where a parent Claude instance spawns Developer and Reviewer subagents via the Task tool.

What I observe

  1. Orchestrator spawns a subagent via Task tool (subagent_type: "general-purpose")
  2. Subagent starts working, hits a Write or Bash call
  3. Subagent blocks on a permission prompt that cannot surface through the nested Task tool
  4. Task tool returns "error - tool result missing due to internal error" to the orchestrator
  5. Orchestrator interprets this as a failed spawn and retries → duplicate agents

Evidence of duplicates

In a single session orchestrating 4 sequential steps with Developer + Reviewer agents, the system-reminder task notifications showed:

| Task ID | Description | Status |
|---------|-------------|--------|
| ac14376 | Developer implements step 01 | completed |
| abce028 | Developer implements step 02 | completed |
| ad3604b | Developer implements step 02 | completed |
| af0ac3c | Reviewer reviews step 02 | completed |
| a81f48c | Reviewer reviews step 02 | completed |
| a17ffd5 | Developer commits step 02 | completed |

Two Developer agents and two Reviewer agents for the same step. Both pairs ran concurrently and wrote to the same files, producing duplicate content that required a fixup commit.

Escape key interaction

Hitting Escape during the apparent hang seems to free blocked agents — suggesting the subagent is stuck on a permission prompt that has no UI path to the user. Escape likely cancels or denies the prompt, allowing the agent to continue (with the tool call denied).

Impact in orchestrated workflows

In a single-agent workflow, this is annoying but manageable. In a multi-agent orchestration managing 15-20 agent spawns across a flight:

  • Duplicate agents write to the same files concurrently → merge conflicts, duplicate content
  • Git workflow corruption → fixup commits needed to clean up duplicate entries
  • Context waste → duplicate completion notifications consume parent context
  • Human intervention required → operator had to intervene to stop the cascade

Workaround

Same as described in this issue — have the orchestrator write files directly instead of subagents. But this defeats the purpose of the separation between orchestrator and executor roles.

Environment

Related

  • #23983 — PermissionRequest hooks not triggered for subagent requests
  • #18950 — Subagents don't inherit user-level permissions
  • #23909 — Main thread ends while subagents run in background
  • #25818 — Orchestrator has no diagnostic context when subagent fails
shafty023 · 6 months ago

It would be REALLY nice if this bug got fixed by Anthropic. This is a huge issue. I should be able to designate that an agent can use WRITE or BASH and regardless that it's a background agent, it should be able to use those tools.

arjun-memfold · 5 months ago

I am facing the same issue now, subagents that run in background is not able to use the Write tool. When I checked the logs this is what I see "Permission to use Write has been denied....". However the same subagent works well when run_in_background is false.

Any leads will be really helpful for me. Thank you!!!

arjun-memfold · 5 months ago

I am facing the same issue now, subagents that run in background is not able to use the Write tool. When I checked the logs this is what I see "Permission to use Write has been denied....". However the same subagent works well when run_in_background is false.

Any leads will be really helpful for me. Thank you!!!

tomridl · 5 months ago

Similar issue (Claude Code CLI v2.1.81 on macOS):

⏺ Now build the wizard component. I'll use the blazor-frontend agent for this since it's a substantial Blazor UI component.

⏺ blazor-frontend(Build dealer creation wizard)
  ⎿  Done (0 tool uses · 38.5k tokens · 4m 12s)
  (ctrl+o to expand)

⏺ Let me read what the agent produced.

  Read 1 file (ctrl+o to expand)

⏺ The agent didn't write the file. Let me create it directly.