Skills/subagents do not inherit user-level permissions from settings.json

Open 💬 29 comments Opened Jan 18, 2026 by thomast73

User-level permissions configured in ~/.claude/settings.json under permissions.allow are not being inherited by skills/subagents. All bash commands require permission prompts when executed within a skill, even though those same commands are auto-approved when used in the main conversation.

Steps to Reproduce

  1. Configure user-level permissions in ~/.claude/settings.json:
{
  "permissions": {
    "allow": [
      "Bash(test)",
      "Bash(git rebase)",
      "Bash(mvn verify)",
      "Bash(grep)",
      "Bash(head)",
      "Bash(lsof)",
      "Bash(sleep)",
      "Bash(tail)",
      "Bash(echo)"
    ]
  }
}
  1. Restart Claude Code to load permissions
  2. Verify permissions work in main conversation (e.g., ls command runs without prompt)
  3. Run a skill that uses these commands
  4. Observe: All bash commands prompt for permission despite being in the allow list

Expected Behavior

Skills should inherit user-level permissions from settings.json. Commands in the permissions.allow list should be auto-approved without prompting.

Actual Behavior

Skills prompt for permission on every bash command. Example commands that prompted:

  • test -f pom.xml
  • grep with head pipeline
  • git rebase team/master
  • mvn spring-boot:run
  • lsof commands
  • sleep and tail commands

Environment

  • Claude Code version: 2.1.12
  • OS: macOS 26.2 (25C56)
  • Settings file: ~/.claude/settings.json

Workaround

Select option 2 to create workspace-level permissions, but this defeats the purpose of user-level permissions.

Impact

Significantly degrades user experience when using skills, requiring manual approval of dozens of commands.

View original on GitHub ↗

29 Comments

github-actions[bot] · 5 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/10906
  2. https://github.com/anthropics/claude-code/issues/11934
  3. https://github.com/anthropics/claude-code/issues/18172

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

thomast73 · 5 months ago

Additional Evidence: Workspace Permissions Not Matching in Skills

I've discovered that workspace-level permissions in .claude/settings.local.json are not matching commands even when they should, specifically in skills/subagents.

Commands That Prompted (Without Option 2)

These commands prompted with only "Yes" or "No" options - no workspace-level "don't ask again" option:

  1. Maven command with redirection and background execution:

``bash
mvn spring-boot:run -pl app > /tmp/tls-server.log 2>&1 & echo $\!
`
**Should match:**
Bash(mvn spring-boot:run:*)`

  1. For loop with variable assignment:

``bash
SPRING_JAVA_PID=""; for i in {1..60}; do SPRING_JAVA_PID=$(pgrep -P 36034 -f java 2>/dev/null); if [ $? -eq 0 ] && [ -n "$SPRING_JAVA_PID" ]; then echo "Server Java process found: $SPRING_JAVA_PID"; exit 0; fi; sleep 1; done; echo "Timeout: No Java process found"
`
**Should match:**
Bash(for i in {1..60}:*)`

Command That Offered Option 2

This command correctly offered workspace-level approval:

kill 36074 2>/dev/null; sleep 2; lsof -i :5000 -t 2>/dev/null && echo "Server still running" || echo "Server stopped"

No pattern in workspace permissions - so it correctly offered to add one.

Current Workspace Permissions

.claude/settings.local.json contains:

{
  "permissions": {
    "allow": [
      "Bash(mvn spring-boot:run:*)",
      "Bash(pgrep:*)",
      "Bash(for i in {1..60}:*)",
      "Bash(lsof -i :5000:*)",
      ...
    ]
  }
}

Analysis

The patterns should match but don't, suggesting:

  1. Redirects (>, 2>&1), pipes, and background operators (&) may break pattern matching
  2. Variable assignments at the start of commands (SPRING_JAVA_PID="") may prevent matching
  3. The :* wildcard syntax in workspace permissions behaves differently than expected

This appears distinct from the duplicate issues mentioned (#10906, #11934, #18172), as this is specifically about workspace permission pattern matching in skills/subagents, not about permissions being ignored entirely.

undeniablyrob · 4 months ago

I was able to use the :* legacy syntax as a workaround. Let's hope the legacy syntax sticks around until this issue is fixed 😅.
e.g.,

Bash(foo *) # doesn't work; not inherited by subagents
Bash(foo:*) # works
dmoorevtedu · 4 months ago

Adding Windows-specific evidence. On Windows this issue compounds with a path format mismatch that makes allow rules unreachable for subagents even when correctly configured.

Setup: defaultMode: "acceptEdits", --add-dir "C:\Users\david", rules like Read(C:/Users/david/**) in ~/.claude/settings.json.

What happens: Main session file ops auto-allow. Subagent file ops prompt every time — two reasons stacking:

  1. acceptEdits scope (including --add-dir) not inherited (the core bug here)
  2. Claude generates backslash paths (C:\Users\david\...) which don't match forward-slash rules (C:/Users/david/**) — no normalization before rule matching

Forensic data from 9 days of PreToolUse + PermissionRequest hook logging (303 permission events, 222 file-tool ops):

  • Main session reads to in-tree paths: ~0% prompt rate
  • Subagent reads: ~100% prompt rate on first call per session
  • 59% of prompted file-tool calls used backslash paths
  • "Always allow" suggestions use a third format — MSYS (//c/Users/david/...) — that matches neither the settings.json rules nor the paths Claude generates

Even with duplicate rules in both C:/ and /c/ format (36 rules total) and the broadest possible --add-dir, subagents still prompt because they don't inherit acceptEdits scope and the path normalization gap blocks rule matching.

Related Windows path issues: #12796, #10337, #22576

tzulingk · 4 months ago

Reproducing on macOS — global settings.json allow rules ignored by subagents

Platform: macOS (Darwin 25.3.0)
Claude Code version: claude-sonnet-4-6 (desktop app)

Setup

~/.claude/settings.json has all common tools in the top-level allow list:

{
  "permissions": {
    "allow": [
      "Bash",
      "WebFetch",
      "WebSearch",
      "Read",
      "Write",
      "Edit",
      "Glob",
      "Grep",
      "Agent",
      "TodoWrite"
    ],
    "ask": [
      "Bash(rm:*)",
      "Bash(sudo:*)"
    ]
  }
}

There is no project-level .claude/settings.json overriding the global config.

Observed behavior

Top-level session: Grep, WebFetch, WebSearch, Bash all auto-allow as expected.

When Claude spawns a subagent via the Agent tool, those same tools prompt for permission on every call — as if settings.json is not being read at all by the subprocess.

Workaround

Perform searches/research directly in the top-level conversation (where runtime grants are in effect) rather than delegating to subagents.

nekovin · 4 months ago

I've also found this to be inconsistent. Sometimes it's fine and the subagent processes requests fine but other times it requires explicit approval

pmolodo · 4 months ago

Also running into this issue constantly. Most annoying...

dfdeagle47 · 4 months ago

@tzulingk @nekovin @pmolodo

Out of curiosity, does it still happen if you run the agent in the background (CTLR+B by default)?

(though I don't know if that's possible in Claude Code via Claude Desktop)

I seemed to get better result in background mode on a similar issue, but I'm not sure if it's a reliable workaround for now, or if I'm missing something.

piotrkolodziej · 3 months ago

Happens all the time. Super annoying.

yurukusa · 3 months ago

Hooks in ~/.claude/settings.json (user-level) do inherit to subagents and skills — unlike permission rules which may not.

If you need safety guarantees across all agents:

a hook validation tool

This writes hooks to ~/.claude/hooks/ and registers them in user-level ~/.claude/settings.json. Since hooks fire before every tool call regardless of which agent instance is running, they effectively enforce permissions that the permission system fails to inherit.

Verified behavior: hooks in user settings fire for main agent, subagents (Task/Agent tool), and skills. The hook process inherits the parent's settings.json path.

zendevio · 3 months ago

I traced the permission flow through the minified CLI source (v2.1.83) and found the exact bug. Here's a
writeup.

The Bug

Inside MN(), the function that spawns subagents, there's a getAppState wrapper that rebuilds
alwaysAllowRules when the subagent has allowedTools defined:

// In MN()'s getAppState wrapper (P = allowedTools parameter)

  if (P !== void 0)
    w6 = {
      ...w6,
      alwaysAllowRules: {
        cliArg: r.toolPermissionContext.alwaysAllowRules.cliArg,
        session: [...P]
      }
    };

When allowedTools is passed, the code creates a new alwaysAllowRules object with only two categories:
cliArg (preserved from parent) and session (set to the allowedTools array). Every other category is
silently dropped:

  • userSettings (rules from ~/.claude/settings.json)
  • projectSettings (rules from .claude/settings.json)
  • localSettings (rules from .claude/settings.local.json)
  • flagSettings (rules from CLI flags)
  • policySettings (enterprise managed policy rules; this one is a security concern since policy rules should

never be droppable)

  • command (rules from parent skill/command configs)

The full set of categories is defined as:

  kG = ["userSettings", "projectSettings", "localSettings", "flagSettings", "policySettings"]
  N6A = [...kG, "cliArg", "command", "session"]

The flatten helpers (jE6, _76, HE6) iterate over all N6A categories using || [] for missing keys, so
dropped categories are silently ignored. No error, just missing rules.

Who Triggers It

Only one call site passes allowedTools to MN: the inProcessRunner (the teammate/in-process agent runner).
Regular Agent/Task tool subagents construct the a object without allowedTools, so P is undefined and the
buggy block doesn't execute for those.

This means the bug specifically affects in-process teammates and any path that provides allowedTools to MN.
Regular Agent(subagent_type="general-purpose") calls should inherit permissions correctly, though users
may still see prompts for other reasons (mode differences, shouldAvoidPermissionPrompts auto-denying in
async agents, etc.).

The Fix Already Exists in the Codebase

The Skill tool's contextModifier does the exact same operation correctly:

// Skill tool contextModifier (CORRECT)

  alwaysAllowRules: {
    ...x.toolPermissionContext.alwaysAllowRules,   // preserves ALL existing categories
    command: [...new Set([...x.toolPermissionContext.alwaysAllowRules.command || [], ...M])]
  }

It spreads the existing rules and only adds to the command category.

Suggested Fix

// Before (buggy):

  if (P !== void 0)
    w6 = {
      ...w6,
      alwaysAllowRules: {
        cliArg: r.toolPermissionContext.alwaysAllowRules.cliArg,
        session: [...P]
      }
    };

// After (fixed):

  if (P !== void 0)
    w6 = {
      ...w6,
      alwaysAllowRules: {
        ...r.toolPermissionContext.alwaysAllowRules,
        session: [...new Set([
          ...r.toolPermissionContext.alwaysAllowRules.session || [],
          ...P
        ])]
      }
    };

One-line change: spread all parent categories instead of cherry-picking cliArg. Deny and ask rules are
already preserved via the ...w6 spread above the block, so there's no security regression. On the contrary,
this restores policySettings to the allow rules, which should never have been droppable.

Why Reports Are Inconsistent

Multiple distinct problems are being reported under this issue:

  1. Teammates with allowedTools: this bug, settings rules dropped
  2. Async subagents (background): shouldAvoidPermissionPrompts is set to true, causing unmatched commands to

be silently denied (not prompted). Rules are inherited correctly, but anything not in the allow list is
auto-rejected instead of asking the user.

  1. Mode differences: a subagent may inherit a different permission mode than expected (e.g., acceptEdits vs

auto), changing which commands get auto-approved by the classifier vs prompted.

  1. The : vs glob discrepancy: may be a separate rule-matching issue, not an inheritance issue.

This would explain why some users see it "all the time" (using teammates/skills with allowedTools) while
others see it inconsistently (hitting different code paths).

jake-foxy · 3 months ago

I run a skill that spawns 6+ subagents in parallel via the Agent tool. The skill defines allowed-tools in its YAML front matter, which means it always hits the buggy allowedTools branch @zendevio identified.

Key data point: 6 identical subagents, same config, same prompt, same Write target — 4 succeed, 2 get permission-denied. Same session, same ~/.claude/settings.json. Not a config problem.

@zendevio's root cause explains every behavior I've seen. Skills with allowed-tools always trigger the broken path — that's why this is consistent for skill authors and intermittent for others.

Consolidating related issues that are all downstream of this:

  • #37730
  • #37442
  • #26479
  • #23983
  • #22665 (closed as dup)
  • #10906
  • #33901

Would love to see the one-line spread fix prioritized. This has been open since January and makes subagent file I/O essentially unusable for skills.

yurukusa · 3 months ago

Hooks in ~/.claude/settings.json do propagate to subagents and skills — unlike permission patterns. This makes hooks the workaround for this exact problem.

INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
[ -z "$COMMAND" ] && exit 0
CLEAN=$(echo "$COMMAND" | sed 's/#.*$//' | tr '\n' ' ')
PARTS=$(echo "$CLEAN" | sed 's/\s*&&\s*/\n/g; s/\s*||\s*/\n/g; s/\s*;\s*/\n/g; s/\s*|\s*/\n/g')
ALL_SAFE=true
while IFS= read -r part; do
    part=$(echo "$part" | sed 's/^\s*//;s/\s*$//')
    [ -z "$part" ] && continue
    BASE=$(echo "$part" | awk '{print $1}')
    case "$BASE" in
        test) ;;                          # Bash(test)
        git) ;;                           # Bash(git rebase) etc
        mvn) ;;                           # Bash(mvn verify)
        grep|head|tail|echo|sleep) ;;     # Bash(grep) etc
        lsof) ;;                          # Bash(lsof)
        cat|ls|find|which|stat|wc|du) ;;
        cd|pwd|pushd|popd) ;;
        sort|uniq|cut|tr|awk|sed|jq) ;;
        *) ALL_SAFE=false; break ;;
    esac
done <<< "$PARTS"
if [ "$ALL_SAFE" = true ]; then
    jq -n '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow",permissionDecisionReason:"User-level permission (hook)"}}'
fi
exit 0

Hooks defined in ~/.claude/settings.json are loaded at the process level for every Claude Code invocation — including subagent spawns. The permissions.allow patterns have a separate resolution path that doesn't always inherit correctly for skill/subagent contexts.
Put this in ~/.claude/settings.json:

{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/user-permissions.sh" }]
    }]
  }
}

Your mvn spring-boot:run, git rebase, grep | head pipelines will all auto-approve in skills without the workspace-level workaround.

coenttb · 3 months ago

+1 — specific use case where this bites hard.

Scenario: large multi-package Swift refactor (~40 file writes across three packages) delegated to a Task subagent for context-budget reasons (the dispatching agent had accumulated significant prior-phase context that didn't fit cleanly into the implementation work).

Friction: parent session had Bash(*) allowed at the user level; Write/Edit were intentionally not allow-listed (interactive approval per invocation, fine for the parent). The Task subagent didn't inherit even the parent's path-scoped Edit(/path/**) allows from .claude/settings.local.json. Faced with 40+ approval prompts mid-task, the subagent fell back to cat <<EOF > file.swift heredocs via Bash — the only pre-approved write path.

Why the heredoc fallback isn't scalable: breaks on content containing EOF tokens or escape-sensitive characters; files >200 LOC are noisy in history; loses Edit's targeted-diff review; bypasses Write's content-as-argument safety check.

Practical workaround we adopted: don't dispatch subagents for file writes; do all writes in the main session. Works but forfeits the context-isolation benefit that motivated subagent dispatch — for large refactors, that's a real cost.

Path-scoped allow inheritance (subagents see parent's .claude/settings.local.json allows for paths the parent has authorized) would resolve this cleanly without changing the security boundary.

sonia1492 · 2 months ago

Adding a Max plan user data point as of 2026-04-22 — this is still blocking real workflows.

My setup: Running an orchestration workspace with 11+ specialist subagents (each defined in .claude/agents/*.md with
tools: that explicitly lists Bash). Parent session has ~/.claude/settings.json with permissions.allow including
Bash(*). Subagents launched via the Agent tool with run_in_background: true still return permission-denied errors when
they try to run Bash — even though the parent would run the same command without a prompt.

Observed behavior is the same as described in this issue and in the now-closed #25526 and #27661, which suggests the
subagent-specific variant of the inheritance bug is still live and should be tracked here.

Practical impact: every subagent dispatch that hits a Bash tool path becomes a wasted round-trip — the orchestrator
has to pick up the work the specialist was supposed to execute. That's the opposite of what multi-agent orchestration
is supposed to enable, and it makes the per-agent permission-block workaround (adding permissions.allow to every agent
definition file) the only option, which is fragile — every new agent needs to remember the block.

What would help:

  1. Confirmation that per-agent permissions.allow blocks are the currently-recommended workaround (or a pointer to a

better one).

  1. A project-level propagateToSubagents: true style option that force-inherits root-session permissions without

per-agent duplication.

  1. An ETA on first-class fix.

Happy to provide a reproduction workspace if that's useful.

— Sonia Martinez, Max plan subscriber, running Claude Code CLI on macOS 15.4

tmol · 2 months ago

I am developing an autonomous agent group which needs limited amount of tools (running things like git, gh, semgrep etc in bash). The workaround was to implement custom mcp servers for these bash calls.

OsakaLOOP · 2 months ago

Really blocking my workflow for subagents generating specifically structured file as output.

android6 · 2 months ago

Reproduces on Windows (native), not just macOS — same root cause.

Setup: project .claude/settings.json has Write(<absolute-windows-path>\\**) in permissions.allow. Both the parent and a Task-tool subagent (custom agent, tools: Write in frontmatter) call Write from the same cwd with identical permission_mode: "default".

Instrumented with a PreToolUse hook on Write logging the full tool_input JSON.

Parent Write call → file_path matches the glob byte-for-byte → OK.
Subagent Write call → file_path matches the same glob byte-for-byte → "Permission to use Write has been denied."
Only structural difference in the hook input: subagent invocation has agent_id and agent_type populated, parent's does not. The rule is correctly configured and matched by the parent's resolver — the subagent's resolver doesn't see permissions.allow. Confirms the title of this issue on Windows.

android6 · 2 months ago

Workaround that worked on 2.1.126:
Project-level .claude/settings.json had "Write(md/code-reviews/**)" — Task-tool sub-agent's Write was being denied (the bug). Adding the same rule to .claude/settings.local.json (local-level, normally gitignored) makes the sub-agent inherit it — Write succeeds deterministically.

xxnickles · 2 months ago

Today I started experiencing this problem on steroids. Custom sub-agents ar not only is not using the user-level permission but are directly getting reject to do task they should be allowed to perform (name write markdown files) Tried @android6 workaround without effect

Setup

  • CLI v2.1.132 / Fedora 44 & windows (wls ubuntu 24.04)
  • Project-level .claude/settings.json allows Write(.claude/**/*.md) (and Write(**/*.md), Write(**/.claude/**/*.md)).
  • Main agent: writes to .claude/journals/... and .claude/_perm_probe.md succeed without prompts. Rule is active.
  • Sub-agent (custom agent defined in .claude/agents/) invoked via the Agent tool: every Write to the same paths returns the literal string Permission to use Write has been denied. from the tool, with no user prompt surfaced.

Tried

  1. Sub-agent frontmatter permissionMode: acceptEdits — nope, denied.
  2. Sub-agent frontmatter permissionMode: bypassPermissions — nope, denied
  3. Mirroring the same Write(...) rules into .claude/settings.local.json —nope, denied event in a fresh session.
  4. Tested with both haiku and sonnet model overrides on the sub-agent — same behavior.
bennet-ikona · 2 months ago

+1 — and worth flagging the symmetric failure on the deny side, which is where this stops being a UX issue and starts being a security issue.

Same root cause: user-level settings.json permissions don't propagate to subagents. But for permissions.deny rules, the failure mode is silent — there's no prompt, the agent just reads files the parent config explicitly forbade.

Concrete example: a deny rule for Read(./.env) and Read(./.env.*) in ~/.claude/settings.json correctly blocks the main session from reading .env. Subagents read it anyway. Users who have correctly hardened their config believe .env is protected and are silently wrong.

The allow-side bug is a papercut. The deny-side bug is a footgun for anyone using Claude Code in repos with secrets.
Fix should cover both: subagents inherit the full parent permission hierarchy (allow + deny) by default.

xxnickles · 2 months ago

One more pointer: I tried removing the settings.json file and the confirmation prompts came back. I moved my permissions list to settings.local.json and removed the settings.json file. Subagents seems to start requesting for access still, but I rather prefer this behavior than just failing. Clearly there are some issues with the permissions

kreare · 2 months ago

This should be considered a blocker and a security issue, has having to confirm, every times, hundreds of operations is prone to errors and could lead to a wrong confirm

MrGarri · 2 months ago

Reporting an adjacent case that may share a root cause: #58652.

Same symptom (Bash(ls *) / Bash(cat *) etc. in permissions.allow are silently ignored, while rules for non-built-in commands like Bash(jq *) work), but the trigger is claude
--agent <name>
rather than a dispatched subagent or skill.

Per the binary, --agent mode runs the named agent as the main session (not as a subagent), so it's a different code path from this issue, but the failure mode looks identical. Posting in case it's useful evidence that the bug isn't subagent-specific but rather affects any non-default agent context.

dmki · 2 months ago

I think Anthropic needs to re-think this permission system. It's a mess.

kreare · 2 months ago
I think Anthropic needs to re-think this permission system. It's a mess.

Agree, and as wrote almost everywhere, this should be considered a CRITICAL and a BLOCKER bug, as having to approve almost every command, tens/hundreds of commands every times generate a lot of noise and dangerous command could be approved wronhly just because you had to approve 23 times in a row a simple "php -l" or any other syntax check.

S-Luiten · 1 month ago

This bug seems to also cause my user-level "disableAutoMode" setting to be ignored. A subagent was able to execute WebFetch without a permission prompt, even though I have NOT allowlisted that tool, I have NOT opted into auto-mode, and I am NOT running bypass/dangerously skip permissions. Related: https://github.com/anthropics/claude-code/issues/64162

MadMartian · 28 days ago

Only work-around seems to be authorizing via the user-level permissions allow in ~/.claude/settings.json

kreare · 28 days ago
Only work-around seems to be authorizing via the user-level permissions allow in ~/.claude/settings.json

Nope, i'm using exactly that and is followed only by the main agent, subagents doesn't follow the auth rules in that file