[BUG] `.claude/skills/**` Edit hangs indefinitely in headless `--dangerously-skip-permissions` sessions (persists across v2.1.114, v2.1.116, v2.1.117)

Resolved 💬 2 comments Opened Apr 22, 2026 by Axiom89 Closed May 28, 2026

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 PreToolUse hook. PostToolUseFailure hook present (matcher .*, async, trivial log-only — not on the critical path).

Detection signature

Every recurrence matches identically:

  • assistant JSONL entry with content[].type == "tool_use", name == "Edit" (or "Write")
  • input.file_path under .claude/** (specifically .claude/skills/** in all observed cases)
  • No matching tool_result ever written to JSONL
  • No permission-request / permission-denied / permission-mode change events anywhere in the session following the tool_use — the JSONL stops writing at the tool_use entry. 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, ELAPSED keeps incrementing indefinitely
  • No signal in journalctl, no WHEA, 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

  1. 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.
  2. Path-specific permissions.allow rules. Added "Edit(.claude/**)" and "Write(.claude/**)" to settings.json permissions.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.
  3. 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-permissions to 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 strace on 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.

View original on GitHub ↗

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