Watch · Sessions
Hand off a session to the next one
A session ends with one dated markdown file in your own repository: what got built, what is next, what slowed the session down. The Sessions tab renders that directory newest first, and the next session's briefing reads the entry back.
For: the person who ran the session, and whoever picks the repository up next
What it does, and why it helps
At the end of a session the SessionEnd hook writes a dated file under .claude/brain/sessions/, named for the day and the branch. It writes three headings, What got built, What's next and What slowed us down / do differently, each holding a _(fill in)_ placeholder, then an Auto-logged facts block: the branch, the commits since midnight, the files those commits touched, and whether anything was uncommitted when the session ended. If turns were captured it appends an Active session log block with the turn count, the span and the files touched. Every file it writes carries a scaffold marker, and the rollup appends only into a file carrying that marker, so an entry a person wrote is never edited by the hook.
The tab is the directory. There is no public/sessions.html and no committed twin: the server reads the directory on each request and renders a row per .md file, badged with the date from the filename and titled from the record's first heading, newest first, with a filter box over the badge and the title. A row opens the record itself, rendered to the same shell. The entry is an ordinary tracked file, so it moves with your commits, and the brain snapshot the desktop publishes leaves the forward-only log directories out on purpose.
The pain. A session ends with its turns captured and nothing written down. The next person, or the next agent, re-reads the diff to work out where it stopped, and the reason behind a decision is already gone.
The point of view. Capture and account are different things. A hook can record the branch, the commits and the files it saw. Only the person who was there can say what it was for and what to do next, so the scaffold writes the facts and leaves the three narrative sections empty rather than guessing at them.
What gets easier. Starting. The briefing reads the most recent dated entry inside its window and takes the What got built and What's next sections from it as written, so the first move of the next session is the last line of the previous one.
When it helps. Any tracked repository with a .claude/brain/sessions/ directory. The scaffold needs the SessionEnd hook registered in that repository's .claude/settings.json; without it the entries are hand-written and the tab reads them the same way.
Its limits. The tab lists what is in the directory and nothing else. These are hand-written journal entries, not the Claude Code telemetry sessions the Activity ledger counts, so the two counts differ on purpose. The hook scaffolds at most one file a day per repository, and it writes nothing at all outside a git work tree. Drafting the three sections is off by default and needs an Anthropic key.
Understand it in 30 seconds
Read the narration
- 0:00 Tomorrow you reopen the repo.
- 0:02 Nothing says where last night stopped.
- 0:06 The captured facts are not the account.
- 0:08 Only the person who was there writes why.
- 0:13 The hook scaffolds three sections and rolls up the session's turns.
- 0:17 A drafted entry waits in a sidecar until you confirm it.
- 0:23 The next session opens with what got built and what is next.
Synthetic example. Read the guide
Where to find it
Where to find it
- Desktop:
localhost:4000, then Attribution in the sidebar, then Sessions under All tools, in the Sessions, transcripts and traces group. - Hosted: desktop only.
- Keyboard: ⌘ K, then type “Sessions”.
When to use it
Cold-starting the next morning
Situation. Yesterday's session ended with the entry filled in. You open the repository today with no memory of where it stopped.
What you do. Open Sessions. The top row is yesterday's date. Click it, read What's next, and start there. The session briefing quotes the same two sections at session start when the entry is inside the window.
What you see. The row badge is the date from the filename and the row title is the record's first heading. The record page shows the three narrative sections plus the Auto-logged facts and Active session log blocks.
What it establishes. The opening move came from the previous session's own account, not from re-reading a diff. What the entry does not say, nobody wrote down.
A day that shipped two pull requests
Situation. Two units of work land the same day, and you want a record of each rather than one file covering both.
What you do. Write the second entry yourself as a second dated file in the same directory. The hook already scaffolded one file for the day and will not create another.
What you see. Both files appear as rows under the same date badge, ordered by their filenames. The hook's rollup block lands only in the file carrying the scaffold marker, so your hand-written entry stays as you wrote it.
What it establishes. One record per unit of work, and no hook edit inside a file a person authored. Two entries dated the same day are the ordinary case here, not a duplicate.
Reviewing a drafted entry
Situation. Drafting is on. A session ended with more than three captured turns and an Anthropic key on the machine.
What you do. Open the Session drafts subtab. Read the three drafted sections beside the source facts, then press Confirm, or Edit and then Save & confirm, or Reject.
What you see. The card shows the turn count, the span, the source JSONL and whether the canonical file exists. Confirm is disabled when that file is missing. Confirming folds the sections into the placeholders and deletes the sidecar; rejecting deletes the sidecar and leaves the canonical file untouched.
What it establishes. The canonical file was never written by the model on its own. If you had already typed a section, confirming leaves your text alone and the fold is a no-op.
Before you start
- Supported versions
- RepoOps desktop v0.3.1, the release this guide was read against. Reading the tab needs a tracked repository the aggregator can reach. The scaffold needs the SessionEnd hook registered in that repository's .claude/settings.json, running node scripts/cc-hook.mjs SessionEnd.
- Where it runs
- Local. The Sessions tab carries three subtabs: Active sessions, Session drafts and Brain diff. Hosted: /team/sessions is a desktop-only stub. It says the entries are repo files under .claude/brain/sessions/ and that the published brain snapshot leaves the forward-only log directories out, and it points at the team Pull requests index for the shared record of what shipped.
- Permissions
- Reading takes no credential. The drafting toggle writes an account setting, so on a machine with REPOOPS_OPERATOR_CONFIRM_SECRET set the tab asks for that value before it saves, read at submit time and never stored.
- Connections
- None for reading. Drafting needs ANTHROPIC_API_KEY on the machine that runs the hook; the Session drafts tab says so rather than drafting nothing in silence.
- Plan
- No plan gate. The tab reads files in your own repository, and nothing about it is metered.
Configure it
- Write the entry at the end of the session.
One file per unit of work under .claude/brain/sessions/, named for the date and a slug, with the three headings: What got built, What's next, What slowed us down / do differently. It is a tracked markdown file, so commit it with the change it describes.
- Let the SessionEnd hook scaffold the file.
Registered in the repository's .claude/settings.json as node scripts/cc-hook.mjs SessionEnd. It creates the dated file with the three placeholders and the Auto-logged facts block, and returns the existing file instead when the day already has one. It writes nothing outside a git work tree.
- Read the directory on the Sessions tab.
The tab renders the directory per request: newest date first, a filter box, and a count that follows the filter. A row opens the record. Nothing is generated and no HTML twin is committed.
- Turn drafting on only if you want a first pass.
On the Session drafts subtab, the switch reads Draft a session entry when a session ends. Off by default. With it on, the hook asks Haiku for a three-section draft from that session's turns and writes it beside the canonical file as a .draft.md sidecar.
- Decide every draft yourself.
Confirm folds the draft into the canonical file's _(fill in)_ placeholders and leaves any text you already wrote alone. Edit then Save & confirm folds your version. Reject deletes the sidecar. The drafting step never writes into the canonical file on its own.
- Sweep the directory when it gets long.
npm run session-forget prints the plan and writes nothing. npm run session-forget -- --apply gzips older records into a month archive under .claude/brain/sessions/archive/, lists them in ARCHIVE-INDEX.md, and regenerates the digest so every session stays listed.
| Setting | Where | A sensible choice | Why it matters |
|---|---|---|---|
autoDraftSessionEntry | Sessions, the Session drafts subtab, Draft a session entry when a session ends | off (the default) | Off means the hook writes the scaffold and no model call happens at session end. |
draftMaxTokensPerSession | stored account setting, no desktop control | 4000 (the default) | The per-call ceiling, and the amount charged against the daily cap before the call is made. |
draftDailyCapTokens | stored account setting, shown as a note on Session drafts | 20000 (the default) | Once the projected spend would pass it, the hook skips the draft and logs the reason instead of spending. |
REPOOPS_DRAFT_ACCOUNT_ID | the hook's environment | local (the default) | Which account's settings the drafting step reads and whose usage rows it writes. |
REPOOPS_BRIEFING_WINDOW_DAYS | the data directory's .env | 14 (the default) | How far back the session briefing will take a dated entry. Older entries stay on the tab and stay searchable; they stop loading at session start. |
REPOOPS_SESSION_DIGEST_OFF | the environment of the process running the nightly pass | unset (the digest runs) | Set to 1 and sessions-digest.md stops being regenerated, so the index of older sessions goes stale. |
REPOOPS_INDEX_MAX_LAG_DAYS | the environment of the brain-drift gate | 7 (the default) | How far INDEX.md's frontmatter timestamp may lag the newest dated session file before the index-freshness check fails. |
--keep-recent and --keep-days | npm run session-forget | 400 and 30 (the defaults) | A record is kept when either bound covers it, so a recent session is never archived on count alone. |
What you should see
A normal session end
Configuration. The SessionEnd hook registered, drafting off, the day's first session in this repository.
Expect. A dated file appears under .claude/brain/sessions/ with the three placeholder sections and the Auto-logged facts block. When turns were captured, an Active session log block is appended with the turn count, the span and the files touched.
Verify. The Sessions tab shows a row badged with today's date at the top of the list. Open the row and the record renders to the shell.
A second session on the same day
Configuration. The same repository, later the same day.
Expect. No second scaffold. The hook finds the existing dated file and returns it, and the rollup appends into it only because that file carries the scaffold marker. A hand-written second entry is left untouched.
Verify. One scaffolded file for the day, with exactly one Active session log block in it. A second entry you write yourself appears as its own row under the same date badge.
Drafting on, but nothing drafted
Configuration. The switch on, with no Anthropic key, fewer than three captured turns, or the day's token cap already reached.
Expect. No sidecar is written and the canonical file keeps its placeholders. The skip reason is printed to the app's output as a [cc-hook] line naming which of the five it was.
Verify. The Session drafts tab reads No pending session drafts. With the key missing, the switch's own note says nothing will be drafted until you add one in Settings.
Data and cost
- What is captured
- One markdown file per session in the repository's own .claude/brain/sessions/. The hook writes the branch, the commits since midnight, the files those commits touched, whether anything was uncommitted at the end, and, from the captured turns, the turn count, the span and the files touched. The three narrative sections are written by you, or by a draft you confirm.
- Who can see it
- Whoever can read the repository. The entries are ordinary tracked files, so they travel with your commits and with nothing else. There is no per-entry sharing control.
- How long it is kept
- Kept until you sweep. npm run session-forget -- --apply gzips a record into its month archive under .claude/brain/sessions/archive/ and adds a row to ARCHIVE-INDEX.md; the defaults keep the newest 400 records and everything from the last 30 days, and a record stays if either bound covers it. --apply is the only mode that removes a file. sessions-digest.md, a sibling of the directory rather than a file inside it, keeps listing every older session whether its body is in the tree or in an archive.
- What leaves the machine
- Nothing, by design: the brain publisher leaves the forward-only log directories (sessions/ and pull-requests/) out of the snapshot, so no entry reaches the team database, and the hosted page is a stub that says so. The one call that leaves the machine is drafting, one Anthropic request per drafted session on your own key.
- What it costs
- Reading the tab makes no model call. Drafting charges draftMaxTokensPerSession, 4000 by default, against the per-account daily cap of 20000 tokens before the call, and writes a usage row for the day.
When the result differs
| Symptom | Likely cause | Next action |
|---|---|---|
| The tab says No records in .claude/brain/sessions/ yet. | That directory is empty in the copy being served. | Write an entry, or check which repository the tab is scoped to. |
| Nothing appeared after a session ended. | The SessionEnd hook is not registered for that repository, or the session ran outside a git work tree. | Add the hook entry to the repository's .claude/settings.json and end a session. |
| A row named ARCHIVE-INDEX sits at the bottom of the list. | The index lists every .md file in the directory, and the sweep's index is one of them. | Expected after a sweep. Open it to find an archived record, and restore one by name with npm run session-forget -- --restore. |
| A row whose name ends in .draft.md. | The drafting sidecar sits beside the canonical file and also ends in .md, so the directory listing picks it up. | Confirm or reject it on the Session drafts subtab. Either one removes the sidecar. |
| The Session drafts tab stays empty with the switch on. | No Anthropic key, fewer than three captured turns, or the daily token cap was reached. | Read the [cc-hook] line in the app's output; it names which skip applied. |
| A banner says the view may be stale. | The repository mirror's last sync failed or its source is offline, so the index shows the last content it synced. | Check the server log for the mirror line the banner names. The mirror retries on its own. |
| CI fails on index-freshness. | INDEX.md's frontmatter timestamp lags the newest dated session file by more than the window. | Update INDEX.md in the same change, or widen REPOOPS_INDEX_MAX_LAG_DAYS. |
- Disable
- Untick Draft a session entry when a session ends, which takes effect at the next session end. Removing the cc-hook SessionEnd entry from .claude/settings.json stops the scaffold. The tab itself is a read of the directory and has nothing to disable.
- Roll back
- Not provided for an entry: no route restores an earlier version of a session file. It is a tracked markdown file, so git holds its history. For an archived record, npm run session-forget -- --restore NAME.md writes it back into the tree.
- Revoke access
- Not applicable. No token grants access to an entry and no link shares one; access follows access to the repository checkout. The hosted side never receives them.
- Delete
- Not provided. No route deletes a session entry. The file sits at .claude/brain/sessions/ in your repository and an archived record sits gzipped under .claude/brain/sessions/archive/, so deleting one is a file you remove and a commit you make. The sweep archives; it does not delete.
Related tasks
Maintenance evidence
- Feature id
sessions(spine leafsessions)- Owner
- Brain log views (docs/features/brain-log-views.md), with the K1.3 session rollup and the K2.F1 auto-draft from the knowledge-fabric program and the 2026-08-31 sessions forgetting sweep. Guide: LDG-0717.
- Supported product version
- RepoOps v0.3.1
- Last verified
- 2026-09-15, read against origin/main at 52366bb6d; labels read from the served tab source, which for this leaf is lib/log-index.mjs rather than a public/sessions.html (there is none), plus public/session-drafts.html for the drafting controls. The spine leaf, the canonical-tabs row and the MINI_RAIL row were each checked for the sessions slug.
- Example fixtures
- lib/log-index.test.mjs (the rendered index, the newest-first sort, the stripped title prefix and the mtime-keyed title cache), scripts/cc-hook.test.mjs (the SessionEnd scaffold, the idempotent rollup and the draft step with an injected synth), lib/session-draft.test.mjs and lib/session-drafts-api.test.mjs (the sidecar and the confirm, edit and reject routes), test/session-draft-enablement.test.mjs (the switch, written and re-read from disk in both directions), lib/brain/session-forget.test.mjs (the sweep, the archive index and the restore), lib/session-digest.test.mjs (the digest).
- Source references
lib/canonical-spine.json,lib/canonical-tabs.mjs,lib/log-index.mjs,lib/routes/mirror-files.mjs,scripts/cc-hook.mjs,lib/session-draft.mjs,lib/session-draft-haiku.mjs,lib/routes/session-drafts.mjs,lib/account-settings.mjs,lib/session-briefing.mjs,lib/session-digest.mjs,lib/brain/session-forget.mjs,lib/brain-publisher.mjs,public/session-drafts.html,scripts/brain-drift/index-freshness.mjs- Documentation review
- Independent review requested on the slice pull request; not yet recorded.
- Video review
- Story script written 2026-09-15; render and review pending in the same slice.
Last updated