Skip to content

Read the numbered terminator Claude shows after an export's final LF - #3570

Merged
trissim merged 3 commits into
mainfrom
work/claude-code--P876
Oct 28, 2026
Merged

trissim merged 3 commits into
mainfrom
work/claude-code--P876

Conversation

@trissim

@trissim trissim commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • 3 commits. Commits 1-2, 2 files: claude_protocol.py +7/-1, tests/test_monitor_delivery.py reworked. The second commit makes truncated-final actually truncate: [:100] on a final part under 100 characters cut nothing.
  • Live read-file confirmation went from 0 of 1 messages (358869593c89, stuck at emitted on installed b685915) to 8 of 8 parts on the exact captured shape. The unrun controls below are what would show that.
  • str.splitlines calls on export text in the test module: 8 → 0.

The bug

The one-event delivery from #3546 exports a large message as one JSON string per line, and every export ends in LF. Claude's Read numbers the empty row after that final LF. On the live record (...a8ec5ff4d019a77d"\n9\t), ReadResultObserved.frames() decoded 9 frames: 8 MonitorPart plus 1 ForeignMonitorFrame for the empty row, so 8 parts. LineMonitorBridge.read_export's all-lines gate (len(parts) != len(frames)) therefore refused the notice. Every read-file delivery was dead on arrival.

Why tests were green: the fake shown() built Read output from splitlines(), which never has that row. Every read-file test checked a Read Claude does not produce.

Mechanism changed

ReadResultObserved.frames() (cli_bridges/claude_protocol.py:679) still splits on physical LF only and still parses the leading numbered block. It validates the whole number sequence, terminal row included, with the existing consecutive rule. Only then does it pop at most one final row whose payload is exactly empty, and never when that row is the only row, so an empty Read still proves nothing. Interior empty rows, a second terminal empty row, gapped terminal numbers, foreign or truncated final payloads and failed Reads all stay unconfirmed.

read_export's reference, identity, part and all-lines gates are unchanged.

Tests

  • shown(text, start) now numbers the physical LF rows of real text. Whole-export callers pass export.read_text(newline=''), so they carry the terminator; partial pages pass explicit excerpts, with the terminator only when they reach the end.
  • Added the 8-part whole-read case (the live shape).
  • Added refusal controls: truncated-final, interior-empty, two-terminal-empties, terminal-gap, foreign-terminal.
  • last-page now reaches the end with the terminator; repeated-page stops before it.
  • Added a parser-level family over the row grammar, including a non-1 page start, plus a terminal-gap case.

Assumed

  • The captured Read shape is one current Claude Read version; it is not verified for every provider or version.
  • Exports never contain an empty record, so a final empty numbered row is always the terminator.

Commit 3: the shadowed delivery reader and the duplicate voice carrier

stored_attempts defined twice. tests/test_monitor_delivery.py:59 declares async def stored_attempts(bridge). My #3546 busy-begin test (fbe666a) then declared a module-level def stored_attempts(db, bridge, message) at line 217, so Python kept the second definition. The five async callers (lines 97, 108, 432, 449, 832) were calling a three-argument sync function with one argument; that is the main-CI stored_attempts() missing 2 required positional arguments. The second helper is deleted. Its one caller now selects HarnessDelivery directly on the held SQLite connection (no coordinator read under BEGIN EXCLUSIVE). Census over src/tools/tests: one stored_attempts definition and its 5 async callers; no other module declares or calls the name.

The voice file sent twice per unit. CheckedProblemOrder.work_instructions (todos.py) appended CommanderPackage.voice() to every rendered order, while UnitInstructions.render (unit_instructions.py:28) already puts the same file in each unit's system prompt. In the 6 h before this change that copy was 178 KB of 502 KB of direct order bytes (29 of 70 orders). The copy and the now-unused import are deleted. test_work_instructions_carry_the_owner_voice tested the deleted copy and is deleted; the voice joins the sources test_unit_prompt_carries_every_rule_under_its_audience already requires of the real carrier.

The decoder (cli_bridges/) is byte-identical to fbc4b10.

Results

  • Source OK is recorded by root on exact head fbc4b10.
  • Root reran the corrected truncated-final export case on fbc4b10: passed in 5.41 s.
  • The 15 unchanged selected cases passed on 2994ffc, whose src/ bytes are identical to fbc4b10.
  • Hosted gates on d4c1eaf: debt-src SUCCESS on rerun job 123103139074 (2m42s, after Toad 61fd landed), debt-tools SUCCESS, agent-comms/review SUCCESS. The earlier red ClientTypeErrors came from a stale cached base measurement, not this change. That cache defect is not fixed here; P872 (One declaration per bus read: UI, MCP and owner-socket reads derive one operation #3538) owns it.
  • Commit 3 controls: root ran six selected controls on d4c1eaf, all passed in 11.66 s. Source OK is recorded on d4c1eaf.
  • The P883 hold no longer applies to this head. Promoted ready with auto-merge through the queue.
  • Source approval is not live delivery. No live Read, replay, install or merge has happened; a fresh live message after install is the proof.

Not run (per order)

Commit 3: the empty-journal, exact-body/reopen, wrong-complete-parts and held-BEGIN controls, plus test_unit_instructions and test_order_claims, are unrun; root selects them.
pytest, mypy, ratchets, import probes, live Read, install. I ran only py_compile and the order's AST check, which prints 1.

Open P876 targets

Stale-backlog attention and noisy replies, end-to-end latency, installed busy/idle Read cost. 358869593c89 stays historical evidence; it is never replayed.

wrote a fake of Claude's Read
fake was missing one line
every test agreed with the fake
the only thing that disagreed was reality

🤖 Generated with Claude Code

https://claude.ai/code/session_01RnH6dR85kyFKcc2tQAJLhK

Every read-file export ends in LF, and Claude's Read numbers the empty row
after it. ReadResultObserved.frames decoded that row as a ninth foreign
frame, so read_export's all-lines gate refused all eight real parts and
358869593c89 sat at emitted forever on the installed b685915.

frames() now drops exactly one final empty row, after the consecutive
numbering holds, and only when it is not the only row. Every other empty
or foreign row stays a frame the unchanged gate refuses.

The test fake shown() modelled the Read from splitlines(), which never has
that row, so every read-file test checked a Read Claude never produces. It
now numbers physical LF rows of real export text; whole reads carry the
terminator, partial pages do not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnH6dR85kyFKcc2tQAJLhK
@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

debt-src at bd6aaa3, every finding:

grew ClientTypeErrors:textual/tests/toad_frame_damage_installed_pilot.py +3: this change removes or changes an agent_comms name that client file uses; run mypy on it to see which

lines[-1][:100] on a final part shorter than 100 characters kept the whole
record, so the negative case fed a valid export and expected refusal. Drop
the last character instead and assert the result differs from the line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnH6dR85kyFKcc2tQAJLhK
My busy-begin test defined a second module-level stored_attempts(db,
bridge, message) after the async stored_attempts(bridge), so Python kept
the later one and the five async callers called a three-argument sync
function with one argument. The one held-connection caller now selects
HarnessDelivery on the held SQLite connection directly; the shadowing
helper is gone and the async reader is the module's only one.

CheckedProblemOrder.work_instructions pasted the whole voice file into
every rendered order although UnitInstructions.render already carries it
in each unit's system prompt: about a third of order bytes were that
copy. The copy and its import are deleted; the test asserting the copy
is deleted and the voice joins the UnitInstructions carrier test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnH6dR85kyFKcc2tQAJLhK
@trissim
trissim marked this pull request as ready for review October 28, 2026 21:17
Copilot AI balanced review requested due to automatic review settings October 28, 2026 21:17
@trissim
trissim merged commit 2bcce13 into main Oct 28, 2026
16 of 17 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

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.

2 participants