Skip to content

fix(audit): parse only the final turn of long transcripts - #263

Draft
DorianZheng wants to merge 1 commit into
mainfrom
worktree-cuddly-forging-sun
Draft

DorianZheng wants to merge 1 commit into
mainfrom
worktree-cuddly-forging-sun

Conversation

@DorianZheng

Copy link
Copy Markdown
Member

TL;DR

Sessions past 64 MiB no longer block every Stop: the gate parses only the final turn, read back from the end of the transcript.

Design doc: #262

Documentation: ARCHITECTURE.md and hooks README.

How it works

  • Before: _verdict_audit_snapshot_final_turn JSON-decoded every line from byte 0 to find the last real user message, at about 5 MiB/s. Callers capped transcripts at 64 MiB to fit its 15 s alarm, so past the cap every Stop got an incomplete snapshot that the auditor must FAIL.
  • Now: it reads 64 KiB blocks back from the end, decodes lines newest first, and stops at the first real user message with the same predicates. The kept records go through the unchanged per-record code, so snapshots stay byte-identical.
  • History before that message is only hashed. Pass 1 hashes the whole file for source_sha256 and must see the same final-turn bytes that were decoded; pass 2 re-hashes, as before.
  • Bounds: verdict_audit_final_turn_max_bytes (64 MiB) limits decoded bytes and replaces the three caller copies. There is no whole-file ceiling, so the 15 s alarm bounds hashing. The custom extractor path keeps its 64 MiB whole-file read.
  • Trade-off: a malformed line before the last real user message no longer fails the read; NUL anywhere still does.

Verification

  • Red, production reverted to main: make test:plugin TEST_FILTER=preflight-verdict-check.test.sh → 231 passed, 3 failed; the 66 MiB session ended on extract truncated-block.
  • Green, with the fix: the same command → 234 passed, 0 failed.
  • Affected suites (verdict-audit, stop-gate, tldr-gates, concise-writing, auditor-override, audit-history-verdict, continue-timed-prompts): make test:plugin TEST_FILTER=<suite> → 0 failures each.
  • make test:writing test:parity test:architecture → exit 0.
  • Old versus new reader on 29 local transcripts under 64 MiB → byte-identical snapshots.
  • Cost on macOS, Perl 5.34: a 52.8 MiB transcript went from 10.2 s to 0.28 s; an 81 MiB transcript over the old cap went from rejected to 0.50 s.
  • Linux and Windows are unverified.

The Stop gate's snapshot reader decoded the whole transcript with JSON::PP
to find the last real user message, at about 5 MiB/s. To stay inside its
15 s alarm, callers capped the file at 64 MiB. Transcripts only grow, so
once a session passed the cap, every Stop produced an incomplete snapshot
that the auditor must FAIL, blocking the session for good.

Scan back from the end of the file instead and decode only the final turn,
at most 64 MiB (verdict_audit_final_turn_max_bytes). Earlier history is
hashed for source_sha256, and the decoded turn bytes must hash the same in
the full-file pass, so the snapshot contract and its stability checks stay
as they were. A 52.8 MiB transcript drops from about 10 s to 0.3 s.

Rejected: raising the cap and alarm (cost still grows with the session),
JSON::XS when installed (not core Perl), recording the turn offset at
UserPromptSubmit (new cross-hook state), and last_assistant_message alone
(no tool evidence for the auditor).

Design: #262
@coderabbitai

coderabbitai Bot commented Oct 24, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

TL;DR

The PR author must acknowledge the current diff and description before requesting review.

Author review acknowledgment

Awaiting the PR author's acknowledgment.

@DorianZheng: read the current diff and description, then check:

Apply boxlite-writing. Does How it works explain the diff's mechanism or rationale?

Use the form best suited to the change. Check drafts too, and repeat this review
after description edits. Post this as a new PR comment:

/reviewed 996b1eb40a6f5349ddf23dd3f7c21da1f12d3be5

Unacknowledged PRs are converted to draft. After this check passes, click Ready for review when you want reviews.
Only a new, unedited comment from the PR author counts. A new commit requires a new acknowledgment.
This records the author's acknowledgment of the commit, not an automated judgment of the description; maintainer approval is separate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant