18 KiB
Session End Hook
This hook runs automatically when a Claude Code session ends, handling session note updates, threshold-based compaction, and archival.
Overview
Purpose: Finalize session notes, compact if threshold exceeded, and archive completed sessions
Compaction Threshold: 500 lines OR 3 days old (whichever comes first)
Archival Strategy: Never delete, always archive to preserve history
Implementation
1. Load Current Session Note
-
Detect the project context (same logic as session-start hook)
-
Determine the session note path:
- Get current date in YYYY-MM-DD format
- Infer session topic from work log or user input
- Construct path:
claude/projects/{projectContext}/sessions/{date}-{topic}.md
-
Attempt to load the session note using MCP vault tool:
mcp__obsidian-vault__vault({ action: "read", path: `claude/projects/${projectContext}/sessions/${date}-${topic}.md`, returnFullFile: true }) -
Handle different cases:
If session note exists:
- Proceed to finalize the session (next steps)
If FileNotFoundError:
- No session note was created this session
- Display: "No session note created this session."
- Exit normally (no-op)
If MCPUnavailableError:
- Display: "MCP unavailable. Cannot finalize session."
- See error recovery section below for options
2. Final Session Note Update
-
Append session closing entry to the "## Work Log" section:
- Add timestamp with current time
- Add heading: "Session End"
- Add message: "Session completed. Work finalized."
-
Update frontmatter:
- Set updated to current date (YYYY-MM-DD)
- Set claude_last_accessed to current date
-
Write the final update using MCP vault tool:
mcp__obsidian-vault__vault({ action: "update", path: sessionPath, content: updatedContentWithFrontmatter })Or use edit tool for efficient append:
// Append closing entry mcp__obsidian-vault__edit({ action: "append", path: sessionPath, content: `\n### ${timestamp} - Session End\n\nSession completed. Work finalized.` }); // Update frontmatter mcp__obsidian-vault__edit({ action: "patch", path: sessionPath, targetType: "frontmatter", target: "updated", operation: "replace", content: new Date().toISOString().split('T')[0] }); -
Proceed to check compaction threshold (next step)
3. Check Compaction Threshold
Threshold Logic:
-
Count lines in the session note by splitting content on newlines
-
Calculate age in days:
- Parse created date from frontmatter
- Compare with current date
- Calculate difference in days
-
Check if either threshold is exceeded:
- Line threshold: >= 500 lines
- Age threshold: >= 3 days
-
If either threshold is exceeded:
- Display reason: "Session note {exceeds 500 lines / exceeds 3 days old}. Triggering compaction..."
- Trigger compaction (next step)
-
If both thresholds are not exceeded:
- Display: "Session note below threshold ({lineCount} lines, {ageInDays} days old). Keeping active."
- Mark session status as "active" in frontmatter
- Update the note using MCP edit tool:
mcp__obsidian-vault__edit({ action: "patch", path: sessionPath, targetType: "frontmatter", target: "status", operation: "replace", content: "active" })
Threshold Rules:
- 500 lines: Session note becomes too large to navigate efficiently
- 3 days old: Knowledge should be consolidated into entities for long-term retention
- Whichever comes first: More aggressive compaction ensures timely knowledge extraction
4. Compact Session Note (I1: Simplified Algorithm)
Compaction Process:
-
Display: "Compacting session note into entity notes..."
-
Parse the session note to identify extractable knowledge:
- Use high-level heuristics rather than detailed algorithms
- Look for distinct concepts and decisions
-
For each identified piece of knowledge:
- Update or create the appropriate entity note
- See step 5 for entity update logic
-
Archive the session note (step 6)
-
Display: "Compaction complete. Session archived."
Knowledge Extraction (Simplified):
Parse the session note to identify key extractable knowledge:
-
Extract decisions from "## Decisions Made" section
- Each decision entry becomes an update to an entity note
- Infer which entity based on context and related entities
-
Extract gotchas from "## Work Log" section
- Look for debugging work: "fixed bug", "troubleshot", "resolved issue"
- Each gotcha updates the troubleshooting section of an entity
-
Extract references from wikilinks throughout the note
- All wikilinked entities get updated with reference to this session
Extraction Categories:
- Architectural decisions → Update entity notes with decision and rationale
- Bug fixes / gotchas → Update entity troubleshooting section
- New patterns → Create topic notes or new entities
- User preferences → Update user preference entity
Note: Keep extraction logic flexible. Claude should use judgment to identify valuable knowledge rather than following rigid algorithms.
5. Update Entity Notes
Use the patch operations and conflict detection patterns from the managing-working-memory skill.
For each extracted piece of knowledge:
-
Determine the target entity note path:
claude/projects/{projectContext}/entities/{entityName}.md
-
Attempt to load the existing entity using MCP vault tool:
const loadResult = await mcp__obsidian-vault__vault({ action: "read", path: `claude/projects/${projectContext}/entities/${entityName}.md`, returnFullFile: true }); -
Store the loaded content for conflict detection
-
Apply patch operation based on knowledge type (using efficient edit tool):
- Decision: Append to "## Key Decisions" section
mcp__obsidian-vault__edit({ action: "patch", path: entityPath, targetType: "heading", target: "Key Decisions", operation: "append", content: `\n- ${date}: ${decisionText}` }) - Gotcha: Append to "## Gotchas & Troubleshooting" section
- Reference: Append to "## Recent Changes" section
- Decision: Append to "## Key Decisions" section
-
Before writing, reload the entity and check for conflicts:
- Compare loaded content with current content
- If conflict detected, trigger conflict resolution
- If no conflict, proceed with update
-
Update frontmatter timestamps:
mcp__obsidian-vault__edit({ action: "patch", path: entityPath, targetType: "frontmatter", target: "updated", operation: "replace", content: new Date().toISOString().split('T')[0] }) -
Display: "Updated {entityName} with knowledge from session."
-
Handle errors:
- If FileNotFoundError: Create new entity from session knowledge
- If other error: Propagate
Conflict Handling: Uses same timestamp-based detection as defined in managing-working-memory.md
6. Archive Session Note
Archival Process:
-
Determine archive path:
- Format:
claude/projects/{projectContext}/archive/sessions/{filename} - Use same filename as original session note
- Format:
-
Update session note metadata:
- Set status to "archived" in frontmatter
- Set updated to current date
- Add archived_date field with current date
- Add archived_reason: "Compaction threshold exceeded"
-
Ensure archive directory exists (create if needed)
-
Copy session note to archive location using MCP vault tool:
mcp__obsidian-vault__vault({ action: "create", path: `claude/projects/${projectContext}/archive/sessions/${filename}`, content: sessionContentWithArchivedFrontmatter }) -
Verify archive was successful by attempting to read the archived note
-
If verification successful:
- Display: "Session archived to: {archivePath}"
- Leave original note with archived status (default behavior)
- User can manually delete original if desired
-
If verification fails:
- Throw error: "Archive verification failed. Session not moved."
- Keep original note intact
Archive Location: ~/.claude-memory/claude/projects/{project}/archive/sessions/{date}-{topic}.md
Preservation Strategy:
- Never delete notes (always archive)
- Archived notes retain full content and frontmatter
- Status marked as "archived" in frontmatter
- Original note can optionally be removed (default: keep with archived status)
7. Create Backlinks
Link archived session to updated entities:
For each entity that was updated during compaction:
-
Determine the entity path:
claude/projects/{projectContext}/entities/{entityName}.md
-
Load the entity using MCP vault tool:
mcp__obsidian-vault__vault({ action: "read", path: `claude/projects/${projectContext}/entities/${entityName}.md`, returnFullFile: true }) -
Create a backlink to the archived session:
- Format:
- [[{session-filename}]] - Archived session (compacted) - Extract filename from session path (without .md extension)
- Format:
-
Append the backlink to the "## References" section of the entity
-
Update the entity frontmatter:
- Set updated to current date
-
Write the updated entity using MCP edit tool:
// Append backlink to References section mcp__obsidian-vault__edit({ action: "patch", path: entityPath, targetType: "heading", target: "References", operation: "append", content: `\n- [[${sessionFilename}]] - Archived session (compacted)` }); // Update frontmatter mcp__obsidian-vault__edit({ action: "patch", path: entityPath, targetType: "frontmatter", target: "updated", operation: "replace", content: new Date().toISOString().split('T')[0] });
This creates bidirectional links between archived sessions and the entities they contributed to.
Error Handling and Recovery (I2)
MCP Unavailable
When MCP server is unavailable at session end:
-
Detect the MCPUnavailableError
-
Display message listing pending actions:
Obsidian MCP unavailable. Cannot finalize session. Pending actions: - Session note update - Compaction check - Archival (if threshold exceeded) -
Decision Point - Ask User:
- Option A: Exit without finalization
- Session ends without memory updates
- User must manually run session cleanup later
- Provide manual steps:
- Check session note line count and age
- If threshold exceeded, compact into entity notes
- Archive to archive/sessions/ directory
- Option B: Wait and retry
- Pause session end
- Wait for user to fix MCP connection
- Retry finalization once fixed
- Option C: Export pending work
- Create local file with session summary
- User can manually update memory later
- Provide file path and instructions
- Option A: Exit without finalization
-
Display troubleshooting reference: "See docs/setup-guide.md for MCP troubleshooting."
Compaction Failure
When compaction fails for any reason:
-
Catch the error and log it
-
Decision Point - Do NOT archive if compaction failed
- Leave session note as-is
- Session remains active until compaction succeeds
-
Display error message with manual action steps:
Session compaction failed. Session note preserved. Manual action required: 1. Review session note: {sessionPath} 2. Extract knowledge into entity notes manually 3. Archive session when complete Error: {error.message} -
Mark session with error status:
- Set frontmatter.status = "compaction_failed"
- Add frontmatter.error = error message
- Update note using MCP edit tool:
mcp__obsidian-vault__edit({ action: "patch", path: sessionPath, targetType: "frontmatter", target: "status", operation: "replace", content: "compaction_failed" }); mcp__obsidian-vault__edit({ action: "patch", path: sessionPath, targetType: "frontmatter", target: "error", operation: "replace", content: error.message });
-
Recovery options:
- User can manually compact and archive later
- On next session, user can trigger manual compaction
- Session will remain in active state with error marker
Archive Verification Failed
When archive verification fails:
-
Detect that archived note cannot be read
-
Display error: "Archive verification failed. Session not moved."
-
Decision Point - Abort archival, keep session active:
- Do NOT remove original session note
- Revert session status to "active"
- Add frontmatter.archive_error = "Verification failed"
-
Update the original session note using MCP edit tool:
mcp__obsidian-vault__edit({ action: "patch", path: sessionPath, targetType: "frontmatter", target: "status", operation: "replace", content: "active" }); mcp__obsidian-vault__edit({ action: "patch", path: sessionPath, targetType: "frontmatter", target: "archive_error", operation: "replace", content: "Verification failed" }); -
Throw error to halt session end process:
- "Archive failed - session preserved at original location"
-
Recovery options:
- User can investigate why archive failed
- Retry archival on next session end
- Manually move to archive directory if needed
Integration with Managing Working Memory Skill
Session End Checklist: Uses TodoWrite checklist from skills/managing-working-memory.md:
- [ ] Final session note update with closing context
- [ ] Check session note size (line count)
- [ ] Check session note age (created date)
- [ ] If threshold met (500 lines OR 3 days old):
- [ ] Parse session note for extractable knowledge
- [ ] Identify entities to create or update
- [ ] Apply patches to entity notes
- [ ] Create new entities if needed
- [ ] Archive session note to archive/sessions/
- [ ] Verify archive successful
- [ ] If threshold not met:
- [ ] Mark session note status: active
- [ ] Note next checkpoint time
- [ ] Review cross-project recall tracking
- [ ] Check for promotion threshold (3 recalls)
- [ ] Confirm all writes successful
Compaction Triggers: Aligns with triggers defined in managing-working-memory skill
Performance Considerations
Compaction Performance
To avoid session-end delays, optimize compaction performance:
-
Track timing:
- Record start time before compaction begins
- Record end time after archival completes
- Calculate total duration in milliseconds
-
Optimize operations:
- Parse session (fast - single pass)
- Batch entity updates in parallel using Promise.all
- Archive in single operation (not per-entity)
-
Display timing:
- Show "Compaction completed in {duration}ms"
-
Performance target: <5 seconds for compaction (even with 10+ entity updates)
Async Session End
Don't block session exit if MCP operations are slow:
-
Display: "Finalizing session..."
-
Run session-end hook asynchronously:
- Execute all finalization steps
- Allow user to exit if it takes too long
-
Handle completion:
If successful:
- Display: "Session finalized successfully."
If error:
- Display: "Session end failed: {error.message}"
- Display: "Session state preserved. Manual cleanup may be needed."
- Allow user to exit anyway (don't block)
Testing
Manual Testing
- Create session note with >500 lines
- Run session-end hook
- Verify compaction triggered
- Check entity notes updated
- Verify session archived at correct path
- Confirm original session marked as "archived"
Edge Cases
- Session note <500 lines and <3 days old: No compaction, marked active
- MCP unavailable: Graceful error, pending actions logged
- Compaction failure: Session preserved, error status set
- No session note: No-op, clean exit
- Archive directory missing: Create directory automatically
Threshold Testing
Test Case 1: Line Threshold
- Session with 600 lines, 1 day old
- Expected: Compact (exceeds 500 lines)
Test Case 2: Age Threshold
- Session with 300 lines, 4 days old
- Expected: Compact (exceeds 3 days)
Test Case 3: Both Thresholds
- Session with 600 lines, 4 days old
- Expected: Compact (exceeds both thresholds)
Test Case 4: Neither Threshold
- Session with 200 lines, 1 day old
- Expected: Mark active, no compaction
Example Session End Flow
$ exit
[Session End Hook Executing...]
Finalizing session for project: tabula-scripta
Session note: 2025-11-18-session-hooks.md
Status: 487 lines, 1 day old (below threshold)
Marking session as active (no compaction needed).
Session finalized successfully.
---
Goodbye!
With Compaction:
$ exit
[Session End Hook Executing...]
Finalizing session for project: tabula-scripta
Session note: 2025-11-15-memory-design.md
Status: 623 lines, 1 day old (exceeds 500 line threshold)
Triggering compaction...
Extracting knowledge:
- 3 architectural decisions → [[Memory System Architecture]]
- 2 debugging insights → [[Conflict Detection]]
- 5 entity references → Updated backlinks
Compaction complete. Session archived to:
archive/sessions/2025-11-15-memory-design.md
Session finalized successfully.
---
Goodbye!
Summary
This session-end hook finalizes working memory at the end of each session:
- Loads current session note and applies final update
- Checks compaction threshold (500 lines OR 3 days old)
- Compacts if threshold exceeded by extracting knowledge into entity notes
- Archives session note to preserve history (never deletes)
- Updates entity notes with decisions, gotchas, and patterns
- Creates backlinks from entities to archived session
- Handles errors gracefully (MCP unavailable, compaction failures, etc.)
Users experience automatic knowledge consolidation without manual effort, while maintaining full session history in archived notes for future reference.