v2.1.123: --dangerously-skip-permissions still denies .claude/skills/ writes in stream-json mode (regression of v2.1.122 fix)

Resolved 💬 3 comments Opened Apr 29, 2026 by HaraldSirius Closed May 3, 2026

Summary

In Claude Code 2.1.123, writing to .claude/skills/, .claude/agents/, or .claude/commands/ is silently denied even with --dangerously-skip-permissions and permissionMode: "bypassPermissions". This contradicts the v2.1.122 changelog entry:

--dangerously-skip-permissions no longer prompts for writes to .claude/skills/, .claude/agents/, and .claude/commands/

The denial is recorded in the final result event's permission_denials array, with no inbound control_request / can_use_tool for the host to surface to the user. In headless / SDK / stream-json contexts (no TTY), this manifests as an agent that emits {"is_error": true, "content": "Claude requested permissions to write to <path>, but you haven't granted it yet."} and stalls — there is no surface where the user can grant permission.

Environment

  • Claude CLI version: 2.1.123
  • Platform: macOS 25.3.0 (Darwin)
  • Shell: zsh
  • Reproducer: vanilla CLI invocation, no PreToolUse hooks, no project settings, fresh /tmp cwd, no plugin / MCP interference (also reproduces with user ~/.claude/settings.json removed)

Reproducer

mkdir -p /tmp/cm-skills-test/.claude/skills
cd /tmp/cm-skills-test

echo '{"type":"user","message":{"role":"user","content":"Use the Write tool to create .claude/skills/diag/SKILL.md with content: # diag"}}' \
  | claude \
      --input-format stream-json \
      --output-format stream-json \
      --include-partial-messages \
      --verbose \
      --dangerously-skip-permissions \
  2>&1 | tee /tmp/repro.jsonl \
  | grep -E '"type":"result"|permission_denials|"is_error":true'

Expected (per v2.1.122 changelog)

The Write succeeds. .claude/skills/diag/SKILL.md is created. The result event has an empty or absent permission_denials.

Actual

The Write is silently blocked. Output:

{
  "type":"user","message":{...,"content":[{
    "type":"tool_result",
    "content":"Claude requested permissions to write to /private/tmp/cm-skills-test/.claude/skills/diag/SKILL.md, but you haven't granted it yet.",
    "is_error":true,
    "tool_use_id":"toolu_01QsYwXfg13ZjFQN2YBGnsMy"
  }]}
}
{
  "type":"result","subtype":"success","is_error":false,...,
  "permission_denials":[{
    "tool_name":"Write",
    "tool_use_id":"toolu_01QsYwXfg13ZjFQN2YBGnsMy",
    "tool_input":{
      "file_path":"/private/tmp/cm-skills-test/.claude/skills/diag/SKILL.md",
      "content":"# diag"
    }
  }],
  "terminal_reason":"completed"
}

The system / init event for the same session reports "permissionMode":"bypassPermissions" — so the flag is being honored at the mode level, but the .claude/skills/ relaxation from v2.1.122 is not taking effect.

What we ruled out

I'm building a stream-json host (Tauri app wrapping the CLI) and chased this through several wrong hypotheses. For the next person debugging the same thing — these are NOT the cause:

| Hypothesis | Disproven by |
|---|---|
| Inbound control_request / can_use_tool not handled by the host | Raw stdout tap shows zero control_request events for the failing turn |
| PreToolUse hook returning the wrong response shape | Reproduces with no hooks at all (vanilla CLI) |
| User-level ~/.claude/settings.json (permissions.defaultMode: "auto", plugins) overriding the flag | Reproduces with ~/.claude/settings.json temporarily moved aside |
| bypassPermissions mode not being applied | Init event explicitly reports permissionMode: "bypassPermissions" |
| Project-level .claude/settings*.json deny rules | Fresh /tmp cwd, no .claude/settings*.json exists |

Suggested fix direction

If the v2.1.122 relaxation is intentionally limited to TTY contexts, the changelog should say so, and stream-json mode should at minimum emit a control_request (or some addressable event) so SDK hosts can prompt the user instead of seeing a silent permission_denials and a stalled agent. Today there is no documented escape hatch for stream-json hosts that legitimately need to write skills/agents/commands.

Workaround (for other host implementers)

Bash heredoc writes to .claude/... are not subject to this guardrail and go through normal hook/approval paths. We're shipping a host-side detection of permission_denials in the result event with a user-facing toast that suggests asking the agent to retry via Bash, but that's a band-aid — the protocol gap remains.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗