Add session-logger hook for durable session notes#14
Open
Conversation
added 4 commits
April 18, 2026 10:51
Single script that handles SessionStart, PostToolUse, and Stop events to write a human-readable markdown log of every Claude Code session: timestamps, cwd, git repo/branch, files touched, and bash commands run. - One install, three registrations (SessionStart + PostToolUse + Stop). - Filename: YYYY-MM-DD_HHmm_<sid-short>.md — sortable + greppable. - Session resume: appends a "Resumed at" marker instead of overwriting. - Bash commands truncated to the first line (200 chars) to avoid logging multi-line command bodies that may contain secrets. - Configurable log directory via top-of-file SESSION_LOG_DIR constant (matches existing SAFETY_LEVEL pattern) or CC_SESSION_LOG_DIR env var. Point it at an Obsidian vault or iCloud folder for cross-device sync. Tests: 18 new, all passing (unit + stdin/stdout integration).
Stop fires at the end of every Claude turn (many times per session), which would write duplicate "Session End" sections and repeatedly overwrite the ended: timestamp. SessionEnd fires once when the session actually ends. - Rename handleStop → handleSessionEnd. - Drop Stop from the event dispatch (add an explanatory comment). - Idempotent finalize: skip if the "ended:" frontmatter line is already filled, so accidentally registering against Stop is harmless. - Update README + install snippet to use SessionEnd. - Tests: add idempotency test and a regression test confirming Stop events do not finalize the note. Bump test badge to 282.
Update the install-example comment in session-logger.js to register PostToolUse with "async": true. PostToolUse fires after every tool call, so making it non-blocking keeps the hot path off Claude's critical path. SessionStart stays sync (note must exist before PostToolUse tries to append); SessionEnd stays sync (finalization must complete before session termination). Ref: https://code.claude.com/docs/en/hooks — async/asyncRewake fields.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a new hook — session-logger — that writes a human-readable markdown log of every Claude Code session: timestamps, cwd, git repo/branch, files touched, and bash commands run.
The repo's own
CONTRIBUTING.mdand README have hadsession-summaryon the "Ideas for new hooks" list since day one. This ships it, scoped more broadly as a full session logger (covers start, during, and end of session).Why
Claude Code sessions are ephemeral. You finish a session, switch repos, and two days later can't remember which session touched which file or ran which command. This hook gives you a durable, greppable (and Obsidian-friendly) record.
What it does
SessionStart→ creates a new note at<log-dir>/YYYY-MM-DD_HHmm_<sid-short>.mdwith YAML frontmatter (session_id,cwd,git_repo,git_branch,started,ended) and two pre-seeded sections: Files Touched and Commands Run.PostToolUse(Edit | Write | Read | Bash) → appends an entry to the right section.SessionEnd→ fills inended:in frontmatter and appends a final git status.Design choices
SessionEnd, notStop.Stopfires at the end of every Claude turn (many times per session);SessionEndfires once when the session actually ends. The handler is also idempotent — ifended:is already filled, it no-ops, so accidentally registering againstStopis harmless.PostToolUsewrites synchronously during the session, so if the terminal is killed beforeSessionEndfires, all activity up to the last tool call is preserved — the note just doesn't get a finalended:timestamp.hook_event_name.YYYY-MM-DD_HHmm_<sid-short>.md— chronologically sortable in any file explorer, greppable by session id suffix.SessionStartwith the same id, appends aResumed atmarker rather than overwriting.SESSION_LOG_DIRconstant (matches the existingSAFETY_LEVELpattern), overridable viaCC_SESSION_LOG_DIRenv var. Point it at an Obsidian vault or iCloud folder for cross-device sync.{}output.Changes
hook-scripts/session/session-logger.js— new hook (~195 LOC).hook-scripts/tests/session/session-logger.test.js— 20 new tests, all passing (includes an explicit regression test thatStopevents do not finalize the note, and an idempotency test for duplicateSessionEnd).README.md— added the new "Session" section to the hooks table, removedsession-summaryfrom the Ideas list (it's now shipped), bumped test badge 262 → 282.Test plan
node --test hook-scripts/tests/session/session-logger.test.js— 20/20 pass.npm test— 280/282 pass (the 2 failures are pre-existingnotify-permissionSLACK_WEBHOOK env issues onmain, unrelated to this PR — verified by stashing untracked changes and re-running).