[BUG] Stop hook fails with EEXIST when plugin data directory already exists

Status Fixed / completed
Reported on v2.1.79
Maintainer reply None cached
Activity 5 comments · opened Mar 27, 2026 · closed May 13, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Stop hooks from plugins fail with EEXIST: file already exists, mkdir when the plugin data directory already exists. The error occurs on every stop hook invocation after the first run.

This is the same root cause as several other EEXIST bugs (#33118, #36919, #37306) — all are missing { recursive: true } on mkdir calls. Closing #39108 as a duplicate in favor of this report.

What Should Happen?

The plugin data directory creation should use recursive/idempotent mkdir (fs.mkdir with { recursive: true }) so it doesn't fail if the directory already exists.

Error Messages/Logs

Ran 1 stop hook (ctrl+o to expand)
  ⎿  Stop hook error: Failed to run: EEXIST: file already exists, mkdir
     'C:\Users\PC\.claude\plugins\data\qingyi-qingyi'

The directory already exists from a previous session:

$ ls ~/.claude/plugins/data/
qingyi-qingyi/
superpowers-claude-plugins-official/

Steps to Reproduce

  1. Install a plugin that uses a stop hook
  2. Run a Claude Code session — stop hook works on first run (creates data dir)
  3. Run another session — stop hook fails because directory already exists

Claude Model

None

Is this a regression?

Yes, this worked in a previous version

Last Working Version

2.1.79

Claude Code Version

2.1.85

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

IntelliJ IDEA terminal

Additional Information

The hook itself never executes — the error is in the plugin infrastructure before the hook runs. The fix should be a one-line change: use fs.mkdir(path, { recursive: true }) instead of fs.mkdir(path) when creating the plugin data directory.

View original on GitHub ↗

5 Comments

github-actions[bot] · 6 months ago

Found 1 possible duplicate issue:

  1. https://github.com/anthropics/claude-code/issues/39108

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

yurukusa · 6 months ago

Workaround: make Stop hooks resilient to filesystem errors:

OUTFILE="/tmp/cc-stop-$(date +%s)-$RANDOM.log"
echo "Stop" > "$OUTFILE" 2>/dev/null
exit 0
hadhud · 6 months ago

Confirming on v2.1.100 (Windows 11 Pro, Git Bash, Node v22).

Plugin: azure@azure-skills (scope: local, v1.0.14 -- latest)
Hook type: PostToolUse:Bash -- not limited to Stop hooks

Actual error from Claude Code:

PostToolUse:Bash hook error
Failed to run: EEXIST: file already exists, mkdir 'C:\Users\<user>\.claude\plugins\data\azure-azure-skills'

The data directory exists but is empty. The hook script never executes -- the error is thrown by the plugin runtime before the shell process spawns, so plugin-side error handling (set +e, early exit) cannot mitigate it.

Standard Node.js behavior confirms the pattern:

fs.mkdirSync(dataDir);                        // throws EEXIST
fs.mkdirSync(dataDir, { recursive: true });   // no-op when dir exists

Two additions to the original report:

  1. Affects PostToolUse hooks, not just Stop hooks -- the mkdir call appears to be in a shared plugin dispatch path.
  2. Still present on v2.1.100 (original report was v2.1.85), confirming this has persisted across multiple releases.
alsgur9865-sketch · 5 months ago

Cross-linking from #55427 (auto-closed as duplicate) — same root cause but with fully blocking impact worth surfacing here.

In addition to the plugin data-directory mkdir, the same EEXIST pattern affects the Bash tool's per-session env directory (~/.claude/session-env/<uuid>). The user-facing consequence is far more severe than the plugin case:

  • Plugin SessionStart: noisy but hooks still execute.
  • Bash tool: fully blocking — once EEXIST triggers (more likely under concurrent Bash invocations on Windows), every subsequent Bash call in the same session fails. The tool layer enters a permanent broken state. Closing/restarting Claude Code is the only recovery; in some cases the on-disk session-env folder must be deleted manually before the next start.

Repro signal

Reproduced reliably on Claude Code 2.1.119 (Windows 11, Git Bash) by issuing 2–3 long-running Bash invocations concurrently (e.g. npm test, npm run typecheck, npm run lint in parallel). The first errors out, all subsequent Bash calls in the session emit EEXIST immediately without executing.

This confirms the regression spans 2.1.85 → 2.1.119.

Workflow impact

Blocks all shell-based work in the affected session: npm, gh, builds, tests — everything. The only way to recover is restart + (sometimes) manual folder removal.

Suggested fix (unchanged)

fs.mkdir(path, { recursive: true }) — or a try/catch that swallows EEXIST — at both call sites:

  1. Plugin data-directory bootstrap (~/.claude/plugins/data/<plugin>-<marketplace>).
  2. Bash tool session-env bootstrap (~/.claude/session-env/<uuid>).

Happy to provide additional logs / a tighter repro if useful.

github-actions[bot] · 3 months ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.