claude-session-log
A Claude Code plugin built to fix one specific, observed failure:
SESSION_LOG.md — a project's durable handoff file — goes stale mid-session,
because nothing actually enforces the "keep it updated" instruction once
real work starts.
The problem
Claude Code sessions lose memory in three ways, none of which the model
controls:
- Compaction. Once the conversation nears the context limit, older turns
get summarized. Detail survives only as much as the summary keeps it —
usually "what changed," rarely "why," almost never "what's still broken."
- API quota / rate limits. A session can stop mid-task and resume later,
possibly as a different model, with nothing but whatever compaction left
behind.
- Interruption. The terminal closes, the user switches machines, or a
different session picks up the same repo later.
SESSION_LOG.md — a plain file, committed to the repo, updated in place —
is the fix for all three: compaction can't touch it, quota exhaustion
doesn't erase it, and any model with zero prior context can cat it and
resume immediately.
But telling Claude "maintain this file" in CLAUDE.md doesn't hold up on
its own. That instruction is prose competing for attention with everything
else in context, and it loses predictably:
- Long multi-step builds. A nine-task feature built via subagents
updates the log after task one, then attention moves to dispatching
implementers, reviewing diffs, fixing bugs — and the log is still
describing task one's state three hours and twenty commits later.
- Session handoff. Once context is summarized or a new session picks up
the work, nothing re-raises "go write down what happened."
- Subagents in worktrees. A subagent implementing one task in an
isolated worktree has no reason to know
SESSION_LOG.md exists, let alone
update it.
This isn't hypothetical — it's what prompted this repo. In the session that
built this plugin, a full feature (spec → plan → 9 implementation tasks →
review → merge) landed with SESSION_LOG.md stuck describing task one's
state the entire time. The user caught it by asking "is the session log up
to date?" The honest answer was no. Asking Claude to "just remember better"
doesn't fix this — the failure isn't a lapse in judgment on one turn, it's
that nothing in the environment ever re-raises the reminder at the moments
that matter.
The fix: enforcement, not another instruction
This repo has two parts, and neither does much alone:
docs/convention.md — the actual
SESSION_LOG.md convention: why it exists, what triggers an update, the
required file structure. Put this (or a reference to it) in CLAUDE.md —
it's the part a model actually reads to know what a correct log entry
looks like.
- Two
PostToolUse hooks — enforcement. They don't write the file or
define its shape; they watch for the two moments an update is most likely
to be forgotten, and say so when it was:
session-log-commit-reminder.sh (matcher: Bash) — after any git commit/git merge, checks whether SESSION_LOG.md was part of that
commit via git show -1 --name-only HEAD. If not, injects a
reminder as hookSpecificOutput.additionalContext.
session-log-spec-reminder.sh (matcher: Write|Edit) — after any
write to docs/superpowers/specs/* or docs/superpowers/plans/*
(i.e. a new body of work starting), injects a reminder to note it.
Both are read-only and non-blocking — they inspect state and emit context,
never "decision": "block". No match, no output, no cost. Both also
pre-filter on the raw tool-call JSON with a plain bash substring test before
ever spawning jq — since these hooks fire on every matching tool call,
the common case (a Bash call that isn't a commit, a Write that isn't a spec)
should cost nothing but a string comparison.
The mechanism is structural, not behavioral: hooks fire on tool-call
events, deterministically, regardless of what Claude is currently focused
on. There's no way for "got busy with task 4" to suppress an event a hook is
watching for — which is exactly the gap that let the real incident above
happen.
Data flow
Claude runs a tool (git commit, or Write/Edit)
│
▼
PostToolUse hook fires
│
▼
trigger moment? (commit missing the
log, or a new spec/plan written)
│ │
no yes
│ │
▼ ▼
silent additionalContext reminder
(exit 0) injected into Claude's context
│
▼
Claude updates SESSION_LOG.md