refactor: streamline input validation and error handling in hooks
This commit is contained in:
+11
-65
@@ -15,54 +15,21 @@ export interface SessionStartInput {
|
|||||||
* Shows user what happened in recent sessions
|
* Shows user what happened in recent sessions
|
||||||
*/
|
*/
|
||||||
export function contextHook(input?: SessionStartInput): void {
|
export function contextHook(input?: SessionStartInput): void {
|
||||||
|
if (!input) {
|
||||||
|
throw new Error('contextHook requires input');
|
||||||
|
}
|
||||||
|
|
||||||
|
const project = input.cwd ? path.basename(input.cwd) : path.basename(path.dirname(input.transcript_path));
|
||||||
|
const db = new HooksDatabase();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Log hook invocation
|
|
||||||
console.error('[claude-mem context] Hook fired with input:', JSON.stringify({
|
|
||||||
session_id: input?.session_id,
|
|
||||||
transcript_path: input?.transcript_path,
|
|
||||||
hook_event_name: input?.hook_event_name,
|
|
||||||
source: input?.source,
|
|
||||||
has_input: !!input
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Handle standalone execution (no input provided)
|
|
||||||
if (!input) {
|
|
||||||
console.error('[claude-mem context] No input provided - exiting (standalone mode)');
|
|
||||||
console.log('No input provided - this script is designed to run as a Claude Code SessionStart hook');
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract project from cwd (same as new-hook to ensure consistency)
|
|
||||||
// If cwd is not available, fall back to extracting from transcript_path
|
|
||||||
const project = input.cwd ? path.basename(input.cwd) : path.basename(path.dirname(input.transcript_path));
|
|
||||||
console.error('[claude-mem context] Extracted project name:', project, 'from', input.cwd ? 'cwd' : 'transcript_path');
|
|
||||||
|
|
||||||
// Get recent summaries
|
|
||||||
console.error('[claude-mem context] Querying database for recent summaries...');
|
|
||||||
const db = new HooksDatabase();
|
|
||||||
const summaries = db.getRecentSummaries(project, 5);
|
const summaries = db.getRecentSummaries(project, 5);
|
||||||
db.close();
|
|
||||||
|
|
||||||
console.error('[claude-mem context] Database query complete - found', summaries.length, 'summaries');
|
|
||||||
|
|
||||||
// Log preview of each summary found
|
|
||||||
if (summaries.length > 0) {
|
|
||||||
console.error('[claude-mem context] Summary previews:');
|
|
||||||
summaries.forEach((summary, idx) => {
|
|
||||||
const preview = summary.request?.substring(0, 100) || summary.completed?.substring(0, 100) || '(no content)';
|
|
||||||
console.error(` [${idx + 1}]`, preview + (preview.length >= 100 ? '...' : ''));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no summaries, provide helpful message
|
|
||||||
if (summaries.length === 0) {
|
if (summaries.length === 0) {
|
||||||
console.error('[claude-mem context] No summaries found - outputting empty context message');
|
|
||||||
console.log('# Recent Session Context\n\nNo previous sessions found for this project yet.');
|
console.log('# Recent Session Context\n\nNo previous sessions found for this project yet.');
|
||||||
process.exit(0);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format output for Claude
|
|
||||||
console.error('[claude-mem context] Building markdown context from summaries...');
|
|
||||||
const output: string[] = [];
|
const output: string[] = [];
|
||||||
output.push('# Recent Session Context');
|
output.push('# Recent Session Context');
|
||||||
output.push('');
|
output.push('');
|
||||||
@@ -90,7 +57,6 @@ export function contextHook(input?: SessionStartInput): void {
|
|||||||
output.push(`**Next Steps:** ${summary.next_steps}`);
|
output.push(`**Next Steps:** ${summary.next_steps}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show files that were read during the session
|
|
||||||
if (summary.files_read) {
|
if (summary.files_read) {
|
||||||
try {
|
try {
|
||||||
const files = JSON.parse(summary.files_read);
|
const files = JSON.parse(summary.files_read);
|
||||||
@@ -98,14 +64,12 @@ export function contextHook(input?: SessionStartInput): void {
|
|||||||
output.push(`**Files Read:** ${files.join(', ')}`);
|
output.push(`**Files Read:** ${files.join(', ')}`);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Backwards compatibility: if not valid JSON, show as text
|
|
||||||
if (summary.files_read.trim()) {
|
if (summary.files_read.trim()) {
|
||||||
output.push(`**Files Read:** ${summary.files_read}`);
|
output.push(`**Files Read:** ${summary.files_read}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show files that were edited/written during the session
|
|
||||||
if (summary.files_edited) {
|
if (summary.files_edited) {
|
||||||
try {
|
try {
|
||||||
const files = JSON.parse(summary.files_edited);
|
const files = JSON.parse(summary.files_edited);
|
||||||
@@ -113,7 +77,6 @@ export function contextHook(input?: SessionStartInput): void {
|
|||||||
output.push(`**Files Edited:** ${files.join(', ')}`);
|
output.push(`**Files Edited:** ${files.join(', ')}`);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Backwards compatibility: if not valid JSON, show as text
|
|
||||||
if (summary.files_edited.trim()) {
|
if (summary.files_edited.trim()) {
|
||||||
output.push(`**Files Edited:** ${summary.files_edited}`);
|
output.push(`**Files Edited:** ${summary.files_edited}`);
|
||||||
}
|
}
|
||||||
@@ -124,25 +87,8 @@ export function contextHook(input?: SessionStartInput): void {
|
|||||||
output.push('');
|
output.push('');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log details about the markdown output
|
console.log(output.join('\n'));
|
||||||
const markdownOutput = output.join('\n');
|
} finally {
|
||||||
console.error('[claude-mem context] Markdown built successfully');
|
db.close();
|
||||||
console.error('[claude-mem context] Output length:', markdownOutput.length, 'characters,', output.length, 'lines');
|
|
||||||
console.error('[claude-mem context] Output preview (first 200 chars):', markdownOutput.substring(0, 200) + '...');
|
|
||||||
console.error('[claude-mem context] Outputting context to stdout for Claude Code injection');
|
|
||||||
|
|
||||||
// Output to stdout for Claude Code to inject
|
|
||||||
console.log(markdownOutput);
|
|
||||||
|
|
||||||
console.error('[claude-mem context] Context hook completed successfully');
|
|
||||||
process.exit(0);
|
|
||||||
|
|
||||||
} catch (error: any) {
|
|
||||||
// On error, exit silently - don't block Claude Code
|
|
||||||
console.error('[claude-mem context] ERROR occurred during context hook execution');
|
|
||||||
console.error('[claude-mem context] Error message:', error.message);
|
|
||||||
console.error('[claude-mem context] Error stack:', error.stack);
|
|
||||||
console.error('[claude-mem context] Exiting gracefully to avoid blocking Claude Code');
|
|
||||||
process.exit(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-35
@@ -14,47 +14,30 @@ export interface UserPromptSubmitInput {
|
|||||||
* Initializes SDK memory session in background
|
* Initializes SDK memory session in background
|
||||||
*/
|
*/
|
||||||
export function newHook(input?: UserPromptSubmitInput): void {
|
export function newHook(input?: UserPromptSubmitInput): void {
|
||||||
|
if (!input) {
|
||||||
|
throw new Error('newHook requires input');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { session_id, cwd, prompt } = input;
|
||||||
|
const project = path.basename(cwd);
|
||||||
|
const db = new HooksDatabase();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Handle standalone execution (no input provided)
|
|
||||||
if (!input) {
|
|
||||||
console.log('No input provided - this script is designed to run as a Claude Code UserPromptSubmit hook');
|
|
||||||
console.log('\nExpected input format:');
|
|
||||||
console.log(JSON.stringify({
|
|
||||||
session_id: "string",
|
|
||||||
cwd: "string",
|
|
||||||
prompt: "string"
|
|
||||||
}, null, 2));
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { session_id, cwd, prompt } = input;
|
|
||||||
|
|
||||||
// Extract project from cwd
|
|
||||||
const project = path.basename(cwd);
|
|
||||||
|
|
||||||
// Check if session already exists
|
|
||||||
const db = new HooksDatabase();
|
|
||||||
const existing = db.findActiveSDKSession(session_id);
|
const existing = db.findActiveSDKSession(session_id);
|
||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
// Session already initialized, just continue
|
|
||||||
db.close();
|
|
||||||
console.log('{"continue": true, "suppressOutput": true}');
|
console.log('{"continue": true, "suppressOutput": true}');
|
||||||
process.exit(0);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create SDK session record
|
|
||||||
const sessionId = db.createSDKSession(session_id, project, prompt);
|
const sessionId = db.createSDKSession(session_id, project, prompt);
|
||||||
db.close();
|
|
||||||
|
|
||||||
// Start SDK worker in background as detached process
|
|
||||||
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT;
|
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT;
|
||||||
|
|
||||||
if (!pluginRoot) {
|
if (!pluginRoot) {
|
||||||
throw new Error('CLAUDE_PLUGIN_ROOT not set - claude-mem must be installed as a Claude Code plugin');
|
throw new Error('CLAUDE_PLUGIN_ROOT not set');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use bundled worker
|
|
||||||
const workerPath = path.join(pluginRoot, 'scripts', 'hooks', 'worker.js');
|
const workerPath = path.join(pluginRoot, 'scripts', 'hooks', 'worker.js');
|
||||||
const child = spawn('bun', [workerPath, sessionId.toString()], {
|
const child = spawn('bun', [workerPath, sessionId.toString()], {
|
||||||
detached: true,
|
detached: true,
|
||||||
@@ -63,14 +46,8 @@ export function newHook(input?: UserPromptSubmitInput): void {
|
|||||||
|
|
||||||
child.unref();
|
child.unref();
|
||||||
|
|
||||||
// Output hook response
|
|
||||||
console.log('{"continue": true, "suppressOutput": true}');
|
console.log('{"continue": true, "suppressOutput": true}');
|
||||||
process.exit(0);
|
} finally {
|
||||||
|
db.close();
|
||||||
} catch (error: any) {
|
|
||||||
// On error, don't block Claude Code
|
|
||||||
console.error(`[claude-mem new error: ${error.message}]`);
|
|
||||||
console.log('{"continue": true, "suppressOutput": true}');
|
|
||||||
process.exit(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-67
@@ -22,72 +22,40 @@ const SKIP_TOOLS = new Set([
|
|||||||
* Sends tool observations to worker via Unix socket
|
* Sends tool observations to worker via Unix socket
|
||||||
*/
|
*/
|
||||||
export function saveHook(input?: PostToolUseInput): void {
|
export function saveHook(input?: PostToolUseInput): void {
|
||||||
try {
|
if (!input) {
|
||||||
// Handle standalone execution (no input provided)
|
throw new Error('saveHook requires input');
|
||||||
if (!input) {
|
|
||||||
console.log('No input provided - this script is designed to run as a Claude Code PostToolUse hook');
|
|
||||||
console.log('\nExpected input format:');
|
|
||||||
console.log(JSON.stringify({
|
|
||||||
session_id: "string",
|
|
||||||
cwd: "string",
|
|
||||||
tool_name: "string",
|
|
||||||
tool_input: {},
|
|
||||||
tool_output: {}
|
|
||||||
}, null, 2));
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { session_id, tool_name, tool_input, tool_output } = input;
|
|
||||||
|
|
||||||
// Skip certain tools
|
|
||||||
if (SKIP_TOOLS.has(tool_name)) {
|
|
||||||
console.log('{"continue": true, "suppressOutput": true}');
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find active SDK session
|
|
||||||
const db = new HooksDatabase();
|
|
||||||
const session = db.findActiveSDKSession(session_id);
|
|
||||||
db.close();
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
// No active session yet - this can happen if UserPromptSubmit hasn't run
|
|
||||||
// Just exit silently
|
|
||||||
console.log('{"continue": true, "suppressOutput": true}');
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get socket path
|
|
||||||
const socketPath = getWorkerSocketPath(session.id);
|
|
||||||
|
|
||||||
// Send observation via Unix socket
|
|
||||||
const message = {
|
|
||||||
type: 'observation',
|
|
||||||
tool_name,
|
|
||||||
tool_input: JSON.stringify(tool_input),
|
|
||||||
tool_output: JSON.stringify(tool_output)
|
|
||||||
};
|
|
||||||
|
|
||||||
const client = net.connect(socketPath, () => {
|
|
||||||
client.write(JSON.stringify(message) + '\n');
|
|
||||||
client.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on('error', (err) => {
|
|
||||||
// Socket not available - worker may have crashed or not started
|
|
||||||
console.error(`[claude-mem save] Socket error: ${err.message}`);
|
|
||||||
// Continue anyway, don't block Claude
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on('close', () => {
|
|
||||||
console.log('{"continue": true, "suppressOutput": true}');
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error: any) {
|
|
||||||
// On error, don't block Claude Code
|
|
||||||
console.error(`[claude-mem save error: ${error.message}]`);
|
|
||||||
console.log('{"continue": true, "suppressOutput": true}');
|
|
||||||
process.exit(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { session_id, tool_name, tool_input, tool_output } = input;
|
||||||
|
|
||||||
|
if (SKIP_TOOLS.has(tool_name)) {
|
||||||
|
console.log('{"continue": true, "suppressOutput": true}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = new HooksDatabase();
|
||||||
|
const session = db.findActiveSDKSession(session_id);
|
||||||
|
db.close();
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
console.log('{"continue": true, "suppressOutput": true}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const socketPath = getWorkerSocketPath(session.id);
|
||||||
|
const message = {
|
||||||
|
type: 'observation',
|
||||||
|
tool_name,
|
||||||
|
tool_input: JSON.stringify(tool_input),
|
||||||
|
tool_output: JSON.stringify(tool_output)
|
||||||
|
};
|
||||||
|
|
||||||
|
const client = net.connect(socketPath, () => {
|
||||||
|
client.write(JSON.stringify(message) + '\n');
|
||||||
|
client.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('close', () => {
|
||||||
|
console.log('{"continue": true, "suppressOutput": true}');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-82
@@ -13,87 +13,31 @@ export interface StopInput {
|
|||||||
* Sends FINALIZE message to worker via Unix socket
|
* Sends FINALIZE message to worker via Unix socket
|
||||||
*/
|
*/
|
||||||
export function summaryHook(input?: StopInput): void {
|
export function summaryHook(input?: StopInput): void {
|
||||||
try {
|
if (!input) {
|
||||||
// Log hook entry point
|
throw new Error('summaryHook requires input');
|
||||||
console.error('[claude-mem summary] Hook fired', {
|
|
||||||
input: input ? { session_id: input.session_id, cwd: input.cwd } : null
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle standalone execution (no input provided)
|
|
||||||
if (!input) {
|
|
||||||
console.log('No input provided - this script is designed to run as a Claude Code Stop hook');
|
|
||||||
console.log('\nExpected input format:');
|
|
||||||
console.log(JSON.stringify({
|
|
||||||
session_id: "string",
|
|
||||||
cwd: "string"
|
|
||||||
}, null, 2));
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { session_id } = input;
|
|
||||||
console.error('[claude-mem summary] Searching for active SDK session', { session_id });
|
|
||||||
|
|
||||||
// Find active SDK session
|
|
||||||
const db = new HooksDatabase();
|
|
||||||
const session = db.findActiveSDKSession(session_id);
|
|
||||||
db.close();
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
// No active session - nothing to finalize
|
|
||||||
console.error('[claude-mem summary] No active SDK session found', { session_id });
|
|
||||||
console.log('{"continue": true, "suppressOutput": true}');
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.error('[claude-mem summary] Active SDK session found', {
|
|
||||||
session_id: session.id,
|
|
||||||
collection_name: session.collection_name,
|
|
||||||
worker_pid: session.worker_pid
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get socket path
|
|
||||||
const socketPath = getWorkerSocketPath(session.id);
|
|
||||||
|
|
||||||
// Send FINALIZE message via Unix socket
|
|
||||||
const message = {
|
|
||||||
type: 'finalize'
|
|
||||||
};
|
|
||||||
|
|
||||||
console.error('[claude-mem summary] Attempting to send FINALIZE message to worker socket', {
|
|
||||||
socketPath,
|
|
||||||
message
|
|
||||||
});
|
|
||||||
|
|
||||||
const client = net.connect(socketPath, () => {
|
|
||||||
console.error('[claude-mem summary] Socket connection established, sending message');
|
|
||||||
client.write(JSON.stringify(message) + '\n');
|
|
||||||
client.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on('error', (err) => {
|
|
||||||
// Socket not available - worker may have already finished or crashed
|
|
||||||
console.error('[claude-mem summary] Socket error occurred', {
|
|
||||||
error: err.message,
|
|
||||||
code: (err as any).code,
|
|
||||||
socketPath
|
|
||||||
});
|
|
||||||
// Continue anyway, don't block Claude
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on('close', () => {
|
|
||||||
console.error('[claude-mem summary] Socket connection closed successfully');
|
|
||||||
console.log('{"continue": true, "suppressOutput": true}');
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error: any) {
|
|
||||||
// On error, don't block Claude Code
|
|
||||||
console.error('[claude-mem summary] Unexpected error in hook', {
|
|
||||||
error: error.message,
|
|
||||||
stack: error.stack,
|
|
||||||
name: error.name
|
|
||||||
});
|
|
||||||
console.log('{"continue": true, "suppressOutput": true}');
|
|
||||||
process.exit(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { session_id } = input;
|
||||||
|
const db = new HooksDatabase();
|
||||||
|
const session = db.findActiveSDKSession(session_id);
|
||||||
|
db.close();
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
console.log('{"continue": true, "suppressOutput": true}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const socketPath = getWorkerSocketPath(session.id);
|
||||||
|
const message = {
|
||||||
|
type: 'finalize'
|
||||||
|
};
|
||||||
|
|
||||||
|
const client = net.connect(socketPath, () => {
|
||||||
|
client.write(JSON.stringify(message) + '\n');
|
||||||
|
client.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('close', () => {
|
||||||
|
console.log('{"continue": true, "suppressOutput": true}');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user