Repository navigation
Fix #1891: Forking an ignored session (claude --fork-session, codex fork) records the whole parent conversation under the new id; a SessionStart hook can close it on both clients - #1897
Conversation
`hyp session ignore` holds one key per opt-out, the raw session id, and `claude --fork-session` mints a new one over a copy of the whole parent conversation. An opted-out conversation was therefore recorded in full under the fork's id, live and through backfill, and the product pushed the problem onto the user. Claude exposes no parent id, so the parent is identified by what the copy keeps: each copied line's `uuid` is carried over unchanged while `sessionId` is rewritten. `hyp session ignore` now stores the leading uuids of the session's transcript as a fork fingerprint beside the id marker, and the managed hook matches any one of them on SessionStart (`source: "fork"`) and UserPromptSubmit, adding the fork's id through the ordinary `hyp session ignore` path. LLP 0419 records the decision; LLP 0403's fork sentence gains the forward-ref. The Codex lane of the issue is not implemented and is named as open in LLP 0419 #scope. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… key Review of 3dec943 (LLP 0419). - A fingerprint is now written only once the id marker is on disk. The Cursor and OpenCode listeners answer the control route over a process-lifetime `Set`, so a confirmed 200 from one of those wrote a fingerprint with no marker beside it: an exclusion no shipped verb can remove, and a fork guard that re-spawns `hyp session ignore` on every prompt of that session without ever settling. - `FINGERPRINT_TOKEN` is a canonical uuid rather than any 8-128 character token. The any-match rule reads one shared value as proof of a copied conversation, which holds only while the values are random; a short or constant token in that set would match every fingerprint at once and ignore every session on the machine. - One unreadable fingerprint no longer aborts the whole match walk, so a concurrent unignore cannot cost the fingerprints after it their turn. - The spawned opt-out passes the id after `--`, so a client-supplied id opening with a dash is not read as an unknown flag on every prompt. - LLP 0419 records the marker precondition, the uuid bound, and the limit a reviewer found: unignoring one member of a fork family leaves the siblings' fingerprints matching, so the guard re-adds the id. That residue is over-exclusion, never a recorded conversation the user opted out of, and what `unignore` should mean for a conversation filed under several ids is left to its own change. The acceptance procedure's step 12 is corrected to unignore the whole chain. - 0419 #verification names `claude_fork_session_optout`, the procedure this change adds, rather than only `claude_otel_shape_check`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Verdict: approve with fixes landed. Full PR review of The mechanism is sound and the fail-closed direction is right throughout. Nothing found could cause the defect this PR closes (an opted-out conversation recorded under a fork's id). Two of the fixes close paths that could have destroyed capture for a session nobody opted out of, which review priority 1 names as worse than the bug. Baseline confirmed at the merge base myself: 1. A fingerprint could be written with no marker beside it, looping the guard forever - high, fixed
Fixed: the marker, not the reply, arms the fingerprint ( 2.
|

Feature or issue
hyp session ignorepersists exactly one key per opt-out, the raw session id, and every recorder drops on that id alone.claude --resume <id> --fork-session(and/branch) mints a new id over a copy of the whole parent transcript, and the fork's first request replays the full parent history as context. So ignoring a conversation and then forking it recorded all of it under the fork's id, live and through backfill. Issue #1891 verified the mechanism locally: forked transcripts keep every copied line'suuidunchanged and rewrite onlysessionId(9 transcript pairs in~/.claude/projectsshare their leading uuids line for line, one of them a three-way chain). The product knew and pushed it onto the user (hypaware-reference/SKILL.md, LLP 0403 line 20); nothing enforced it.Solution
src/core/control/session_ignore_store.js).hyp session ignorenow also stores a fork fingerprint beside the id marker undersession-ignores/: the leading (at most 8) transcript line uuids, read from thetranscript_paththe session-context channel already records, and nothing else. The loader learns the.fingerprint.jsonsuffix while still rejecting any other filename as corruption; fingerprint bytes count toward the existing 4 MiB ceiling, a write that would overshoot it is refused (overshooting would make the next load refuse the whole store and stop capture everywhere), anddeleteremoves marker and fingerprint together. No existing drop path changed.claude/src/hook_command.js). The managed hook, onSessionStartwithsource: "fork"(repeated idempotently onUserPromptSubmit), matches any one uuid of the new transcript against any stored fingerprint and, on a hit, adds the new id by runninghyp session ignorein this same install: the same persistent marker plus POST to every live control route (LLP 0256 #cli-posts-to-both), nothing reimplemented. Any-match covers a fork of a fork with no record per generation. The ordinary path costs onestatplus onereaddirthat stops at its first entry. The receipt now states whether fork protection was armed or is UNCONFIRMED for this id, in prose and asfork_protectionin--json, because silence would read as armed.test/plugins/claude-fork-guard.test.jsdrives the real verb against a real control route over a realSessionIgnoreSet, then the real hook against a forked transcript; before the fix, "the live recorder drops the fork" isexpected: true, actual: false, andunignore removes the fingerprintisexpected: 1, actual: 0. The false-positive direction (a fork of an un-ignored session must not be ignored) has its own case and passes both before and after, so it is a guard against silently destroying capture rather than a symptom of the fix.npm test: 6924 tests, 3 failures, identical to the merge base'shyparquet-floor-pintrio from stale localnode_modules;npm run typecheck: the one pre-existingsrc/core/otlp/server.js(85,51)error, unchanged; smokesclaude_telemetry_session_ignore,gateway_claude_capture,claude_telemetry_capture,hypignore_capture_dropall green.SessionStarthook is installed into Codex'shooks.json,hyp leavereverses nothing new, and no hook-trust requirement is surfaced. That lane needs a second managed-file perimeter on a client whose attach probe is single-file today, and its match key has a container-versus-thread grain (session_meta.forked_from_idvs the id the recorder stamps) that no fixture on this machine can settle; it is left to its own change rather than landed unproven. It is named as open in LLP 0419 #scope and the skills still tell users acodex forkneeds its own exclusion. Also out of scope per the issue: purging content recorded before the ignore, and retroactively excluding forks made before the parent was ignored.Extended-by:forward-ref (editorial only, nothing it settled was edited). Bothhypaware-referenceand bothhypaware-privacyskill copies anddocs/PRIVACY.mdare updated, the outdatedcodex/src/index.js"Codex has no SessionStart context-injection hook" comment is corrected, and aclaude_fork_session_optoutacceptance procedure is added for the two upstream facts no fixture can settle.Code: +622 / -15 lines
Fixes #1891