Commit 3791202f7b9a

Vincent Demeester <vincent@sbr.pm>
2025-12-04 09:10:31
feat: Add session saving and history sync for Claude Code
- Enable automatic session-end prompts to capture work summaries - Sync Claude history across machines via Syncthing for backup - Provide /save-session command for manual session documentation Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Vincent Demeester <vincent@sbr.pm>
1 parent 02b3af6
Changed files (8)
dots
.claude
plugins
session-manager
tools
dots/.claude/plugins/session-manager/commands/save-session.md
@@ -0,0 +1,186 @@
+---
+description: Save a summary of the current Claude Code session to history
+---
+
+# Save Session
+
+Create a comprehensive session summary following the history-system format and save it to `~/.claude/history/sessions/`.
+
+## Important Guidelines
+
+- **DO NOT use echo or command-line tools to communicate** - output all text directly in your response
+- **Create a properly formatted session entry** following the history-system skill template
+- **Save to the correct location** with proper timestamp and filename format
+- **Be concise but comprehensive** - capture what matters
+
+---
+
+## Workflow
+
+### Step 1: Analyze the Session
+
+Review the conversation and identify:
+1. **Main accomplishments** - What was actually done?
+2. **Key decisions** - What choices were made and why?
+3. **Next steps** - What follow-up work is needed?
+4. **Related topics** - What areas of the codebase or system were touched?
+
+### Step 2: Generate Session Entry
+
+Create a session entry file with:
+
+**Filename format**: `YYYY-MM-DD-HHMMSS_SESSION_description.md`
+
+**Location**: `~/.claude/history/sessions/YYYY-MM/`
+
+**Template**:
+```markdown
+# Session: <Brief Description>
+
+**Date**: YYYY-MM-DD HH:MM
+**Duration**: <estimated duration if known>
+**Context**: <What prompted this work session>
+
+## What Was Accomplished
+- <Specific task 1>
+- <Specific task 2>
+- <Specific task 3>
+
+## Decisions Made
+- **<Decision>**: <Rationale>
+- **<Decision>**: <Rationale>
+
+## Technical Details
+<Any important technical information worth preserving>
+
+## Next Steps
+- [ ] <Follow-up task 1>
+- [ ] <Follow-up task 2>
+
+## Related Notes
+<If applicable, link to related denote notes>
+
+## Tags
+#session #<relevant-topic-tags>
+```
+
+### Step 3: Save the File
+
+Use the Write tool to save the session entry to:
+```
+~/.claude/history/sessions/YYYY-MM/YYYY-MM-DD-HHMMSS_SESSION_description.md
+```
+
+Replace:
+- `YYYY-MM` with current year-month
+- `YYYY-MM-DD-HHMMSS` with current timestamp
+- `description` with a short hyphenated description (e.g., `nixos-syncthing-config`)
+
+### Step 4: Confirm to User
+
+After saving, inform the user:
+```
+✅ Session saved to: ~/.claude/history/sessions/YYYY-MM/YYYY-MM-DD-HHMMSS_SESSION_description.md
+
+Summary:
+- X tasks completed
+- Y decisions documented
+- Z next steps identified
+```
+
+---
+
+## Examples
+
+### Example 1: Configuration Change Session
+```markdown
+# Session: Configure Syncthing for Claude History
+
+**Date**: 2025-12-04 07:30
+**Duration**: 30 minutes
+**Context**: Need to sync Claude Code history across machines for backup
+
+## What Was Accomplished
+- Added claude-history folder to globals.nix syncthingFolders
+- Generated syncthing folder ID (j5zdn-6kq4t)
+- Configured claude-history sync for kyushu and aomi
+- Path: /home/vincent/.claude/history
+
+## Decisions Made
+- **Syncthing ID format**: Used random 5-char strings (xxxxx-xxxxx) following existing pattern
+- **Machines**: Started with kyushu and aomi only, can add more later
+
+## Technical Details
+- Syncthing folder ID: j5zdn-6kq4t
+- Path: /home/vincent/.claude/history
+- Synced machines: kyushu (SBLRZF4-...), aomi (CN5P3MV-...)
+
+## Next Steps
+- [ ] Rebuild NixOS on kyushu to activate sync
+- [ ] Rebuild NixOS on aomi to activate sync
+- [ ] Verify syncthing picks up the new folder
+- [ ] Consider adding more machines later
+
+## Tags
+#session #nixos #syncthing #claude-code #homelab
+```
+
+### Example 2: Development Session
+```markdown
+# Session: Implement Session Saving Hooks
+
+**Date**: 2025-12-04 08:00
+**Duration**: 45 minutes
+**Context**: Automate session saving in Claude Code
+
+## What Was Accomplished
+- Created claude-hooks-save-session Go command
+- Created /save-session slash command plugin
+- Updated plugin structure in dots/.claude/plugins/session-manager
+- Documented workflow in command file
+
+## Decisions Made
+- **SessionEnd hook approach**: Prompt user instead of auto-save (user agency)
+- **Plugin structure**: Created dedicated session-manager plugin
+- **Format**: Follow existing history-system markdown template
+
+## Technical Details
+- Hook location: tools/claude-hooks/cmd/save-session/
+- Plugin location: dots/.claude/plugins/session-manager/
+- Follows existing claude-hooks Go patterns
+
+## Next Steps
+- [ ] Update default.nix to build save-session command
+- [ ] Add SessionEnd hook to settings.json
+- [ ] Test hook and slash command
+- [ ] Update setup-hooks.sh
+
+## Tags
+#session #development #claude-code #golang #hooks
+```
+
+---
+
+## Best Practices
+
+1. **Be specific** - Don't just say "worked on code", say what exactly was modified
+2. **Capture decisions** - Document WHY choices were made, not just what was done
+3. **Include context** - Future you needs to know what prompted this work
+4. **Link related work** - Reference denote notes, commits, or other sessions
+5. **Add tags** - Make sessions searchable by topic
+6. **Keep it real** - If nothing significant happened, it's okay to skip saving
+
+## When to Use
+
+- After completing significant work
+- When making important architectural decisions
+- When learning something worth preserving
+- At the end of productive sessions
+- When asked by SessionEnd hook
+
+## When to Skip
+
+- Very short sessions (<5 minutes)
+- Purely exploratory work with no findings
+- When no meaningful progress was made
+- When the work will be documented elsewhere
dots/.claude/plugins/session-manager/plugin.json
@@ -0,0 +1,12 @@
+{
+  "name": "session-manager",
+  "version": "1.0.0",
+  "description": "Save and manage Claude Code session summaries",
+  "author": "Vincent Demeester",
+  "commands": [
+    {
+      "name": "save-session",
+      "file": "commands/save-session.md"
+    }
+  ]
+}
dots/Makefile
@@ -31,6 +31,9 @@ claude-hooks : ~/.claude/hooks
 all += claude-settings
 claude-settings : ~/.claude/settings.json
 
+all += claude-plugins
+claude-plugins : ~/.claude/plugins/session-manager
+
 # Example: Override default rule to generate content instead of copying
 # Uncomment and customize this pattern for files that should be generated:
 #
tools/claude-hooks/cmd/save-session/main.go
@@ -0,0 +1,42 @@
+package main
+
+import (
+	"fmt"
+	"os"
+)
+
+// isSubagentSession checks if this is a subagent session
+func isSubagentSession() bool {
+	claudeProjectDir := os.Getenv("CLAUDE_PROJECT_DIR")
+	if len(claudeProjectDir) > 0 && (len(claudeProjectDir) < 2 || claudeProjectDir[len(claudeProjectDir)-2:] != "/.") {
+		return true
+	}
+	if os.Getenv("CLAUDE_AGENT_TYPE") != "" {
+		return true
+	}
+	return false
+}
+
+func main() {
+	// Check if this is a subagent session
+	if isSubagentSession() {
+		// Silent exit for subagent sessions
+		os.Exit(0)
+	}
+
+	// Output prompt for Claude to save the session
+	// This will be shown in the conversation
+	fmt.Println("")
+	fmt.Println("---")
+	fmt.Println("")
+	fmt.Println("**Session ending**. Would you like me to save a summary of this session to your history?")
+	fmt.Println("")
+	fmt.Println("I can create a session entry in `~/.claude/history/sessions/` documenting:")
+	fmt.Println("- What was accomplished")
+	fmt.Println("- Decisions made")
+	fmt.Println("- Next steps")
+	fmt.Println("- Related notes")
+	fmt.Println("")
+
+	os.Exit(0)
+}
tools/claude-hooks/default.nix
@@ -7,11 +7,12 @@ buildGoModule rec {
 
   vendorHash = "sha256-bdpAteulG3045jPdEpjcT4yGlnxLKDMlK7lk9WVRTKc=";
 
-  # Build all three binaries
+  # Build all binaries
   subPackages = [
     "cmd/capture-tool-output"
     "cmd/initialize-session"
     "cmd/validate-docs"
+    "cmd/save-session"
   ];
 
   # Rename binaries to have consistent prefix
@@ -19,10 +20,11 @@ buildGoModule rec {
     mv $out/bin/capture-tool-output $out/bin/claude-hooks-capture-tool-output
     mv $out/bin/initialize-session $out/bin/claude-hooks-initialize-session
     mv $out/bin/validate-docs $out/bin/claude-hooks-validate-docs
+    mv $out/bin/save-session $out/bin/claude-hooks-save-session
   '';
 
   meta = {
-    description = "Claude Code hooks for session initialization, tool output capture, and documentation validation";
+    description = "Claude Code hooks for session management, tool output capture, and documentation validation";
     mainProgram = "claude-hooks-capture-tool-output";
   };
 }
tools/claude-hooks/README.md
@@ -34,6 +34,16 @@ Migrated from TypeScript/Bun implementation in `dots/.claude/hooks/`.
 - Exit code 0 if valid, 1 if broken links found
 - Skips external URLs, anchors, and mailto links
 
+### 4. `claude-hooks-save-session`
+**Event**: SessionEnd
+
+**What it does**:
+- Prompts user to save session summary when ending a Claude Code session
+- Skips subagent sessions (silent)
+- Displays a message asking if session should be saved
+- Works with `/save-session` slash command for creating session entries
+- Saves to `~/.claude/history/sessions/YYYY-MM/YYYY-MM-DD-HHMMSS_SESSION_description.md`
+
 ## Architecture
 
 ```
@@ -41,6 +51,7 @@ tools/claude-hooks/
 ├── cmd/
 │   ├── capture-tool-output/main.go   # PostToolUse hook
 │   ├── initialize-session/main.go    # SessionStart hook
+│   ├── save-session/main.go          # SessionEnd hook
 │   └── validate-docs/main.go         # Documentation validator
 ├── internal/
 │   └── paths/paths.go                # Shared path utilities
@@ -100,11 +111,27 @@ tools/claude-hooks/
              }
            ]
          }
+       ],
+       "SessionEnd": [
+         {
+           "hooks": [
+             {
+               "type": "command",
+               "command": "claude-hooks-save-session"
+             }
+           ]
+         }
        ]
      }
    }
    ```
 
+5. **Enable the session-manager plugin** (for `/save-session` command):
+   ```bash
+   # Symlink the plugin if not already linked
+   ln -s ~/src/home/dots/.claude/plugins/session-manager ~/.claude/plugins/session-manager
+   ```
+
 ### Manual Build (without Nix)
 
 ```bash
@@ -113,6 +140,7 @@ cd tools/claude-hooks
 # Build all hooks
 go build -o bin/claude-hooks-capture-tool-output ./cmd/capture-tool-output
 go build -o bin/claude-hooks-initialize-session ./cmd/initialize-session
+go build -o bin/claude-hooks-save-session ./cmd/save-session
 go build -o bin/claude-hooks-validate-docs ./cmd/validate-docs
 
 # Copy to PATH
tools/claude-hooks/setup-hooks.sh
@@ -62,10 +62,23 @@ POST_TOOL_HOOKS=$(cat <<'EOF'
 EOF
 )
 
+SESSION_END_HOOKS=$(cat <<'EOF'
+{
+  "hooks": [
+    {
+      "type": "command",
+      "command": "claude-hooks-save-session"
+    }
+  ]
+}
+EOF
+)
+
 # Use jq to update the settings.json with proper JSON merging
 UPDATED_SETTINGS=$(echo "$CURRENT_SETTINGS" | jq --argjson sessionStart "$NEW_HOOKS" \
     --argjson postTool "$POST_TOOL_HOOKS" \
-    '.hooks.SessionStart = [$sessionStart] | .hooks.PostToolUse = [$postTool]')
+    --argjson sessionEnd "$SESSION_END_HOOKS" \
+    '.hooks.SessionStart = [$sessionStart] | .hooks.PostToolUse = [$postTool] | .hooks.SessionEnd = [$sessionEnd]')
 
 if [[ "$DRY_RUN" == true ]]; then
     echo "=== DRY RUN - No changes made ==="
@@ -81,6 +94,7 @@ else
     echo "Hook binaries configured:"
     echo "  - SessionStart: claude-hooks-initialize-session"
     echo "  - PostToolUse: claude-hooks-capture-tool-output"
+    echo "  - SessionEnd: claude-hooks-save-session"
     echo ""
     echo "Make sure claude-hooks is installed:"
     echo "  nix profile install .#claude-hooks"
globals.nix
@@ -35,6 +35,10 @@ _: {
       id = "kcyrf-mugzt";
       path = "/home/vincent/desktop/music";
     };
+    claude-history = {
+      id = "j5zdn-6kq4t";
+      path = "/home/vincent/.claude/history";
+    };
   };
   net = {
     dns = {
@@ -215,6 +219,7 @@ _: {
           sync = { };
           screenshots = { };
           wallpapers = { };
+          claude-history = { };
           # TODO: implement paused or filter theses
           # photos = {
           #   type = "receiveonly";
@@ -251,6 +256,7 @@ _: {
           sync = { };
           screenshots = { };
           wallpapers = { };
+          claude-history = { };
           # photos = {
           #   type = "receiveonly";
           #   paused = true; # TODO: implement this, start as paused