a73x

20d59fae

docs(e2e): say what the bell leg's cleanup and its zero-OSC check actually do

a73x   2026-08-15 21:28

Commit message
docs(e2e): say what the bell leg's cleanup and its zero-OSC check actually do

Three comments on the bell leg, two of which claimed coverage that is not
there.

The trap's rm said the daemon capture is "rm'd here and never mid-suite,
so the leak sweep still has it to read". It is rm_swept mid-suite, in the
leg's own success path. The behaviour is safe for a different reason than
stated — rm_swept banks a LEAK verdict into $LEAKBANK before deleting,
and the sweep reads the bank for what no longer exists — so the trap line
is a backstop for failing runs, not the only deletion. The tp1 leg
sweeps its daemon capture the same way, so this is that leg's convention
and the comment now points at it.

The zero-OSC check was credited with covering the session's own
BEL-terminated OSC 133 marks. There are none to cover: the daemon runs
--shell /bin/sh, and shellint.detect matches the basename against zsh,
bash and fish with no symlink resolution, so sh yields .other and
no_injection. A true conclusion — the count is unaffected, because the
zero-OSC check is what guards it — resting on a mechanism that does not
run. Dropped.

What the check genuinely does not cover is now written down instead: a
BEL painted as cell content carries no ESC] in front of it, since
paint.zig writes row.bytes verbatim. It cannot arise here, because
ghostty-vt executes C0 rather than storing it and this session paints a
prompt, an echo and BELLDONE. The two checks are what make the count
unambiguous; they are not a proof that no other path to a BEL exists.

And "one write and therefore one pty read" was a derivation the write
does not license. Not splitting five bytes is the line discipline's
behaviour, not the writer's guarantee, so the note now hedges and says
how a split would present: `2 bells ... want exactly 1` reads as a
chunking accident before it reads as lost coalescing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

test/e2e.sh
Old New
@@ -806,10 +806,17 @@ cleanup() {
806 # ...and its other half: the paste capture and the file nvim wrote, which 806 # ...and its other half: the paste capture and the file nvim wrote, which
807 # IS that scenario's assertion rather than a log beside it. 807 # IS that scenario's assertion rather than a log beside it.
808 rm -f "$OUT.paste" "$OUT.paste.err" "$OUT.paste.log" "$OUT.pasted.txt" 808 rm -f "$OUT.paste" "$OUT.paste.err" "$OUT.paste.log" "$OUT.pasted.txt"
809 # ...and the bell leg's five: the four the other side-channel legs have, 809 # ...and the bell leg's five, plus the daemon capture and socket, since
810 # plus the daemon capture, since this leg runs a daemon of its own. The 810 # that leg runs a daemon of its own.
811 # daemon capture is rm'd here and never mid-suite, so the leak sweep above 811 #
812 # still has it to read. 812 # All six are ALSO rm_swept mid-suite on success, which is not a conflict:
813 # rm_swept banks a capture's LEAK verdict into $LEAKBANK before deleting
814 # it, and the sweep above reads the bank for everything that no longer
815 # exists. So the daemon capture's verdict survives its own deletion, and
816 # this line is the backstop for the runs where that rm_swept never ran —
817 # the failing ones, which are exactly the runs somebody is looking at. The
818 # tp1 leg sweeps its own daemon capture the same way (see its rm_swept),
819 # so this is that leg's convention rather than a new one.
813 rm -f "$SOCK24" "$OUT.d21.d" \ 820 rm -f "$SOCK24" "$OUT.d21.d" \
814 "$OUT.bell" "$OUT.bell.err" "$OUT.bell.log" "$OUT.bell.sh" 821 "$OUT.bell" "$OUT.bell.err" "$OUT.bell.log" "$OUT.bell.sh"
815 # M14 handoff. The shim, both runtime dirs (each holding its daemon's 822 # M14 handoff. The shim, both runtime dirs (each holding its daemon's
@@ -3046,20 +3053,27 @@ ok "a paste into nvim keeps its indentation"
3046 # attaching there is then told about. On a fresh daemon nothing sets a 3053 # attaching there is then told about. On a fresh daemon nothing sets a
3047 # title and nothing copies, so neither OSC has an occasion to be built. 3054 # title and nothing copies, so neither OSC has an occasion to be built.
3048 # 2. The zero-OSC check below, which is what turns that argument into a 3055 # 2. The zero-OSC check below, which is what turns that argument into a
3049 # measurement. With no `ESC]` anywhere in the capture, every BEL in it 3056 # measurement. With no `ESC]` anywhere in the capture, no BEL in it can
3050 # is a bare bell — there is no sequence left for one to be terminating. 3057 # be terminating a sequence.
3051 # 3058 #
3052 # The session's own OSC 133 marks are BEL-terminated too and do not reach 3059 # That leaves one way a BEL could arrive with no `ESC]` in front of it, which
3053 # here, because the engine consumes them into cmd_state rather than 3060 # check 2 would NOT catch: as painted cell content, since paint.zig writes
3054 # forwarding bytes. That is not an assumption either: check 2 would see them. 3061 # row.bytes verbatim. It cannot arise here — ghostty-vt executes C0 rather
3062 # than storing it, and this session paints a prompt, an echo and BELLDONE —
3063 # but the two checks above are what make the count unambiguous, not a proof
3064 # that no other path to a BEL exists.
3055 "$MUXD" run --sock "$SOCK24" --shell /bin/sh > "$OUT.d21.d" 2>&1 & 3065 "$MUXD" run --sock "$SOCK24" --shell /bin/sh > "$OUT.d21.d" 2>&1 &
3056 D21PID=$! 3066 D21PID=$!
3057 wait_sock "$SOCK24" "$OUT.d21.d" "bell daemon socket never appeared" 3067 wait_sock "$SOCK24" "$OUT.d21.d" "bell daemon socket never appeared"
3058 3068
3059 # Five rings in ONE printf, which is one write and therefore one pty read and 3069 # Five rings in ONE printf, which is one write and so in practice one pty read
3060 # one drain. Split across two writes they would be two chunks and two frames 3070 # and one drain — the line discipline is not going to split five bytes short of
3061 # — correctly, since coalescing is per drain — so the single printf is what 3071 # a full output buffer, but that is a property of the read side rather than
3062 # makes "one" the right answer rather than a number that depends on timing. 3072 # anything the single write guarantees. Split across two writes they would be
3073 # two chunks and two frames, correctly, since coalescing is per drain; the
3074 # single printf is what makes "one" the right answer here. A split would show
3075 # up as `2 bells ... want exactly 1`, so read that as a chunking accident
3076 # before reading it as lost coalescing.
3063 # 3077 #
3064 # Emitted by a FILE, never typed, for the M12 reason spelled out on the 3078 # Emitted by a FILE, never typed, for the M12 reason spelled out on the
3065 # clipboard leg: the shell echoes what is typed, so a needle that could 3079 # clipboard leg: the shell echoes what is typed, so a needle that could