Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save haydenholligan/b676acb48d250490787a9a83feb8923b to your computer and use it in GitHub Desktop.

Select an option

Save haydenholligan/b676acb48d250490787a9a83feb8923b to your computer and use it in GitHub Desktop.
fix: skip hook injection for non-interactive (sdk-cli) sessions
# Bug: Caveman hooks fire on sdk-cli probe sessions
## Summary
Caveman's `SessionStart` and `UserPromptSubmit` hooks fire on every Claude
session, including non-interactive `sdk-cli` sessions spawned by external
tools. This causes those tools to receive caveman-mode responses instead of
the plain output they expect, silently breaking their functionality.
## How it was discovered
CodexBar (a macOS menu bar app that tracks Claude usage) shows the error:
> Could not parse Claude usage: Missing Current session.
Investigation traced the failure to CodexBar's probe mechanism. Every ~2
minutes, CodexBar spawns a Claude `sdk-cli` session with `cwd` set to
`~/Library/Application Support/CodexBar/ClaudeProbe` and sends the prompt
`"show usage\n\n"`. It then parses the response text using regex patterns
to extract current session token/cost data.
## Root cause
The `~/.claude/sessions/` directory confirmed the probe sessions have
`"entrypoint":"sdk-cli"` in their session files. Inspecting the probe
session's JSONL (`~/.claude/projects/-Users-hayden/<session-id>.jsonl`)
revealed the full injection chain:
**Attachment: `SessionStart:startup` hook**
```
CAVEMAN MODE ACTIVE — level: full
Respond terse like smart caveman...
```
**Attachment: `UserPromptSubmit` hook**
```
CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging...
```
**Attachment: `skill_listing`** (injected by UserPromptSubmit hook)
```
- caveman:caveman-stats: Show real token usage...
```
With the caveman ruleset and skill listing injected, Claude matched
`"show usage"` → "token usage/savings" → `caveman:caveman-stats` skill.
The `caveman-stats` hook then blocked the turn and injected stats output
instead of a response. CodexBar received:
> Hook blocked — stats injected directly. If nothing displayed, hook may
> not have fired correctly. Run `/caveman-stats` in the input instead.
This is unparseable by CodexBar's regex patterns → "Missing Current session."
## Affected tools
Any tool that spawns a Claude `sdk-cli` session hits this:
- CodexBar (usage monitoring)
- CI/CD pipelines using Claude SDK
- Automation scripts using `claude -p` or the Claude SDK
- Other IDE extensions or menu bar apps with Claude integrations
## Fix
Both hooks check `transcript_path` from stdin to identify the session, look
up the session file in `~/.claude/sessions/`, and read the `entrypoint`
field. If `entrypoint !== "cli"`, the hooks exit immediately without
injecting any caveman context.
`SessionStart` uses async stdin reading with a 200ms fallback timeout so it
does not block on sessions that may send no stdin data.
`UserPromptSubmit` returns `{}` (no-op) for non-interactive sessions.
Interactive `cli` sessions are unaffected.
## Testing
Verified with a synthetic sdk-cli session file:
```bash
# Create fake sdk-cli session
echo '{"pid":99999,"sessionId":"test-id","entrypoint":"sdk-cli",...}' \
> ~/.claude/sessions/99999.json
# UserPromptSubmit hook returns {} (no caveman injection)
echo '{"transcript_path":".../test-id.jsonl","prompt":"show usage"}' \
| node src/hooks/caveman-mode-tracker.js
# → {}
# Interactive session still gets caveman context
echo '{"transcript_path":".../real-session.jsonl","prompt":"show usage"}' \
| node src/hooks/caveman-mode-tracker.js
# → {"hookSpecificOutput":{"additionalContext":"CAVEMAN MODE ACTIVE..."}}
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment