Claude Transcripts docs GitHub
Work in progressUnder active development — not tested as ready for use. Breaking changes land without notice, stored data may need to be discarded between revisions, and there is no auth or security model.

The hook

The writer: what Claude Code runs on every registered session event, logging the session to CouchDB and S3 as it happens. It never blocks or fails a session: every external call is wrapped, errors go to stderr, and it always exits 0.

Setup: hook-setup.md. Events: hook-events.md (generated). What each action does: actions.md.

Where it lives

In the CLI, at packages/cli/src/hook/:

index.ts       dispatch: payload → the actions bound to that event in the blueprint
handlers.ts    one handler per action
runtime.ts     runtime config, CouchDB + S3 clients, per-session state in /tmp

claude-transcripts hook run reads one payload on stdin; hook install registers that command in ~/.claude/settings.json. The plugin in hooks/ holds no logging code: scripts/dispatch.ts pipes the payload to claude-transcripts hook run and exits 0. hooks/hooks/hooks.json is generated by bun run gen:hooks.

There is one implementation. The plugin once carried its own copy of the writer because a plugin directory can't import the workspace; that ended when the installed CLI became the hook (ADR 0004).

Dispatch

Event → action bindings come from the blueprint (BINDINGS), the same source the registration is generated from, so dispatch and registration can't drift. An event's actions run concurrently and settled: one failing doesn't stop the others.

SessionStart and SessionEnd get a 180 s timeout because they do real work (seeding state; writing the summary and uploading the transcript); every other event gets 5 s.

Eight of the eleven events are registered async: true, so Claude Code doesn't wait for the writer (a synchronous PostToolUse costs roughly 130–160 ms per tool call). Three stay synchronous:

EventWhy
SessionStartIts actions print the banner and the recall primer, and Claude Code discards an async hook's output.
SessionEndA fire-and-forget upload at teardown could be killed half way.
UserPromptSubmitClaude Code ignores async on it.

The split lives in hookAsync() (packages/cli/src/hook/index.ts); sync-hooks.ts applies it to the plugin, and hook install rewrites an older registration in place. On async events Claude Code also discards exit codes and stderr, so a broken writer shows up in the statusline (◐ ct stalled), /claude-transcripts:status and the session-start banner instead.

Runtime config

From CT_HOOK_CONFIG, else ~/.config/claude-transcripts/config.json (hook-setup.md). No config means the hook does nothing; the session-start banner then says the session is not being recorded.

Checking it