fix(hooks): isolate session-end.js filename using transcript_path UUID

When session-end.js runs and CLAUDE_SESSION_ID is unset, getSessionIdShort()
falls back to the project/worktree name. If any other Stop-hook in the chain
spawns a claude subprocess (e.g. an AI-summary generator using 'claude -p'),
the subprocess also fires the full Stop chain and writes to the same project-
name-based filename, clobbering the parent's valid session summary with a
summary of the summarization prompt itself.

Fix: when stdin JSON (or CLAUDE_TRANSCRIPT_PATH) provides a transcript_path,
extract the first 8 hex chars of the session UUID from the filename and use
that as shortId. Falls back to the original getSessionIdShort() when no
transcript_path is available, so existing behavior is preserved for all
callers that do not set it.

Adds a regression test in tests/hooks/hooks.test.js.

Refs #1494
This commit is contained in:
Taro Kawakami
2026-04-19 11:37:32 +09:00
parent 1a50145d39
commit a35b2d125d
2 changed files with 44 additions and 1 deletions

View File

@@ -190,7 +190,16 @@ async function main() {
const sessionsDir = getSessionsDir();
const today = getDateString();
const shortId = getSessionIdShort();
// Prefer the real session UUID (first 8 chars) from transcript_path when available.
// Without this, a parent session and any `claude -p ...` subprocess spawned by
// another Stop-hook share the project-name fallback filename, and the subprocess
// overwrites the parent's summary. See issue #1494 for full repro details.
let shortId = null;
if (transcriptPath) {
const m = path.basename(transcriptPath).match(/([0-9a-f]{8})-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.jsonl$/i);
if (m) { shortId = m[1]; }
}
if (!shortId) { shortId = getSessionIdShort(); }
const sessionFile = path.join(sessionsDir, `${today}-${shortId}-session.tmp`);
const sessionMetadata = getSessionMetadata();