Identify the claude process by argument, not by substring
Testing the reboot case exposed a live regression, and the identity check added for reboots is what made it visible. Matching "claude" anywhere in an ancestor's command line was too loose. The hook is spawned as `/bin/sh -c /.../claude-status-hook.py`, so its parent's command line contains "claude" -- in the path to this very script -- while being a shell that exits milliseconds later. That shell's pid was being recorded as the session's. Existence checks alone hid it: the pid was dead, the file was deleted, the next event recreated it, and the session flickered. Once the pid was pinned to a process start time the session vanished outright. The rule was broadened in the first place to cover npm-style installs that run `node .../claude-code/cli.js`, which was a real gap. It is now matched per argument instead: argv[0] named claude, or a cli.js under a claude path. Both installs pass, and the spawning shell does not. The pid is also resolved before the unchanged-check rather than after, so a pid that has changed forces a write. A session resumed under a new pid -- which is exactly what --resume does, and what this session had done -- kept its old pid for as long as its state happened not to change, and the reader would drop it as dead. The debug log now records the process ancestry, which is what made this diagnosable at all rather than guessable.
This commit is contained in:
@@ -32,6 +32,31 @@ ev() { # event [extra json]
|
||||
echo "{\"session_id\":\"$SID\",\"hook_event_name\":\"$1\",\"cwd\":\"/tmp/proj\"${2:+,$2}}"
|
||||
}
|
||||
|
||||
# --- identifying the claude process ----------------------------------------
|
||||
# The hook is spawned as `/bin/sh -c /.../claude-status-hook.py`, so its
|
||||
# parent's command line contains "claude" in a path without being claude.
|
||||
# Matching on the raw string latches onto that shell, which exits at once.
|
||||
python3 - "$HOOK" <<'PY'
|
||||
import importlib.util, sys
|
||||
spec = importlib.util.spec_from_file_location("h", sys.argv[1])
|
||||
h = importlib.util.module_from_spec(spec); spec.loader.exec_module(h)
|
||||
cases = [
|
||||
("/bin/sh -c /home/u/claude-code-gnome-extension/hooks/claude-status-hook.py ", False),
|
||||
("/home/u/.local/bin/claude --resume ", True),
|
||||
("bash /home/u/bin/claude ", True),
|
||||
("node /usr/lib/node_modules/@anthropic-ai/claude-code/cli.js ", True),
|
||||
("/home/u/bin/zellij --server /run/user/1000/zellij/x ", False),
|
||||
("nvim /home/u/.claude/settings.json ", False),
|
||||
]
|
||||
bad = 0
|
||||
for cmd, want in cases:
|
||||
got = h.looks_like_claude(cmd)
|
||||
print(("ok " if got == want else "FAIL ") + "claude in %r -> %s" % (cmd[:46], got))
|
||||
bad += got != want
|
||||
sys.exit(1 if bad else 0)
|
||||
PY
|
||||
check "command lines classified correctly" "0" "$?"
|
||||
|
||||
# --- state machine ---------------------------------------------------------
|
||||
emit "$(ev SessionStart '"source":"startup"')"
|
||||
check "SessionStart -> waiting" "waiting" "$(field state)"
|
||||
|
||||
Reference in New Issue
Block a user