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.

CouchDB documents and views

CouchDB is the primary store (ADR 0007). The sessions database (claude-transcripts-sessions by default) holds typed, append-only documents; map/reduce design views do the aggregation. The byte-exact transcript lives in S3 (ADR 0014); CouchDB holds parsed, pruned turns on full-content chunks (ADR 0027).

Conventions

Document types

type_idWritten byStatus
eventautothe hook, per event; backfillwritten
summarysummary:<sessionId>the hook at SessionEnd; backfill; doctorwritten
chunkchunk:<sessionId>:<byte_start, zero-padded to 12>the hook mid-session; backfillwritten
schema_versionschema_versionthe migration runnerwritten
—_local/search_checkpointthe webapi's search followerwritten (a local doc: not in _all_docs, _changes or replication)
session_startsession_start:<sessionId>—planned: a start record with session metadata
metaauto—planned: append-only enrichment (attribution, tags, extracted facts)
logauto, in the separate app-logs database—planned (app-logging.md)

event

{
  "type": "event",
  "event": "PostToolUse",          // hook event name
  "session_id": "<uuid>",
  "timestamp": "2026-06-18T12:34:56.789Z",
  "hostname": "…",
  "cwd": "/abs/path"
}

Plus, per event (previews are capped at 200 characters; full content is in chunks and S3):

eventExtra fields
SessionStartsource (startup/clear/resume/compact), model, permission_mode
UserPromptSubmitprompt_length, prompt_preview
PostToolUsetool_name, tool_use_id, input_preview
PostToolUseFailuretool_name, error_preview, is_interrupt
Stopstop_hook_active
StopFailureerror_type, error_preview
SubagentStart / SubagentStopagent_id, agent_type
PreCompact / PostCompacttrigger (manual/auto)

summary

Written once per session. A session is ended exactly when this doc exists.

{
  "_id": "summary:<sessionId>",
  "type": "summary",
  "event": "SessionEnd",
  "session_id": "<uuid>",
  "timestamp": "…", "hostname": "…", "cwd": "/abs/path",
  "end_reason": "…",              // Claude Code's SessionEnd `reason`, else "unknown"
  "event_count": 0, "prompt_count": 0, "error_count": 0,
  "tool_counts": { "Bash": 12, "Edit": 5 },
  "transcript_bytes": 0,          // size only; the transcript is in S3
  "token_usage": { "input": 0, "output": 0, "cacheCreation": 0, "cacheRead": 0, "total": 0, "messages": 0 },
  "system_checks": {},            // hook-written docs; reserved, currently empty
  "source": "live | backfill | doctor",
  "model": "…",                   // backfill/doctor docs, from the transcript
  "backfilled_at": "…",           // backfill only; the session's own time stays in `timestamp`
  "actor": "…"                    // optional, from `backfill --actor`
}

token_usage is computed by sumTranscriptTokens, deduplicated by message.id.

chunk

A slice of the transcript, written as the session runs (mid-flight-chunking.md) or reconstructed by backfill. Both use the shared sliceIntoChunks, so the boundaries match.

{
  "_id": "chunk:<sessionId>:000000010240",
  "type": "chunk",
  "session_id": "<uuid>",
  "byte_start": 10240, "byte_end": 10752,
  "entry_count": 8,
  "timestamp": "…", "hostname": "…", "cwd": "/abs/path",
  "schema_version": 2,            // 2 with entries[], 1 for byte-range only
  "source": "live | backfill | doctor",
  "entries": [ /* parsed, pruned turns, only with couchFullContentChunks */ ]
}

Keying on byte_start keeps ids unique across resumes. Chunks are not deduplicated at write time; repeated streaming entries are a read-time concern.

schema_version

{
  "_id": "schema_version",
  "type": "schema_version",
  "version": 10,                  // highest applied migration; 0 = pristine
  "applied": [ { "id": 1, "name": "initial-schema", "at": "2026-06-20T09:14:02.881Z" } ]
}

Status model (derived, not stored)

ended if a summary doc exists; otherwise running or incomplete by how recently the session had activity (webapi.md).

Design views

All JavaScript map/reduce, all installed by migrations from packages/shared/src/migrations/ and applied by the webapi on boot.

ViewMapsKey → valueReduceUsed by
sessions/by_datesummary[y, m, d] → { session_id, event_count, prompt_count, error_count, cwd }_countreindex
sessions/by_cwdsummary[cwd, timestamp] → { session_id, event_count, prompt_count }_count
events/by_sessionevent[session_id, timestamp] → { event, tool_name, input_preview }—a session's event timeline
events/by_typeevent[event, y, m, d] → 1_count
tools/usageevent with a tool_name[tool_name, y, m, d] → 1_count
tools/failuresPostToolUseFailure[tool_name, timestamp] → { session_id, error_preview, cwd }—
tools/errorsPostToolUseFailure, or any doc with an error field[tool_name or "unknown", timestamp] → same—
activity/timelineevent[y, m, d, h] → 1_count
chunks/by_sessionchunk[session_id, byte_start] → { byte_start, byte_end, entry_count }—reassembly
chunks/entry_count_by_sessionchunksession_id → entry_count_sum
chunks/entries_by_sessionfull-content chunk[session_id, byte_start, entry_index] → the turn (role, timestamp, text, toolUses, toolUseId, isError, isSidechain, kind)_countGET /api/sessions/{id}/transcript
speaker_split/by_rolefull-content chunk[session_id, role, byte_start, entry_index] → { role, timestamp, text, toolUses, toolUseId, isError }_countGET /api/sessions/{id}/turns
speaker_split/by_role_timefull-content chunk[role, timestamp, session_id, byte_start, entry_index] → { sessionId, cwd, role, timestamp, text }_countGET /api/turns
session_index/aggregateevent, summary, chunksession_id → per-doc rollupcustom mergesession list and detail
session_index/event_timesevent, summary, chunksession_id → timestamp—active duration
session_meta/start_metaSessionStart eventssession_id → { timestamp, model, cwd, hostname }—unused by the webapi
session_meta/tokens_by_datesummary[y, m, d] → token sums + sessions_sum

Notes:

Changing a view

Add a migration; never edit a design doc in place. The runner records each step in schema_version and the webapi applies pending steps on boot, so every instance ends up with the same views.