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.

webapi — codebase reference

A Hono + Bun service that reads sessions back out of CouchDB + S3, exposes them over a small JSON API, and (in production) serves the built webui SPA from the same process. As built today it is read-only for session data (the only writes it does are idempotent schema setup on boot).

Direction (ADR 0016): the webapi is the project's single I/O gateway and stability column — the hook and all consumers will read and write through it, and it will add read-only /api/couch + /api/s3 proxies. This doc describes the current code; see architecture.md and routes.md for the target.

API docs tooling (decided). Keep the OpenAPI spec — it's the contract source of truth and orval needs it to generate the CLI + webui clients (ADR 0019). The rendered docs at /api/docs will be served by Scalar (@scalar/hono-api-reference) — a modern reference UI over the same spec — replacing @hono/swagger-ui; @hono/zod-openapi stays for spec generation.

File layout

FilePurpose
src/index.tsBoot sequence: build config, open CouchDB + S3 handles, ensureCouchDbs, start the server.
src/server.tsOpenAPIHono app factory: health check, OpenAPI doc + Swagger UI, optional SPA static serving.
src/config.tsConfig loader: claude-transcripts.config.json defaults overlaid with .env.
src/routes/sessions.tsThe session/transcript endpoints + their zod schemas; running-session detection.
src/storage/couch.tsmakeCouchHandles(config)nano server + database handles.
src/storage/blob-store.tsBlobStore interface (get, stat).
src/storage/s3-blob-store.tsS3BlobStoreBun.S3Client implementation (path-style, vendor-neutral).
src/storage/ensure.tsensureCouchDbs — creates the DB, upserts every design doc, creates the Mango index.

Configuration (config.ts)

Two layers, per configuration.md: non-secret defaults from the repo-root claude-transcripts.config.json (DB/bucket names, features, servicesMenu), overlaid with secrets/endpoints from .env.

HTTP API

All session endpoints are under /api/claude. Routes are declared with createRoute(...) + zod schemas so the OpenAPI spec and Swagger UI are generated from the same definitions (no hand-written spec).

MethodPathQueryReturns
GET/health{ ok, status, version, startedAt, stores } — see below
GET/api/claude/sessionslimit=50, skip=0{ sessions: ClaudeSessionSummary[], totalCount }
GET/api/claude/sessions/{id}ClaudeSessionSummary (404 if absent)
GET/api/claude/sessions/{id}/transcriptlimit=100, offset=0{ entries: TranscriptEntry[], totalCount, hasMore, source, byteCoverage }
POST/api/search/reindex{ enabled, sessions: {scanned, indexed}, turns: {scanned, indexed}, failures }
GET/api/openapi.jsonOpenAPI 3.0 spec
GET/api/docSwagger UI
GET/*SPA static + index.html fallback (only when CT_STATIC_DIR is set)

Health and store readiness

/health answers two questions that are easy to conflate:

Boot deliberately never blocks on the stores (index.ts): the webapi must come up so you can see what is wrong. That makes this distinction load-bearing — without it, a webapi with no databases is indistinguishable from a healthy one until the first write fails. cli doctor checks it before writing anything.

List behaviour (running-session detection)

Ended sessions come from the sessions/by_date view, newest-first, paged by limit/skip. On the first page only (skip=0), the route also surfaces active sessions — entries that have a SessionStart (via session_meta/start_meta) but no summary: doc, bounded to starts within the last 36 h. Each is classified running if it logged activity within 15 min, else incomplete. This matches the status model in architecture.md.

Detail / transcript

The S3 blob (<id>/transcript.jsonl) is the fallback, used when it reaches further than the chunks: for a session logged with couchFullContentChunks off (byte-range-only chunks carry no turns), or when a final flush was missed so the last chunk falls short of the uploaded file. The rule is whichever source covers more bytes, chunks winning ties — identical for an ended or backfilled session, chunks for a live one. The response reports which store answered (source: chunks | s3) and how far it reaches (byteCoverage).

Both sources normalise to the same pruned per-turn shape (ADR 0027), so the response never changes form with source. That means the endpoint no longer returns raw Claude Code JSONL — byte-exact lines remain available through the read-only S3 proxy (/api/s3/sessions/<id>/transcript.jsonl). This narrows ADR 0014: S3 is still the durable, byte-faithful home, but it is no longer the read path.

A 502 (rather than 404) distinguishes "nothing in CouchDB and S3 itself failed" from "no transcript stored".

Search indexes (derived state)

Two Meilisearch indexes, named from config/ (meilisearch.indexes, namespaced so a shared engine can't collide — ADR 0028): claude-transcripts-sessions (one doc per session, metadata search) and claude-transcripts-turns (one doc per conversation turn, content search).

They are derived — everything in them is a projection of CouchDB, so losing the engine costs a reindex, not data. Three write paths keep them current:

Worth knowing: only conversation turns (user/assistant/tool_result) enter the turns index. Non-message lines carry a display summary but are deliberately not indexed — they're context, not content, and there are more of them than user turns. Deleting a session through DELETE /api/ingest/{id} removes its search entries too.

Two Meilisearch behaviours the write path has to respect:

Storage

Schema setup on boot (ensure.ts)

ensureCouchDbs runs every boot and is idempotent: it creates the databases (ignoring "already exists") and creates a Mango index on type (non-fatal on error), then applies any pending migrations.

The design docs come from the migration registry (@claude-transcripts/shared src/migrations/) and nowhere else — there is one authoritative definition, applied through the versioned path rather than upserted blindly, so a view can never drift from the document shapes it maps over (migrations.md). The full view catalogue is documented in couchdb.md.

SPA serving (prod)

In production the combined image sets CT_STATIC_DIR to the built SPA (packages/webui/dist); server.ts then serves static files with an index.html fallback for client-side (hash) routing — one container serves API + UI (ADR 0002). In dev the var is unset and Vite serves the UI, proxying /api to this service.

packages/shared

packages/shared/src/index.ts holds cross-cutting domain types + helpers. The wire/response types are currently imported directly by the webui, but the direction is for webui + CLI to consume a client generated from the OpenAPI spec (ADR 0019, superseding 0006), leaving shared for genuinely cross-cutting domain types like sumTranscriptTokens: