#6854

security-guidance: UserPromptSubmit blocks every prompt ~4s (up to 30s timeout) on WSL /mnt/c repos — git stash create over 9P

OpenSerkanGeziciopened 8d ago

Summary

On WSL2, for repos on the Windows drive (/mnt/c, 9P/drvfs), the security-guidance UserPromptSubmit hook blocks every prompt for about 4 s. It sometimes takes 10-16 s and hits the 30 s hook timeout on a cold page cache. Nearly all of that time is git stash create in capture_git_baseline. The hook is synchronous and the plugin has no option to background it, so users pay this on every prompt.

This is a different root cause from #4727. There is no state-file bloat here: the repo has 1 untracked file, so _list_untracked takes 0.2 s.

Environment

  • security-guidance 2.0.16 (also reproduced on 2.0.8)
  • Claude Code 2.1.308, native install, WSL2 (kernel 6.6.87.2), git 2.43.0
  • Repo: 1,424 tracked files, about 17 MiB pack, on /mnt/c/...

Evidence

  • Plugin's own log.txt: 210 UPS invocations over 3 days. I measured from Processing: hook_event=UserPromptSubmit to Captured git baseline. Median 3.45 s, p90 5.4 s, max 16.3 s. Transcripts also show 3 hook_cancelled entries with timedOut: true and timeoutMs: 30000 in 60 days. All were in /mnt/c repos.
  • Direct run of the hook (2.0.16, same repo): 4,127 / 4,030 / 4,019 ms. cProfile shows about 95% of the time in capture_git_baseline → subprocess.run(git stash create).
  • GIT_TRACE_PERFORMANCE=1 git -c core.fsmonitor=false stash create: diff-files takes 4.0 s of 4.16 s total. Baseline costs: git diff-files --quiet 1.8-2.3 s, and update-index --refresh 2.2 s. That is the per-file lstat cost over 9P, about 1.4 ms per file, and stash create does it at least twice.
  • Windows git.exe on the same worktree is no faster (stash create 4.9 s), because the plugin forces core.fsmonitor=false.

Impact

Every prompt in such a repo waits about 4 s before the model starts. Occasional 30 s stalls leave no baseline, so the next Stop review diffs against an older baseline. WSL users commonly keep repos on /mnt/c, which they also share with Windows IDEs.

Workaround we're using

We disabled the plugin and re-registered its hooks.json entries in user settings.json, with "async": true on the UserPromptSubmit entry. The prompt is released in about 35 ms, and the baseline is still captured in the background in about 3.7 s, verified in log.txt.

The trade-off: an edit made within the first few seconds of a turn can predate the baseline. A short turn's Stop can also run before the new baseline lands.

Possible fixes

  1. Ship UPS with "async": true, or a documented opt-in. Then have Stop wait briefly for an in-flight baseline capture, for example via a marker in the session state, instead of racing it.
  2. Defer the baseline: capture it lazily on the first PreToolUse for Edit/Write/MultiEdit/NotebookEdit/Bash. Turns without edits then pay nothing, and the capture still happens before the first modification.
  3. Allow core.fsmonitor (or a SG_GIT_FSMONITOR opt-in) instead of forcing it off. With a Windows-side fsmonitor daemon, diff-files drops sharply.
  4. At minimum, log the UPS duration and the cwd in log.txt so slow repos are visible.

Activity

0

No activity yet.