Files
claude-mem/src/hooks/cleanup.ts
T
Alex Newman f849a69506 fix: Move ensureWorkerRunning to start of hooks to prevent race condition
All hooks now call ensureWorkerRunning() BEFORE querying the database. This
ensures the worker's orphaned session cleanup runs before hooks check for
active sessions, preventing 404 errors when hooks try to use sessions that
don't exist in worker memory after a restart.

Hook order now:
1. ensureWorkerRunning() - starts worker, runs cleanup
2. Query DB - cleanup already marked orphaned sessions as failed
3. Use session - only valid sessions are processed

Fixed in:
- new-hook: Line 26, before DB queries
- save-hook: Line 37, before DB queries
- summary-hook: Line 24, before DB queries
- cleanup-hook: Line 50, before DB queries

This prevents the race condition where hooks would read session status before
cleanup ran, then get 404 from worker after cleanup marked sessions failed.
2025-10-19 02:01:11 -04:00

119 lines
3.9 KiB
TypeScript

import { SessionStore } from '../services/sqlite/SessionStore.js';
import { ensureWorkerRunning } from '../shared/worker-utils.js';
export interface SessionEndInput {
session_id: string;
cwd: string;
transcript_path?: string;
hook_event_name: string;
reason: 'exit' | 'clear' | 'logout' | 'prompt_input_exit' | 'other';
}
/**
* Cleanup Hook - SessionEnd
* Cleans up worker session via HTTP DELETE
*
* This hook runs when a Claude Code session ends. It:
* 1. Finds active SDK session for this Claude session
* 2. Sends DELETE request to worker service
* 3. Marks session as failed if not already completed
*/
export async function cleanupHook(input?: SessionEndInput): Promise<void> {
try {
// Log hook entry point
console.error('[claude-mem cleanup] Hook fired', {
input: input ? {
session_id: input.session_id,
cwd: input.cwd,
reason: input.reason
} : null
});
// Handle standalone execution (no input provided)
if (!input) {
console.log('No input provided - this script is designed to run as a Claude Code SessionEnd hook');
console.log('\nExpected input format:');
console.log(JSON.stringify({
session_id: "string",
cwd: "string",
transcript_path: "string",
hook_event_name: "SessionEnd",
reason: "exit"
}, null, 2));
process.exit(0);
}
const { session_id, reason } = input;
console.error('[claude-mem cleanup] Searching for active SDK session', { session_id, reason });
// Ensure worker is running first (runs cleanup if restarting)
const workerReady = await ensureWorkerRunning();
if (!workerReady) {
console.error('[claude-mem cleanup] Worker not available - skipping HTTP cleanup');
}
// Find active SDK session
const db = new SessionStore();
const session = db.findActiveSDKSession(session_id);
if (!session) {
// No active session - nothing to clean up
console.error('[claude-mem cleanup] No active SDK session found', { session_id });
db.close();
console.log('{"continue": true, "suppressOutput": true}');
process.exit(0);
}
console.error('[claude-mem cleanup] Active SDK session found', {
session_id: session.id,
sdk_session_id: session.sdk_session_id,
project: session.project,
worker_port: session.worker_port
});
// 1. Delete session via HTTP
if (session.worker_port && workerReady) {
try {
const response = await fetch(`http://127.0.0.1:${session.worker_port}/sessions/${session.id}`, {
method: 'DELETE',
signal: AbortSignal.timeout(5000)
});
if (response.ok) {
console.error('[claude-mem cleanup] Session deleted successfully via HTTP');
} else {
console.error('[claude-mem cleanup] Failed to delete session:', await response.text());
}
} catch (error: any) {
console.error('[claude-mem cleanup] HTTP DELETE error:', error.message);
}
} else {
console.error('[claude-mem cleanup] No worker available or no worker port, skipping HTTP cleanup');
}
// 2. Mark session as failed in DB (if not already completed)
try {
db.markSessionFailed(session.id);
console.error('[claude-mem cleanup] Session marked as failed in database');
} catch (markErr: any) {
console.error('[claude-mem cleanup] Failed to mark session as failed:', markErr);
}
db.close();
console.error('[claude-mem cleanup] Cleanup completed successfully');
console.log('{"continue": true, "suppressOutput": true}');
process.exit(0);
} catch (error: any) {
// On error, don't block Claude Code exit
console.error('[claude-mem cleanup] Unexpected error in hook', {
error: error.message,
stack: error.stack,
name: error.name
});
console.log('{"continue": true, "suppressOutput": true}');
process.exit(0);
}
}