Skip to content

guidanceOnce throttle keyed on process.ppid never throttles under Claude Code (tip re-injected on every call) #1107

Description

@charl2x

Summary

The "show each advisory type at most once per session" throttle in hooks/core/routing.mjs (guidanceOnce) never throttles under Claude Code: the marker directory is keyed on process.ppid, but Claude Code spawns every hook invocation under a fresh parent process, so the key changes on every call.

Measured impact (Claude Code on WSL2, plugin 1.0.65)

  • 2,149 directories /tmp/context-mode-guidance-<pid> accumulated, one per hook invocation (each containing a single marker file that no later invocation ever reads).
  • The context_guidance tip (~90 tokens) was injected on every Bash/Read/Grep call in every session, instead of once per session per type. On a working session with 100+ tool calls, that is thousands of tokens of repeated identical guidance, which is the opposite of what context-mode is for.
  • Nothing ever cleans these directories up (sessionstart's lazy cleanup only handles plugin cache version dirs).

Root cause

// hooks/core/routing.mjs
const _guidanceId = process.env.VITEST_WORKER_ID
  ? `${process.ppid}-w${process.env.VITEST_WORKER_ID}`
  : String(process.ppid);
const _guidanceDir = resolve(tmpdir(), `context-mode-guidance-${_guidanceId}`);

process.ppid is only stable when the host process invokes hooks directly and stays alive. Claude Code (and likely other hosts that shell out per hook) gives each invocation a different parent PID, so O_CREAT | O_EXCL always succeeds and the guidance is returned every time.

Suggested fix

Key the marker directory on the session rather than the parent process. The hook stdin payload already carries session_id:

  • in pretooluse.mjs, after JSON.parse(raw): if (input.session_id) globalThis.__ctxGuidanceSessionId = String(input.session_id);
  • in routing.mjs, resolve the marker dir lazily: session_id (via the global) → process.env.CLAUDE_SESSION_ID → process.ppid as the last-resort fallback (preserves current behavior for in-process hosts like the OpenCode ts-plugin, and the VITEST_WORKER_ID suffix logic).

Running with this patch locally: first call per type gets the tip, subsequent calls are quiet, one marker dir per session. Happy to send a PR if useful.

A cleanup pass for stale context-mode-guidance-* dirs (age-gated, like the #181 cache cleanup) would also help hosts that accumulated thousands of them.

Two smaller observations from the same audit

  1. <output_constraints> in the SessionStart routing block (word_limit 500 words, "Return only: file path + 1-line description") reads like subagent instructions, but it is injected into the main session too, where it fights the host's own response conventions. Consider emitting it only on the Agent/Task prompt-append path.
  2. On this setup, ctx_fetch_and_index failed 60% and ctx_search 58% of calls over 7 days (147 log files), while ctx_execute was at 0% over 919 calls, yet the injected hierarchy routes to batch_execute/search first. A "fall back to ctx_execute on error" hint in the guidance would keep agents from retrying the failing path. I can open a separate issue with the failure details if useful.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions