[BUG] `.claude/skills/**` Edit hangs indefinitely in headless `--dangerously-skip-permissions` sessions (persists across v2.1.114, v2.1.116, v2.1.117)
Follow-up to #50727 (filed 2026-04-19, two failure modes documented). Mode B (assistant-never-emits-next-message after successful tool_result) appears to have been fixed in the v2.1.113 era. Mode A — Edit tool_use with no matching tool_result, file untouched on disk, session freezes indefinitely — persists across v2.1.114, v2.1.116, v2.1.117. Filing fresh to keep the surviving failure mode focused and with fresh evidence. Cross-linking #50727, #36497, #47081, #36923, #36192 as related.
Environment
- Claude Code CLI: v2.1.114, v2.1.116, v2.1.117 — all exhibit the stall (reproduced within 72 hours)
- Model:
claude-opus-4-7(1M context),CLAUDE_CODE_EFFORT_LEVEL=max - Platform: Ubuntu 24.04 on AWS EC2
- Launch (systemd unit):
script -qec "claude --channels plugin:telegram@claude-plugins-official --dangerously-skip-permissions --disallowedTools mcp__telegram-mcp__tg_send" - No
PreToolUsehook.PostToolUseFailurehook present (matcher.*, async, trivial log-only — not on the critical path).
Detection signature
Every recurrence matches identically:
assistantJSONL entry withcontent[].type == "tool_use",name == "Edit"(or"Write")input.file_pathunder.claude/**(specifically.claude/skills/**in all observed cases)- No matching
tool_resultever written to JSONL - No
permission-request/permission-denied/permission-modechange events anywhere in the session following the tool_use — the JSONL stops writing at thetool_useentry. Key signal: whatever is blocking is upstream of the permission-event emitter. - Target file on disk stays byte-identical (git-clean)
- Claude process stays alive,
STAT=Sl+, ~0% CPU,ELAPSEDkeeps incrementing indefinitely - No signal in
journalctl, noWHEA, no OS-level errors - No stream-idle timeout fires either (session frozen past 10+ minutes, well past default 90s
CLAUDE_STREAM_IDLE_TIMEOUT_MS)
Recurrence evidence
| UTC | CLI | Target | Outcome |
|---|---|---|---|
| 2026-04-19 | 2.1.109 | .claude/skills/holder-scanner/scripts/scan_holders.sh | #50727 Mode A — original report |
| 2026-04-21 16:34 | 2.1.114 | .claude/skills/discovery/SKILL.md (P1 apply, new_len=1669) | Hung; restart + SSH-apply |
| 2026-04-22 08:33 | 2.1.116 | .claude/skills/discovery/SKILL.md (mirror-patch) | Hung; restart + SSH-apply |
| 2026-04-22 09:19 | 2.1.117 | .claude/skills/discovery/SKILL.md (retry after string-match error) | Hung |
| 2026-04-22 10:01 | 2.1.117 + perm rules | .claude/skills/discovery/SKILL.md (re-attempt same content) | Hung again |
Every hang target is under .claude/skills/**. Non-skill file Edits on the same system work reliably.
Reproducible diagnostic: "string not found" returns normally, match hangs
When the Edit's old_string does not match the file content (self-correcting retries after typos / indent errors), the tool_result with is_error: true, content: "<tool_use_error>String to replace not found in file..." returns in ~20ms — no hang.
The hang only occurs when old_string matches, i.e. when a real write would proceed. This strongly suggests a gate firing between match-validation and actual file-write — not a streaming, network, or response-size issue.
Workarounds tested
- ❌ CLI upgrade. v2.1.114 → v2.1.116 → v2.1.117 all exhibit Mode A unchanged. The v2.1.113 fix that appeared to resolve the original #50727 was likely a Mode B fix that coincidentally allowed work to proceed, not a Mode A resolution.
- ❌ Path-specific
permissions.allowrules. Added"Edit(.claude/**)"and"Write(.claude/**)"tosettings.jsonpermissions.allow, restarted service. Hang persisted identically on the first real Edit attempt after restart. No permission-related events appear in the JSONL before or after the rule addition — the allow-rule path does not appear to be consulted for this hang class, consistent with the hardcoded protection described in #36497 / #47081 / #36923 / #36192. - ✅ Out-of-session SSH apply. Interactive Claude Code session on a separate machine (TTY-attached, same CLI version v2.1.117, same codebase, same target file) applies the identical edit without issue every time. This is the current operational fallback but it defeats the purpose of headless-agent autonomy.
Hypothesis (partially tested)
The four related issues (#36497 skill-exemption gap, #47081 bypass doesn't bypass skill/memory edits, #36923 SKILL.md creation prompt, #36192 DSP doesn't bypass Edit) all describe symptoms consistent with a single hardcoded path-protection gate for .claude/** that sits before the user-configurable permission layer. In interactive sessions it surfaces as an annoying prompt that can't be suppressed by allow-rules. In headless sessions with no TTY, it surfaces as a silent indefinite hang because the prompt has no display surface and no timeout.
If that model is correct, this fifth symptom (headless hang) and the four related issues all share one root cause. Fixing the exemption-function gap (per #36497) or providing an override mechanism usable from headless (env var, managed-settings.json key, or permissionMode value that genuinely bypasses the skill gate) would address the full family.
What would help
- Acknowledgement of whether the five related issues share a root cause (hardcoded skill/memory gate upstream of allow-rules)
- A supported mechanism for headless agents to auto-approve edits to their own
.claude/skills/**— right now there is no way for--dangerously-skip-permissionsto live up to its name for this path class - Any tracing flag that surfaces what the CC runtime is waiting on during the hang. Happy to run
straceon a fresh repro, attach JSONLs, service unit, settings — prefer a private channel because conversation content is business-sensitive.
Data available on request
Full JSONLs from all five stalls, systemd unit file, settings.json, environment, process-tree snapshots during stall, and git state showing target files git-clean across all five. Prefer sending via a private email or a scoped repo rather than attaching publicly.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗