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. These docs describe the intended design as much as the current state.

CouchDB document types, schemas & design views

CouchDB is the primary store (ADR 0007): a single database (claude-sessions by default) of heterogeneous, append-only docs, with map-reduce design views doing the aggregation. Full-fidelity transcript bytes never live here — they go to S3 only (ADR 0014).

This doc has two halves: (1) the document types and their schemas, and (2) how the design views bring those documents together and which views exist.

Conventions


1. Document types & schemas

type_idWritten byWhen
eventautoper-event handlerslive, per hook event
summarysummary:<sessionId>session-end (live) / backfillat session end
chunkchunk:<sessionId>:<byte_start>backfill / chunk-flushon backfill, or live (chunking on)
session_start (planned)session_start:<sessionId>session-startonce, at start (#15)
meta (planned)autoenrichment endpointany time, append-only (#3/#7)
schema_version (planned)schema_versionmigrationson migrate

Operational/app logs (type:"log") live in a separate database, not this one — see app-logging.md / ADR 0018.

event

One per hook event, POSTed live. Carries the common fields stamped on every event doc, plus event-specific fields.

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

Event-specific additions:

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

(Previews are deliberately short markers; full content lives in chunks/S3. As more hook types are wired (hooks.md), their marker fields are added here.)

summary

Written once at SessionEnd by the live hook, or by backfill when adopting an on-disk transcript. A session is ended iff this doc exists.

{
  "_id": "summary:<sessionId>",
  "type": "summary",
  "event": "SessionEnd",
  "session_id": "<cc uuid>",
  "timestamp": "…",
  "hostname": "…",
  "cwd": "/abs/path",
  "end_reason": "user-input | session-limit | unknown",
  "event_count": 0,
  "prompt_count": 0,
  "error_count": 0,
  "tool_counts": { "Bash": 12, "Edit": 5 },   // tool name → call count
  "transcript_bytes": 0,                        // size only; content is in S3
  "token_usage": {                              // see TokenUsage below
    "input": 0, "output": 0,
    "cacheCreation": 0, "cacheRead": 0,
    "total": 0, "messages": 0
  },
  "system_checks": {},
  "source": "live | backfill",                  // "live" = hook-recorded; "backfill" = adopted transcript
  "backfilled_at": "…"                          // present only on backfilled docs; real session time stays in `timestamp`
}

TokenUsage (shared shape, computed by sumTranscriptTokens, deduped by message.id): input, output, cacheCreation, cacheRead, total, messages.

chunk

Append-only byte-faithful slice of the transcript — reconstructed by backfill (via the shared sliceIntoChunks), or written live during the session when mid-flight chunking is enabled (mid-flight-chunking.md).

{
  "_id": "chunk:<sessionId>:<byte_start padded to 12>",
  "type": "chunk",
  "session_id": "<cc uuid>",
  "byte_start": 10240,
  "byte_end": 10752,
  "entry_count": 8,             // non-null parsed JSONL entries in this slice
  "timestamp": "…",
  "hostname": "…",
  "cwd": "/abs/path",
  "schema_version": 1,
  "entries": [ /* parsed, pruned JSONL entries — only when couchFullContentChunks */ ]
}

The id is keyed on byte_start (monotonic, unique per session) so it never collides across resumes. Chunks stay byte-faithful to their slice (no dedup at write time — that's a read/view-time concern), which keeps them append-only and replication-safe.

Status model (derived, not stored)

A session's status is derived, never written as a field:

Schemas defined in code

CouchDB is schemaless, but our documents have a known, expected shape — so we define that shape in code and treat it as authoritative:

The schema definitions are the same source the webapi's ensure.ts and the hook reference; keep them aligned with the design-view mirrors (below).


2. How design views bring documents together

The documents above are a flat, heterogeneous stream. Design views are what turn that stream into queries — they map over docs of a given type/event, emit keys that group or order them (by session, by date, by tool, by hour), and optionally reduce (count/sum). Two patterns recur:

The design docs are defined in two mirrored places that must stay in sync: hooks/couchdb/claude-sessions/designs/*.json (synced by setup-views.sh) and packages/webapi/src/storage/ensure.ts (auto-applied on webapi boot). All views are JavaScript map/reduce. (The migration tool will become the authoritative path for view changes over time.)

_design/sessions — sessions by time & location

Maps type === "summary".

ViewKeyValueReduce
by_date[year, month, day]{ session_id, event_count, prompt_count, error_count, cwd }_count
by_cwd[cwd, timestamp]{ session_id, event_count, prompt_count }_count

by_date (descending) drives the webui session list.

_design/events — events by session & type

Maps type === "event".

ViewKeyValueReduce
by_session[session_id, timestamp]{ event, tool_name, input_preview }
by_type[event, year, month, day]1_count

by_session reassembles a session's event timeline.

_design/tools — tool usage & failures

ViewKeyValueReduce
usage[tool_name, year, month, day]1_count
failures[tool_name, timestamp]{ session_id, error_preview, cwd }
errors[tool_name?, timestamp]{ session_id, error_preview, cwd }

failures maps PostToolUseFailure; errors is broader (any doc with an error field), keyed "unknown" when no tool name.

_design/activity — hourly timeline

ViewKeyValueReduce
timeline[year, month, day, hour]1_count

_design/chunks — content reassembly

ViewKeyValueReduce
by_session[session_id, byte_start]{ byte_start, byte_end, entry_count }
entry_count_by_sessionsession_identry_count_sum
entries_by_session[session_id, byte_start, entry_index]the parsed turn (role, timestamp, text, toolUses, toolUseId, isError, isSidechain)_count

by_session reassembles a session's chunked content in byte order.

entries_by_session (v6) is the session's transcript in reading order across all speakers — unlike speaker_split/by_role, which groups by speaker and so can't interleave them. It backs GET /api/sessions/{id}/transcript, paged at the view, with the _count reduce supplying the total in one query. Since chunks are flushed mid-session, it serves a live session's transcript-so-far; only full-content chunks (couchFullContentChunks) populate it, so byte-range-only chunks fall back to S3.

_design/session_meta — running-session enrichment & token rollup

ViewKeyValueReduce
start_metasession_id{ timestamp, model, cwd, hostname }
tokens_by_date[year, month, day]{ input, output, cacheCreation, cacheRead, total, sessions }_sum

start_meta (maps SessionStart events) lets the webapi enrich sessions that have started but have no summary yet.

Planned feature views

Deferred until validated against a running CouchDB (mid-flight-chunking.md, actions.mdextract-feature): features/urls, then repos / PRs / issues / /-commands / models, and the cross-session "events of interest" views (single timeline, durations, active-vs-idle).

Mango index

indexes/type.json defines idx-type on { fields: ["type"] }, so filtering docs by type doesn't scan the whole DB. Index creation is non-fatal — an optimisation, not a correctness requirement.

Keeping the mirrors in sync

When a view changes, edit both hooks/couchdb/.../designs/*.json and the matching design in ensure.ts (the map function bodies must match). The hook's setup-views.sh applies the former; the webapi applies the latter on every boot (idempotent upsert carrying _rev forward). Schema/view evolution is captured as versioned migrations.