Files
claude-mem/src/hooks/new.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

89 lines
2.9 KiB
TypeScript

import path from 'path';
import { SessionStore } from '../services/sqlite/SessionStore.js';
import { createHookResponse } from './hook-response.js';
import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js';
export interface UserPromptSubmitInput {
session_id: string;
cwd: string;
prompt: string;
[key: string]: any;
}
/**
* New Hook - UserPromptSubmit
* Initializes SDK memory session via HTTP POST to worker service
*/
export async function newHook(input?: UserPromptSubmitInput): Promise<void> {
if (!input) {
throw new Error('newHook requires input');
}
const { session_id, cwd, prompt } = input;
const project = path.basename(cwd);
// Ensure worker is running first (runs cleanup if restarting)
const workerReady = await ensureWorkerRunning();
if (!workerReady) {
throw new Error('Worker service failed to start or become healthy');
}
const db = new SessionStore();
try {
// Check for any existing session (active, failed, or completed)
let existing = db.findActiveSDKSession(session_id);
let sessionDbId: number;
let isNewSession = false;
if (existing) {
// Session already active, increment prompt counter
sessionDbId = existing.id;
const promptNumber = db.incrementPromptCounter(sessionDbId);
console.error(`[new-hook] Continuing session ${sessionDbId}, prompt #${promptNumber}`);
} else {
// Check for inactive sessions we can reuse
const inactive = db.findAnySDKSession(session_id);
if (inactive) {
// Reactivate the existing session
sessionDbId = inactive.id;
db.reactivateSession(sessionDbId, prompt);
const promptNumber = db.incrementPromptCounter(sessionDbId);
isNewSession = true;
console.error(`[new-hook] Reactivated session ${sessionDbId}, prompt #${promptNumber}`);
} else {
// Create new session
sessionDbId = db.createSDKSession(session_id, project, prompt);
const promptNumber = db.incrementPromptCounter(sessionDbId);
isNewSession = true;
console.error(`[new-hook] Created new session ${sessionDbId}, prompt #${promptNumber}`);
}
}
// Get fixed port
const port = getWorkerPort();
// Only initialize worker on new sessions
if (isNewSession) {
// Initialize session via HTTP
const response = await fetch(`http://127.0.0.1:${port}/sessions/${sessionDbId}/init`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ project, userPrompt: prompt }),
signal: AbortSignal.timeout(5000)
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Failed to initialize session: ${response.status} ${errorText}`);
}
}
console.log(createHookResponse('UserPromptSubmit', true));
} finally {
db.close();
}
}