112 lines
3.3 KiB
JavaScript
Executable File
112 lines
3.3 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
/**
|
|
* SessionEnd Hook
|
|
*
|
|
* Handles cleanup when Claude Code session terminates.
|
|
* Ensures .active-session file is removed and index is updated.
|
|
*
|
|
* Hook receives:
|
|
* - session_id: string
|
|
* - transcript_path: string
|
|
* - cwd: string
|
|
* - permission_mode: string
|
|
* - hook_event_name: "SessionEnd"
|
|
* - reason: "exit" | "clear" | "logout" | "prompt_input_exit" | "other"
|
|
*
|
|
* SAFETY: Includes graceful failure handling to avoid blocking Claude Code shutdown
|
|
* if plugin is uninstalled or dependencies are missing.
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
// Graceful failure wrapper - protect against plugin uninstallation
|
|
try {
|
|
// Check if critical dependencies exist (indicates plugin is installed)
|
|
const cliLibPath = path.join(__dirname, '../cli/lib');
|
|
if (!fs.existsSync(cliLibPath)) {
|
|
// Plugin likely uninstalled, exit silently
|
|
process.exit(0);
|
|
}
|
|
|
|
const IndexManager = require('../cli/lib/index-manager');
|
|
|
|
// Constants
|
|
const SESSIONS_DIR = '.claude/sessions';
|
|
const ACTIVE_SESSION_FILE = path.join(SESSIONS_DIR, '.active-session');
|
|
const indexManager = new IndexManager(SESSIONS_DIR);
|
|
|
|
try {
|
|
// Read input from stdin (provided by Claude Code)
|
|
const input = fs.readFileSync(0, 'utf8').trim();
|
|
|
|
// Parse the JSON input
|
|
let eventData;
|
|
try {
|
|
eventData = JSON.parse(input);
|
|
} catch (parseError) {
|
|
// If parsing fails, exit silently (no input provided)
|
|
process.exit(0);
|
|
}
|
|
|
|
// Clean up active session marker
|
|
let sessionName = null;
|
|
|
|
if (fs.existsSync(ACTIVE_SESSION_FILE)) {
|
|
try {
|
|
sessionName = fs.readFileSync(ACTIVE_SESSION_FILE, 'utf8').trim();
|
|
} catch (readError) {
|
|
// Continue even if read fails
|
|
}
|
|
|
|
// Delete the .active-session file
|
|
try {
|
|
fs.unlinkSync(ACTIVE_SESSION_FILE);
|
|
} catch (unlinkError) {
|
|
// File may already be deleted, continue
|
|
}
|
|
}
|
|
|
|
// Mark session as closed in .auto-capture-state (fast, atomic)
|
|
if (sessionName) {
|
|
const sessionDir = path.join(SESSIONS_DIR, sessionName);
|
|
const statePath = path.join(sessionDir, '.auto-capture-state');
|
|
|
|
if (fs.existsSync(statePath)) {
|
|
try {
|
|
let state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
|
state.session_status = 'closed';
|
|
state.session_closed = new Date().toISOString();
|
|
fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
} catch (stateError) {
|
|
// Silent fail - hook must be safe
|
|
// Session status update failed but continue with other cleanup
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the index.json to clear activeSession using IndexManager
|
|
// This uses proper locking and atomic writes to prevent corruption
|
|
try {
|
|
const index = indexManager.read({ skipValidation: true });
|
|
index.activeSession = null;
|
|
indexManager.write(index);
|
|
} catch (indexError) {
|
|
// Continue even if index update fails
|
|
// This prevents blocking the hook if index is temporarily locked
|
|
}
|
|
|
|
// Exit successfully
|
|
process.exit(0);
|
|
|
|
} catch (error) {
|
|
// Exit silently on any errors to avoid blocking Claude Code shutdown
|
|
process.exit(0);
|
|
}
|
|
} catch (error) {
|
|
// Outer catch: Handle plugin missing/uninstalled
|
|
// Exit silently to avoid blocking Claude Code
|
|
process.exit(0);
|
|
}
|