#!/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); }