Description
Problem
The guidanceOnce() function in hooks/core/routing.mjs uses process.ppid to create session-scoped marker directories in /tmp/. On Windows with Git Bash, each hook invocation spawns a new bash.exe process, causing process.ppid to differ on every call. This means:
- The file-based throttle marker lands in a new directory each time
- The in-memory
Set is always empty (new Node.js process per invocation)
- Result: Read/Bash/Grep guidance tips are injected on EVERY tool call, not once per session
Evidence
- 716 stale marker directories accumulated in
/tmp/context-mode-guidance-* over ~3 days
- Within a single 2-minute window, 5 different PIDs were observed (25712, 20372, 8512, 8648, 17724)
node -e "process.ppid" run from Bash returns a different PID than what the hook's Node.js process sees
Impact
- Every Read call gets a "use ctx_execute_file instead" system reminder (~200 chars)
- Every Bash call gets a "use ctx_batch_execute instead" system reminder (~200 chars)
- Every Grep call gets a "use ctx_execute instead" system reminder (~200 chars)
- In a 50+ tool call session, this adds 10KB+ of repetitive context noise
- User-reported symptom: Claude stops mid-task after reading files — the conflicting "use a different tool" advice disrupts multi-step planning
Environment
- Windows 11 Pro (10.0.26100)
- Shell: Git Bash (bash.exe)
- Claude Code CLI
- Node.js (invoked via
node hooks/pretooluse.mjs as a hook command)
Root Cause
In routing.mjs line 31:
const _guidanceId = process.env.VITEST_WORKER_ID
? `${process.ppid}-w${process.env.VITEST_WORKER_ID}`
: String(process.ppid);
On macOS/Linux, the parent process (shell) may persist across hook invocations, keeping process.ppid stable. On Windows/Git Bash, a new bash.exe is spawned for each hook command, making process.ppid unique per invocation.
Suggested Fix
Use a more stable session identifier. Options:
- Environment variable: Claude Code could set a
CLAUDE_SESSION_ID env var that persists across hook invocations
- Grandparent PID: Use
process.ppid of the bash parent (the Claude Code process), though this requires an extra syscall
- Startup timestamp: Use a marker from SessionStart (e.g., write a session ID file in
/tmp/ during sessionstart.mjs and read it in pretooluse.mjs)
- Single file with atomic check: Instead of per-ppid directories, use a single file per guidance type with a session timestamp
Option 3 seems most pragmatic — sessionstart.mjs already runs once per session and could write a stable session ID.
Workaround
Remove Read and Grep from the PreToolUse matcher in settings.json (they aren't in context-mode's default hooks.json PreToolUse entries anyway). This eliminates the most disruptive guidance while keeping Bash routing (curl/wget blocking) functional.
Update (2026-04-17)
- Bug confirmed still present in context-mode v1.0.75 (latest at time of writing) at
hooks/core/routing.mjs:31:
const _guidanceId = process.env.VITEST_WORKER_ID
? `${process.ppid}-w${process.env.VITEST_WORKER_ID}`
: String(process.ppid);
- Claude Code 2.1.104 also affected (verified on Windows 11 Pro 10.0.26100, Git Bash).
- Workaround in continuous use for 8 days —
Read/Grep removed from PreToolUse matcher, no regressions observed. Bash routing (curl/wget scheme blocking, etc.) remains functional.
- Supplementary mitigation: a SessionStart hook (
session-cleanup.sh) was added locally to purge stale /tmp/context-mode-guidance-* directories, preventing the accumulation symptom. This treats the symptom, not the root cause — the upstream fix is still needed for correct throttle behavior.
Description
Problem
The
guidanceOnce()function inhooks/core/routing.mjsusesprocess.ppidto create session-scoped marker directories in/tmp/. On Windows with Git Bash, each hook invocation spawns a newbash.exeprocess, causingprocess.ppidto differ on every call. This means:Setis always empty (new Node.js process per invocation)Evidence
/tmp/context-mode-guidance-*over ~3 daysnode -e "process.ppid"run from Bash returns a different PID than what the hook's Node.js process seesImpact
Environment
node hooks/pretooluse.mjsas a hook command)Root Cause
In
routing.mjsline 31:On macOS/Linux, the parent process (shell) may persist across hook invocations, keeping
process.ppidstable. On Windows/Git Bash, a newbash.exeis spawned for each hook command, makingprocess.ppidunique per invocation.Suggested Fix
Use a more stable session identifier. Options:
CLAUDE_SESSION_IDenv var that persists across hook invocationsprocess.ppidof the bash parent (the Claude Code process), though this requires an extra syscall/tmp/duringsessionstart.mjsand read it inpretooluse.mjs)Option 3 seems most pragmatic —
sessionstart.mjsalready runs once per session and could write a stable session ID.Workaround
Remove
ReadandGrepfrom the PreToolUse matcher insettings.json(they aren't in context-mode's defaulthooks.jsonPreToolUse entries anyway). This eliminates the most disruptive guidance while keeping Bash routing (curl/wget blocking) functional.Update (2026-04-17)
hooks/core/routing.mjs:31:Read/Grepremoved from PreToolUse matcher, no regressions observed.Bashrouting (curl/wget scheme blocking, etc.) remains functional.session-cleanup.sh) was added locally to purge stale/tmp/context-mode-guidance-*directories, preventing the accumulation symptom. This treats the symptom, not the root cause — the upstream fix is still needed for correct throttle behavior.