v2.1.123: --dangerously-skip-permissions still denies .claude/skills/ writes in stream-json mode (regression of v2.1.122 fix)
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-permissionsno 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
/tmpcwd, no plugin / MCP interference (also reproduces with user~/.claude/settings.jsonremoved)
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.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗