Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 16 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ Run `substrate-claude-code configure` to print the required wiring without writi
If the variables are absent or the API is temporarily unreachable, capture remains enabled and
new events are placed in the local spool beneath `~/.substrate/claude_code_memory/`. Set
`SUBSTRATE_STATE_HOME` to relocate the state root, which is particularly useful for isolated
tests.
tests. Sidechain capture is enabled by default. Set `SUBSTRATE_CAPTURE_SIDECHAINS=0`
to use the emergency exclusion kill-switch.

## MCP tools

Expand All @@ -62,8 +63,8 @@ The repository also adds `/substrate-status` and `/substrate-recall` slash comma

Claude Code invokes four command hooks from `hooks/hooks.json`:

- **Stop** captures only user and assistant messages not previously checkpointed and emits a
`turn` event.
- **Stop** captures normalized user, assistant, tool-call, tool-result, and system blocks not
previously checkpointed and emits a `turn` event.
- **PreCompact** captures the same incremental transcript window as `pre_compress` before Claude
Code compacts its context.
- **SessionEnd** emits a content-free `session_end` event containing only the normalized message
Expand All @@ -76,29 +77,24 @@ under the plugin state directory. Capture events are durably spooled before netw

## Privacy boundary

The transcript reader keeps only top-level `user` and `assistant` records. It excludes sidechain
records produced by subagents and ignores system, attachment, mode, permission-mode, and
last-prompt records.
The transcript reader captures top-level and sidechain `user`, `assistant`, and `system` records.
Sidechain records carry record/block coordinates and session ancestry. Tool calls and results are
separate text-only messages paired by `tool_call_id`. A paired result receives its tool name; an
orphaned or ambiguous result receives a reason code and no source identity.

For captured user and assistant messages, the plugin may send:

- visible text from string content and `text`, `input_text`, or `output_text` blocks;
- short markers such as `[tool_use: Read]` and `[tool_result]`;
- role, message index, bounded message identifier, session boundary, and capture metadata.

It does not intentionally send:

- raw tool inputs or raw tool-result payloads;
- hidden reasoning, token usage, billing fields, or arbitrary provider metadata;
- attachment bodies, binary media, data URLs, or sidechain transcript text;
- `SUBSTRATE_API_KEY` or other recognized credential-shaped values.
For every captured block, full credential detection runs before the 65,536-byte UTF-8 ceiling. If
a recognized credential occurs anywhere in a block, the whole block becomes content-free. This
prevents a secret from being cut at a former truncation boundary. Binary and media bodies, hidden
reasoning, token usage, billing fields, and arbitrary provider metadata are not captured.

The shared capture core redacts recognized secrets before persistence and transfer. Redaction is
defense in depth, not proof that arbitrary sensitive prose is absent. Visible prompts and
assistant output can themselves contain confidential material, so configure only a trusted
Substrate server and review its access and retention policy. Failed deliveries remain in a
bounded owner-private local spool until delivered, evicted by the bound, quarantined, or removed
by the operator.
bounded owner-private local spool. The spool reserves capacity for
boundary events and refuses newest events under pressure instead of evicting older evidence.
`substrate-claude-code status` exposes persistent `evicted`, `quarantined`, `dropped`, and
`duplicates` counters without exposing content.

## Fail-open behavior

Expand Down
7 changes: 6 additions & 1 deletion src/claude_code_memory/hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@ def _capture_transcript(
) -> None:
session_id = str(data.get("session_id") or "")[:512]
transcript_path = data.get("transcript_path")
messages = read_messages(transcript_path) if isinstance(transcript_path, str) else []
settings = config.resolved(PROVIDER_ID)
messages = (
read_messages(transcript_path, include_sidechains=bool(settings["capture_sidechains"]))
if isinstance(transcript_path, str)
else []
)
client, spool, deliverer, builder = runtime_factory()
del client, spool
checkpoint = _checkpoint(session_id)
Expand Down
Loading
Loading