f849a69506
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.
119 lines
3.9 KiB
TypeScript
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);
|
|
}
|
|
}
|