Files
claude-mem/src/hooks/save-hook.ts
T
Alex Newman 1fb8df42b6 Refactor hook timeout settings to use centralized constants
- Introduced a new module `hook-constants.ts` to define timeout constants for various hooks.
- Updated `cleanup-hook.ts`, `context-hook.ts`, `save-hook.ts`, and `summary-hook.ts` to utilize the new `HOOK_TIMEOUTS.DEFAULT` for fetch timeouts instead of hardcoded values.
- Adjusted worker utility timeouts in `worker-utils.ts` to use constants from `hook-constants.ts`, improving maintainability and consistency across the codebase.
2025-12-09 14:25:53 -05:00

101 lines
3.2 KiB
TypeScript

/**
* Save Hook - PostToolUse
*
* Pure HTTP client - sends data to worker, worker handles all database operations
* including privacy checks. This allows the hook to run under any runtime
* (Node.js or Bun) since it has no native module dependencies.
*/
import { stdin } from 'process';
import { createHookResponse } from './hook-response.js';
import { logger } from '../utils/logger.js';
import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js';
import { HOOK_TIMEOUTS } from '../shared/hook-constants.js';
export interface PostToolUseInput {
session_id: string;
cwd: string;
tool_name: string;
tool_input: any;
tool_response: any;
[key: string]: any;
}
// Tools to skip (low value or too frequent)
const SKIP_TOOLS = new Set([
'ListMcpResourcesTool', // MCP infrastructure
'SlashCommand', // Command invocation (observe what it produces, not the call)
'Skill', // Skill invocation (observe what it produces, not the call)
'TodoWrite', // Task management meta-tool
'AskUserQuestion' // User interaction, not substantive work
]);
/**
* Save Hook Main Logic - Fire-and-forget HTTP client
*/
async function saveHook(input?: PostToolUseInput): Promise<void> {
// Ensure worker is running before any other logic
await ensureWorkerRunning();
if (!input) {
throw new Error('saveHook requires input');
}
const { session_id, cwd, tool_name, tool_input, tool_response } = input;
if (SKIP_TOOLS.has(tool_name)) {
console.log(createHookResponse('PostToolUse', true));
return;
}
const port = getWorkerPort();
const toolStr = logger.formatTool(tool_name, tool_input);
logger.dataIn('HOOK', `PostToolUse: ${toolStr}`, {
workerPort: port
});
try {
// Send to worker - worker handles privacy check and database operations
const response = await fetch(`http://127.0.0.1:${port}/api/sessions/observations`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
claudeSessionId: session_id,
tool_name,
tool_input,
tool_response,
cwd: cwd || ''
}),
signal: AbortSignal.timeout(HOOK_TIMEOUTS.DEFAULT)
});
if (!response.ok) {
const errorText = await response.text();
logger.failure('HOOK', 'Failed to send observation', {
status: response.status
}, errorText);
throw new Error(`Failed to send observation to worker: ${response.status} ${errorText}`);
}
logger.debug('HOOK', 'Observation sent successfully', { toolName: tool_name });
} catch (error: any) {
// Only show restart message for connection errors, not HTTP errors
if (error.cause?.code === 'ECONNREFUSED' || error.name === 'TimeoutError' || error.message.includes('fetch failed')) {
throw new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue");
}
throw error;
}
console.log(createHookResponse('PostToolUse', true));
}
// Entry Point
let input = '';
stdin.on('data', (chunk) => input += chunk);
stdin.on('end', async () => {
const parsed = input ? JSON.parse(input) : undefined;
await saveHook(parsed);
});