Read Claude Code evidence only. Do not resume the old process, edit its project,
or turn a read request into a continuation task. When the user later asks to act,
hand the verified evidence to daymade-claude-code:continue-claude-code-work.
Route by the requested result
| User wants | Use |
|---|---|
| Recent Claude Code sessions, titles, dates, or IDs | Indexed metadata only; the bundled list_local_history.py currently reads all candidate bodies before date/limit filtering, so do not use it for a broad inventory |
| One known Session reconstructed as a chronological evidence briefing | scripts/read_claude_session.py --session <ID> |
| The exact command behind a hook audit row that records a Session ID and a command SHA-256 instead of the text | scripts/read_claude_session.py --session <ID> --find-command-sha256 <HEX>; see Command behind a hook audit row below |
| Every Agent/Task tool_use in one Session, quoted verbatim (e.g. the exact prompt handed to an independent reviewer agent) | scripts/read_claude_session.py --session <ID> --agent-prompts (optionally --agent-description-prefix TEXT); a prompt dispatched twice is listed twice |
| Which session launched a program in a known time window, across sessions | scripts/analyze_sessions.py tool-calls --from <ISO> --to <ISO> --pattern <REGEX> [--tool NAME]; candidates are selected by filesystem metadata before any body is read, then grouped by session; Codex rollouts are out of scope and the gap is stated in the output |
| When hook runs happened across sessions — outcome, duration, exit code per run (e.g. "why was session startup slow", "did this guard fire today") | scripts/analyze_sessions.py hook-events --from <ISO> --to <ISO> [--pattern <REGEX>] [--event NAME]; same candidate-first narrowing as tool-calls; forked-session copies of a run collapse to one; Codex rollouts are out of scope and the gap is stated in the output |
| The user's recent words, including human queued prompts | scripts/extract_user_messages.py |
| A conversation or quote by keyword | scripts/history_index.py recall --mode bm25, then the exact-session reader |
| Prior work whose wording may have changed | scripts/history_index.py recall after checking index status |
| A topic with no known Session ID | scripts/history_index.py status, then recall; state index coverage and freshness |
| How identified sessions or a physically bounded project ended | Follow Inspect session endings in references/workflow_examples.md |
| A deleted/overwritten file preserved in Claude file-history records | scripts/recover_content.py |
| How often each Skill ran and who started it (user command, model after the user named it, model unprompted), e.g. to decide model-visible vs user-invocable-only | scripts/skill_usage_ledger.py index, then report (Claude and Codex together); see Skill usage ledger below |
| Kimi CLI sessions | history_index.py recall --provider kimi, then read the named session's wire.jsonl; see Kimi CLI below |
| Continue a verified Claude session | Stop reading and invoke daymade-claude-code:continue-claude-code-work |
Three criteria for finding "that X we made before" (artifact-type target)
When keyword search fails, switch to these three criteria; do not broaden the terms and force a search:
- The time constraint is the first sieve. If the user gives a time window ("the last couple of days", "this week") → first filter the filesystem by mtime (
find <root> -newermt '<日期>' -name '*.html'), narrowing the candidate set to a few dozen entries before ranking by form. A keyword search of the whole repository should come after the time filter, not before it. - The session scratchpad must be within the search surface(
/private/tmp/claude-$(id -u)/**/scratchpad/, on macOS single-user it is usuallyclaude-501). The default output destination for Claude sessions is there, not in the workspace—"the X we wrote before" has a considerable chance of lying in the scratchpad. When looking for artifact-type targets, explicitly include it. - The recovered version ≠ the final version. Recovering a file from session jsonl (taking the content of Write) only gets some full write; subsequent incremental Edits are not automatically merged in—the recovered artifact may be only a fraction of the real file (measured: a 28KB recovered fragment vs a 2.45MB complete version). When after opening/rendering it you feel it "doesn't look like the target," first suspect "what I got is not the complete version" (is the file still at its original path, are there any subsequent Edits in the session), and do not directly rule out the target.
War story (2026-09-18): looking for "the clustering webpage made before," more than a dozen rounds of whole-repository keyword search yielded nothing; the target had been in the 9-13 session scratchpad all along (favorites-ledger.html, 2.45MB), and the recovered 28KB fragment rendered with bare styling was misjudged and ruled out; it was only located after the user gave the exact path.
The requested output wins over the background story. If the user asks for a chronological table of their raw inputs, return that table; do not replace it with a topic analysis because their motivation mentions an incident.
Evidence surface and completeness
By default, discover every active Claude config home and every archive registered
in ~/.claude/history-sources.json. De-duplicate physical copies by Session ID and
content identity, and use record timestamps rather than file mtime. A result scoped
to one explicit --home is a diagnostic slice, not a completeness claim.
Treat Claude's record labels as storage metadata, not authorship proof. A top-level
type: user record can contain a command envelope, hook boilerplate, a whole pasted
document, agent-voiced text, or a system placeholder. Human text typed while the
assistant was busy can live in attachment.queued_command.prompt with
origin.kind: human; do not lose those corrections by reading only user records.
Read references/session_file_format.md when
interpreting schemas, authorship, sidechains, attachment records, compaction, or
file-history snapshots. Read
references/hybrid_history_recall.md before
building or repairing the optional BM25/vector index. Read
references/workflow_examples.md for indexed discovery,
triage, and recovery examples. Read
references/claude_session_format.md when you
need the layout rather than the message schema — where sessions live on disk, how
project paths are normalized into directory names, the sessions-index.json fields,
and the compact_boundary markers that tell you a transcript was summarized rather
than truncated.
Commands
Resolve every script relative to this SKILL.md; do not search the machine for a same-named helper or recreate a JSONL parser inline.
Indexed discovery
<skill-dir>/scripts/history_index.py status
<skill-dir>/scripts/history_index.py recall '<topic>' --provider claude --mode bm25
Expected output: candidate Session IDs and index coverage. This is topic discovery, not a complete recent-session inventory. If a complete list is needed, report that the current bundled inventory would scan raw bodies.
Exact Session evidence
<skill-dir>/scripts/read_claude_session.py --session <SESSION_ID>
# Add this only when the caller intentionally wants to restrict lookup to one workspace.
<skill-dir>/scripts/read_claude_session.py --session <SESSION_ID> --project <workspace>
Expected output: # Claude Code Session Evidence Briefing, session identity,
compaction boundary, chronological user/assistant handoff, queued human prompts,
end reason, unresolved calls, subagent state, files touched, memory, and current
workspace state. The exact reader always parses every physical Session record,
including records before compaction; --full only removes output character
clipping. It checks active and registered-archive copies, accepts only identical
or strict append-only supersets, and fails visibly on divergent copies, multiple
Session identities, a missing record-level Session identity, malformed JSONL, or
unreadable bytes. With an exact Session ID and no --project, it searches every
project across the discovered active homes and registered archives; an explicit
--project remains a strict scope. A filename alone never proves Session identity.
Read Local runtime and working-directory evidence
before interpreting task endings, local-command output, or cwd receipts.
Command behind a hook audit row
Hook audit logs often store sid=<SESSION_ID> cmd_sha256=<HEX> rather than the
command text. Resolve a row to the tool call that produced it:
<skill-dir>/scripts/read_claude_session.py --session <SESSION_ID> --find-command-sha256 <HEX>
Expected output: a # Tool calls whose command has SHA-256 … heading, the number
of transcript files and tool calls examined, the match count, and for each match
the file and line, timestamp, record uuid, tool name and id, cwd, and the full
command in a fence. Exit 0 means at least one match, 1 no match, 2 a malformed
digest or missing --session. The digest is SHA-256 over tool_input.command
as UTF-8. A hook event raised inside a subagent carries the parent Session ID,
so the lookup also searches <SESSION_ID>/subagents/*.jsonl; a main-transcript-only
search misses those calls. Unlike the briefing, the lookup skips and counts
unparseable lines so it still works on a Session that is being written; a zero
with skipped lines does not prove the call is absent.
Indexed content search
<skill-dir>/scripts/history_index.py status
<skill-dir>/scripts/history_index.py recall '<keyword>' \
--mode bm25 --provider claude --exclude-session <CURRENT_ID>
Inspect the index's provider coverage and last indexed time. Open only matching
Session IDs with the exact-session reader to verify original records. Indexed
recall returns ranked prose candidates, not a census of thinking, tool results,
attachments, or unindexed records. The raw analyze_sessions.py search entry
is disabled for live stores: its date flags filter after reading the files.
Use --role user or --role assistant to narrow the stored role, and repeat
--phrase '<literal text>' to require exact substrings. These filters constrain
both BM25 and vector candidates; --terms constrains only the FTS leg. Returned
record_key, source_kind, and human_authorship describe provenance, not an
authenticated human speaker. Inspect the original record before saying “you said”.
Codex tool results are outside this prose index; hand a selected Codex candidate
to read-codex-history's original tool/record evidence route. Export original
values without automatic content redaction. A result about a neighboring fact
does not answer the requested proposition.
Human-input export
<skill-dir>/scripts/extract_user_messages.py \
<persistent-output-base> --days 7 --group-by session
This produces Markdown and HTML. It separates storage pollution from human prose and recovers queued prompts. Preserve timestamps, duplicates, and Session boundaries; do not add a second thematic classification unless asked.
Deleted-content recovery
Recovery writes files, so keep it separate from ordinary reading. First run the recovery report against the exact Session file, review every proposed destination, then write only after the user asked to recover content. Never restore directly over the current project tree.
Skill usage ledger
Answers "which Skills do we actually use, and who starts them". It is an
incremental index (~/.claude-history-index/skill-usage-v1.db): the first
index parses every Claude and Codex session once, later runs re-parse only
files whose size or mtime changed. Reports read the ledger, never raw history.
python3 scripts/skill_usage_ledger.py index # --no-codex to skip Codex
python3 scripts/skill_usage_ledger.py report --override user-invocable-only
python3 scripts/skill_usage_ledger.py report <skill> [<skill> ...] --since 2026-06-01 --until 2026-09-24
python3 scripts/skill_usage_ledger.py status # freshness + what is not covered
Columns: user_command (user typed /X, or Codex $X), model_named (the
model invoked X and the latest human prompt contained X's name), model_auto
(the model invoked X unprompted), last_model_use, blocked (Claude refused a
model call because X is not model-invocable — the model reached for it and
could not use it; not counted as use), and the Skill's current skillOverrides
value. --override STATE also lists Skills in that state with zero recorded use.
Rows group by bare name, which is what skillOverrides keys on; identities
lists the qualified forms merged into a row. More than one namespace there
(review, suite:review) means same-named Skills were counted together — rerun
with --exact to split them.
Read these limits into every conclusion:
- A Skill that is user-invocable-only cannot be model-invoked, so its
model_autocount stops at the day it was hidden. Compare use before and after that date, not the lifetime total. - Codex model use is inferred from the model reading one
.../X/SKILL.md; a session that edits or audits X's SKILL.md with plain shell reads looks the same. Several SKILL.md files read by one command are recorded asbulk_readand excluded. - Not covered: Skills invoked inside Claude subagents, Kimi CLI, and Codex Skills followed from memory without a read. Zero recorded use means "not seen in the covered stores", not "never used".
Read-result contract
Every answer must state:
- Sources read — active homes, registered archives, exact Session files.
- Coverage — Session IDs and internal time window.
- Result — raw chronology or matching evidence, in the requested format.
- Gaps — unreadable files, missing parent/attachment bytes, excluded sidechains, or any scope that was not searched.
“Not found” means “not found in the stated coverage,” never “never happened.” Indexed recall omits some record types. State that limit and the index frontier; do not present zero ranked hits as a complete label census. Do not call a compact summary verbatim history; it is a continuation aid and must be checked against raw records and the current workspace for load-bearing claims.
Before writing any negative or absolute claim ("never said," "never appears," "could not have happened," "impossible to satisfy"), clear all three:
- Every record type, not only user/assistant text. A grep for the literal
string must also cover
attachment(queued_command.prompt),tool_result, andthinking— a record's top-leveltype/isMetaalone does not prove or disprove human authorship. Classifying hits bytype:user vs assistantwhile skippingattachmentis exactly how a real mid-turn human command gets reported as never having been said. - The cheap next step before "unrecoverable." Use indexed recall or re-read an already identified Session if that can close the gap. A boundary you have not tested is not evidence of a boundary.
- A contradicting firsthand account reopens the question; it does not lose to your reading. If the user states they did something and your evidence says otherwise, treat the conflict as a signal to redo (1) and (2), not as a result to defend.
If a tool result states it was truncated or paginated ("showing lines X-Y of Z... do not answer from this page alone"), that warning is binding: read the remainder before any conclusion that depends on it.
Guardrails
- Keep ordinary read modes read-only.
- Do not run
claude --resumeorclaude --continue. - Do not use file mtime as conversation chronology.
- Do not run a raw whole-history scan. Use the index, or first select an exact Session. A date filter applied after reading every file does not bound work. If index coverage is incomplete, report the gap instead of scanning it.
- Do not share raw history outside the local machine without explicit user approval; it can contain credentials and private business context.
- Do not report a search as complete after a timeout or malformed source.
- Do not assert a negative ("never said," "never appears," "impossible to satisfy") without clearing the checklist in Read-result contract.
Surface contract
First use in a session: run python3 scripts/surface_version.py once and note
the 12-char fingerprint — the sha256 of this skill's scripts/**/*.py code
surface. If it differs from the fingerprint you last saw for this skill, the
code changed under you: re-read this SKILL.md and the references from disk
before acting on in-context echoes of them. The fingerprint covers code only;
documentation edits do not change it.
CC behavior claims
references/cc-behavior-claims.json is the per-release ledger of every
"Claude Code behaves like X" assertion this skill depends on (Read row
numbering, tool_use/tool_result ordering, AUQ answer shape, interrupt markers,
plan bindings, sidechain semantics). python3 scripts/verify_cc_claims.py --fixtures is the CI gate — exit 1 means the implementation, a fixture, or
the ledger broke a claimed shape. --corpus <dir> re-verifies the ledger
against live transcripts: exit 2 means the schema drifted, which is a
correctness task, not a test failure — update observed/last_verified/
evidence or fix the implementation, and name the drifted claim(s) in the
CHANGELOG.
Router and legacy compatibility
daymade-claude-code:local-conversation-history is the cross-provider router. It
sends Claude reads and every Kimi CLI request here, and does not replace this
Skill's identity or evidence contract. New Codex requests route to
daymade-claude-code:read-codex-history.
Kimi CLI is a live surface of this Skill, not a legacy one. It has no reader
of its own. Use indexed recall for discovery, then inspect the named session's
wire records. A Kimi question answered from Claude data alone produces a false
"never happened". Home resolution order is --kimi-home > KIMI_HOME > ~/.kimi-code.
When that default home does not exist, the store is not missing — it is somewhere else, and the tools say so. The Kimi desktop client bundles the CLI inside its own Electron runtime and keeps sessions under that runtime rather than in the home directory, so a machine with hundreds of real conversations answers the default path with nothing at all. The inventory prints the home it tried as a diagnostic line; read that line before reporting an empty Kimi result, because "no home found" and "no conversations" are different findings.
Locate the real home instead of guessing, in this order:
- Ask the recall index, if one was ever built with Kimi in scope.
scripts/history_index.py statusprintsscope.sources, and aprovider: kimientry there carries the absolutehomeit was indexed from. One command, no searching. It only answers after Kimi has been indexed once, so it is the fastest path on a configured machine and silent on a fresh one. - Read the desktop client's user-data directory off its running process.
An Electron app carries
--user-data-diron its command line (ps ax | grep -i <client>). The bundled CLI is not directly beneath it: the home is<user-data-dir>/daimon-share/daimon/runtime/kimi-code/home. Note the finalhomesegment —.../runtime/kimi-codeis the CLI install and fails the step-4 test, while itshome/child is the store. - Follow a transcript record's
meta.sourcePath. The client mirrors conversations to<user-data-dir>/daimon-share/daimon/agents/<agent>/memory/transcripts/days/<YYYY-MM-DD>/conv-*.jsonl, and each record'smeta.sourcePathis the absolute wire path it came from, which contains the home. Do not look inkimi-agent/conversation-archive.json— that file holds titles and timestamps only, with no path of any kind. - Confirm before using it. A real Kimi home contains
session_index.jsonland asessions/wd_<workspace>_<hash>/tree; pass it as--kimi-homeonce both are present. This test is what tells the store apart from the CLI install directory one level up.
Two schema facts that decide whether a located store reads correctly. Newer
builds drop id and cwd from each session's state.json and keep the working
directory only in session_index.jsonl, so a session's project must come from
that map rather than from its own state file. And the same sessions/ tree holds
internal agent runs alongside real conversations, separated only by a directory
prefix — title generation, vault maintenance, and skill summarization are machine
chatter, not history, and on a real store they outnumbered the genuine
conversations.
Kimi coverage check. Confirm that
history_index.py status includes Kimi and read its freshness boundary.
An unindexed or newer Kimi session remains unknown to recall.
Reading one located Kimi session has no bundled command. read_claude_session.py
resolves Claude session files only and exits non-zero on a Kimi session ID. The Kimi
surface is inventory plus indexed recall; to show a conversation's contents, read the
session's agents/<agent>/wire.jsonl directly and interpret it with the record types
above. Say that this is a direct file read rather than presenting it as the same
verified reconstruction the Claude reader produces.
The former claude-code-history-files-finder also exposed optional Codex and Kimi
branches. Its original instructions are retained in
references/legacy_cross_provider_workflow.md
as a frozen snapshot for migration and regression evidence only — read it for what
the old contract said, never as a description of what ships today.