mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-23 02:23:33 +08:00
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:
@@ -190,7 +190,16 @@ async function main() {
|
|||||||
|
|
||||||
const sessionsDir = getSessionsDir();
|
const sessionsDir = getSessionsDir();
|
||||||
const today = getDateString();
|
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 sessionFile = path.join(sessionsDir, `${today}-${shortId}-session.tmp`);
|
||||||
const sessionMetadata = getSessionMetadata();
|
const sessionMetadata = getSessionMetadata();
|
||||||
|
|
||||||
|
|||||||
@@ -633,6 +633,40 @@ async function runTests() {
|
|||||||
passed++;
|
passed++;
|
||||||
else failed++;
|
else failed++;
|
||||||
|
|
||||||
|
// Regression test for #1494: transcript_path UUID takes precedence over fallback
|
||||||
|
if (
|
||||||
|
await asyncTest('derives shortId from transcript_path UUID when available', async () => {
|
||||||
|
const isoHome = path.join(os.tmpdir(), `ecc-session-transcript-${Date.now()}`);
|
||||||
|
const transcriptUuid = 'abcdef12-3456-4789-a012-bcdef3456789';
|
||||||
|
const expectedShortId = 'abcdef12'; // First 8 chars of UUID
|
||||||
|
const transcriptPath = path.join(isoHome, 'transcripts', `${transcriptUuid}.jsonl`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(path.dirname(transcriptPath), { recursive: true });
|
||||||
|
fs.writeFileSync(transcriptPath, '');
|
||||||
|
|
||||||
|
const stdinJson = JSON.stringify({ transcript_path: transcriptPath });
|
||||||
|
await runScript(path.join(scriptsDir, 'session-end.js'), stdinJson, {
|
||||||
|
HOME: isoHome,
|
||||||
|
USERPROFILE: isoHome,
|
||||||
|
// CLAUDE_SESSION_ID intentionally unset so that without the fix the project-name
|
||||||
|
// fallback would be used, exposing the filename collision described in #1494.
|
||||||
|
});
|
||||||
|
|
||||||
|
const sessionsDir = getCanonicalSessionsDir(isoHome);
|
||||||
|
const now = new Date();
|
||||||
|
const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
||||||
|
const sessionFile = path.join(sessionsDir, `${today}-${expectedShortId}-session.tmp`);
|
||||||
|
|
||||||
|
assert.ok(fs.existsSync(sessionFile), `Session file with transcript UUID shortId should exist: ${sessionFile}`);
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(isoHome, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
passed++;
|
||||||
|
else failed++;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
await asyncTest('writes project, branch, and worktree metadata into new session files', async () => {
|
await asyncTest('writes project, branch, and worktree metadata into new session files', async () => {
|
||||||
const isoHome = path.join(os.tmpdir(), `ecc-session-metadata-${Date.now()}`);
|
const isoHome = path.join(os.tmpdir(), `ecc-session-metadata-${Date.now()}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user