You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Capture Claude's text narration from transcript file
Adds the "film room" missing piece: Claude's decisions and framing
between tool calls, read from the Claude Code transcript JSONL at every
Stop / SubagentStop / SessionEnd.
- migrations/003_messages.py creates `messages` table (PK: message_id +
block_index, idempotent ingest)
- src/cc_logger/transcripts.py reads transcript JSONL and extracts only
`text` blocks. Extended `thinking` blocks are encrypted by Anthropic
(signature only, no plaintext) — that's an API-level choice we can't
work around.
- New Stop handler + transcript ingestion called from Stop, SubagentStop,
and SessionEnd for live + reconciliation capture
- install-hooks.py now installs a Stop hook too
- inspect.py interleaves text blocks (· prefix) with tool calls so you
can see "what Claude was thinking out loud" inline with "what it did"
- docs/SCHEMA.md + docs/HOOKS.md updated; 8 new unit tests for transcript
parsing (skips thinking blocks, handles malformed lines, etc.)
`cc-logger insights` adds the cross-session view — power-law distribution of where your time goes, top failure domains, sub-agent fan-out patterns, hourly activity.
@@ -74,6 +80,7 @@ python scripts/install-hooks.py # wires the Claude Code hooks
74
80
- Every sub-agent invocation (root + children, with linkage to the spawning `Agent` tool call)
75
81
- Every tool call in the capture allowlist (Agent, Bash, Edit, Write, WebFetch, WebSearch, and `mcp__.*`)
76
82
- Tool input + tool response payloads as JSONB; anything >50KB spills to a separate `artifacts` table
83
+
-**Claude's text narration between tool calls** — read from the Claude Code transcript file at every `Stop` / `SubagentStop`, stored in the `messages` table. (Extended `thinking` blocks are encrypted by Anthropic — only `text` blocks are capturable.)
77
84
- Optional regex redaction of common secret patterns before write (on by default)
Copy file name to clipboardExpand all lines: docs/HOOKS.md
+14-2Lines changed: 14 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -14,8 +14,9 @@ Official Claude Code hooks documentation: https://code.claude.com/docs/en/hooks.
14
14
|`PostToolUse`| After a tool succeeds. **Filtered**. | Updates the matching `tool_calls` row with the response, `status='success'`, and duration. Spills payloads >50KB to `artifacts`. |
15
15
|`PostToolUseFailure`| When a tool fails. **Filtered**. | Updates the matching `tool_calls` row with `error`, `status='failure'`, duration. |
16
16
|`SubagentStart`| Sub-agent spawned. | Inserts an `agent_invocations` row. Resolves the parent `Agent` tool_call by `subagent_type` match. |
17
-
|`SubagentStop`| Sub-agent finishes. | Updates the matching `agent_invocations` row with `last_message`, `ended_at`, `status='completed'`. |
18
-
|`SessionEnd`| Session ends (exit, logout, kill, etc.). | Updates `sessions.ended_at` + `end_reason`. Sweeps any still-pending `tool_calls` and `agent_invocations` for this session to `orphaned`. |
17
+
|`SubagentStop`| Sub-agent finishes. | Updates the matching `agent_invocations` row with `last_message`, `ended_at`, `status='completed'`. **Also reads the sub-agent's transcript file and ingests `text` blocks into the `messages` table.**|
18
+
|`Stop`| Root agent finishes a turn (one response to one user message). | Reads the root transcript file at `transcript_path`, extracts assistant `text` blocks, INSERTs them into `messages` (idempotent on `message_id` + `block_index`). This is where Claude's mid-process narration lands. |
19
+
|`SessionEnd`| Session ends (exit, logout, kill, etc.). | Updates `sessions.ended_at` + `end_reason`. Sweeps any still-pending `tool_calls` and `agent_invocations` for this session to `orphaned`. Also does a final transcript ingestion pass to catch anything `Stop` missed. |
19
20
20
21
## Tool capture allowlist
21
22
@@ -47,6 +48,17 @@ cc-logger resolves the link by matching on `subagent_type`:
47
48
48
49
In practice, Claude Code emits hook events sequentially even when sub-agents execute in parallel, so the multi-candidate case is rare.
49
50
51
+
## Transcript-based message capture
52
+
53
+
Hooks don't include Claude's narration text in their payloads — they only fire at action boundaries. To capture the *decisions* Claude is making mid-process (e.g., "I'll start by exploring the project structure..."), cc-logger reads the Claude Code JSONL transcript file at `transcript_path` (a field present in every hook event).
54
+
55
+
- On `Stop` / `SubagentStop`: incremental ingest, near-realtime
56
+
- On `SessionEnd`: final reconciliation pass
57
+
58
+
Only `text` blocks are extracted. Claude's `thinking` (extended thinking) blocks are encrypted in the transcript by Anthropic — only a `signature` is present, no plaintext reasoning. This is an API-level choice and not something cc-logger can work around.
59
+
60
+
Insertion is idempotent (`ON CONFLICT (message_id, block_index) DO NOTHING`), so repeated reads of the same transcript are safe.
Copy file name to clipboardExpand all lines: docs/SCHEMA.md
+18Lines changed: 18 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -53,6 +53,24 @@ cc-logger writes to 4 tables. The full DDL is in [`migrations/001_initial_schema
53
53
|`started_at`| TIMESTAMPTZ | When PreToolUse fired. |
54
54
|`received_at`| TIMESTAMPTZ | When the worker actually processed the event. Difference shows async queue lag. |
55
55
56
+
## `messages` — assistant text blocks (Claude's narration)
57
+
58
+
Populated by reading the Claude Code transcript JSONL at `Stop` / `SubagentStop` / `SessionEnd`. Only `text` blocks are captured; Claude's `thinking` blocks are encrypted in the transcript (signature only, no plaintext) and we can't extract them.
59
+
60
+
| column | type | notes |
61
+
|---|---|---|
62
+
|`message_id`| TEXT | Anthropic message UUID from the transcript. |
63
+
|`block_index`| INTEGER | Position of the text block within the message's `content` array. PK is composite (`message_id`, `block_index`). |
64
+
|`session_id`| TEXT FK | References `sessions(session_id)`. |
65
+
|`invocation_id`| TEXT FK | The agent that produced the message (root or sub-agent). |
66
+
|`role`| TEXT |`assistant` (we only capture assistant text). |
67
+
|`block_type`| TEXT |`text` (we only capture text blocks). |
68
+
|`text`| TEXT | The text Claude said, after redaction. |
69
+
|`position`| INTEGER | Line number in the source JSONL — gives a stable in-transcript ordering. |
70
+
|`created_at`| TIMESTAMPTZ | When the row was inserted. |
71
+
72
+
Indexed on `(session_id, position)` and `(invocation_id)`.
0 commit comments