Machine pill indicator flickers while a full-screen app redraws (production) #227
Open
opened 2026-10-16 05:35:17 +00:00 by kayg
·
150 comments
No Branch/Tag specified
main
j470b-server-mesh-rejoin
mesh-compat-382
j444b-d381-key-wins-flood
j461b-macos-utun-reader-thread
j475b-remove-host-motion
fix-reconnect-new-link
j481-d380
dev
j479-mesh-soak
fix/j476-host-facts
j475-motion-pill-split
j470-mac-node-join
root-upgrade-d376
j474-review-merge
latency-targets-120hz
nightly-suite-shards
gotatun-pin-j469
macos-utun-reader-thread
fix-flood-echo-stall
wip/j416
motion-fast-ease-out
mesh-addhost-j259
takeover-close-terminals-d368
kbd-instant-followups
fix-trust-key-double-click
nightly-suite-partial-report
appwin-icons-j207
rusqlite-slice1
swipe-content-first-d374
mesh-mtu-381
addhost-instant-open
glass-contrast-d365-mock
glass-contrast-d365
signout-followups-442
fix-startup-toast-red
research-gotatun-matrix-j317
themes-sync-j304
fix-load-flakes
fix-nix-zig-patch
fix-compute01-browser-baseline
ratchet-size-baseline-419
predict-auto-d130
revoke-coverage-d367
nightly-suite-queue-timeout
nightly-browser-suite
fix-rustsec-2026-0459
fonts-ioskeley-obel
log-retention-d375
fix-kbd-no-anim-427
fix-dev-red-specs-2
takeover-proof-305-j205
wip/j410-server-pump
ui-consistency-d372
fix-osc52-clipboard
libghostty-vt-1.0
forget-retired-host-d369
j404-merge-426
fix-setup-lock-race
fix-touch-select-426
revoke-access-report-ui
fix-predictive-echo-specs
fix-selinux-agent-exec
wip/j395-checkpoint2
wip/j395
j391-review-scratch
ui-consistency-j195-deferred-rebased
fix-rustc-paths-421
decisions-wording-341
fix-install-suite-red
fix-dev-rtt-specs
fix-pending-removal-align
fix-privileged-skip
fix-install-suite-404
review-j368
mesh-dns-j254
revoke-access-report-d356b
revoke-privilege-round5
fix-removal-confirm
review-j360
gotatun-rotation-367
deps-refresh-340
review-j354
review-J351
review-j350
addhost-takeover-d359
revoke-report-items-347
revoke-end-sessions-d362
review-j348
size-mesh-ssh-346
size-trim-337
revoke-access-diff-d356
j259-pre-rebase-b06c5a6
j305-ended-at-start-spec
addhost-sudo-consent
fix-layout-real-specs-411
fix-shell-open-411
fix-console-hosts-373
fix-shell-exit
fuzz-host-name-353
fix-host-details-410
macos-filter-j197
research-gotatun-tricks
revoke-p3-j216l
stale-state-pill
revoke-followups-j216k
revoke-progress-j216e
backup/macos01-20261106/lume/j197
backup/macos01-20261106/lume/appwin-icons-j207-stash0
backup/macos01-20261106/claude/stash-0
backup/macos01-20261106/claude/stash-1
backup/macos01-20261106/claude/J80-macos-s1
backup/macos01-20261106/claude/J80
backup/macos01-20261106/claude/J80-slice1-20261102
backup/macos01-20261106/claude/J16b-appwin-on-dev
backup/macos01-20261106/claude/appwin-launch-idle-146
backup/macos01-20261106/claude/J80-s1-run
wip/j259
trust-285-p3c
runtime-lock-j216f
mesh-ssh-p3-j175
tun-proof-p3b
trust-285-p3
terminal-polish-j271
tun-proof-p3
ssh-install-flakes-j216i
fuzz-fixes-j264
trust-285-a
trust-285-b
restart-oversized-fixture-j198
backup/macos01-20261106/lume/appwin-icons-j207
restart-bound-j216h
up-preflight-p3-j198
backup/macos01-20261106/lume/j251
up-preflight-j198
wip/up-preflight-j198-j245-p2
wip/up-preflight-j198-j245-p1
agent-fsync-offloop-j196
wip/up-preflight-j198-j245
settings-offscreen-j231
wip/up-preflight-j198-j226-inspection
wip/j103f
themes-j206
wip/up-preflight-j198-j226
flakes-j216c
backup/macos01-20261106/claude/j197
flakes-j216b
backup/macos01-20261106/claude/j197-red
wip/up-preflight-j198-review
flakes-j216
backup/macos01-20261106/claude/appwin-icons-j207
engineering-j208b
appwin-mock-j209
toast-stack-j165
engineering-j208
decisions-d351
decisions-d350
decisions-d349
appearance-presets-mock-j129
decisions-d348
docs-audit-spec
doc-coverage-baseline-raise
decisions-d347
decisions-d346b
decisions-d346
j88h-profile
ui-consistency-j195-deferred
ui-consistency-j195
engineering-practices-j194
preferred-login-ui-j166
signin-pools-263-j174
macos-node-265-s1
decisions-d345
decisions-d344
proxy-cgroup-j164
verify-quick-fuzz-329
proxy-start-io-j168
timing-flakes-j163
up-263-s1
mcp-events-j147
verify-quick-fuzz-j158
proxy-p3-j159
agent-resource-j157-detect
wip/j103d-final
remote-cancel-hang-j155
proxy-traefik-281-j62d
j149-verify-quick
proxy-traefik-281-j62b
j150-drop-unnamed
wip/j79d-red-r4
wip/j103d-d332
rootless-toast-j142
wip/j103d-head
wip/j103d
j125c-mcp
macos-build-j136
backup/macos01-20261106/claude/j136
wip/j79d-red-cli
wip/j79d-red-server
j121-link-liveness
j118c-tracer-failures
wip/j79c-head
release-desktop-j131
j124c-signed-macos
wip/j79c-red-p3
wip/j79c-red-p23
wip/j79c-red-p22
merge-j128-check
perf-deferred-j128
sidebar-toggle-260
j127-shortcut-changed-dot
j125-mcp
j118-tracer-failures
j126-account-summary
settings-sections-mock-j92
j120-pinch-lock
sidebar-292
backup/macos01-20261106/claude/j120
taste-2026-11-03
row-actions-242-215
backup/macos01-20261106/claude/stash-0-untracked
backup/macos01-20261106/claude/J80-265-s1-run
backup/macos01-20261106/claude/J80-macos-s1-20261102
j112-canvas-commands
ui-no-overshoot-scroll
fix-recovery-restart
backup/macos01-20261106/claude/stash-1-untracked
taste-2026-11-02
size-baseline-ratchet
machine-cards-structure-j108
j109-boot-copy-mockups
fix-dev-red
wip/up-263-s1
j95b-settings-rows
proxy-traefik-281
j95-option-rows
docs-human-j91
docs-keys-palette
fix-flood-1013-270-j38d
mesh-ssh-s1
mesh-ssh-s0
addhost-d291-tunnel
test-stability-load
mesh-relay-220
repro-296
shim-thp-off-24
mesh-pins-258-v2
mesh-dns
signin-d311-merged
docs-pages
ui-size-283
fix-ui-batch-specs
wip/j12b-edge-update
gate-on-arm
fix-rename-sync-293
trust-285-j60b
fix-focus-ring-clip
integ-ui-1101
edge-update-v2
signin-d311
taste-2026-11-01
layout-per-tab-250
ui-approved-splitopt-224
ui-approved-splitopt-224-on-dev
icon-text-centre-282
ui-approved-189-212-216
research-reverse-proxy
transfer-budget-320
review-mesh-pins-258-r2
sidebar-pill-259
review-mesh-relay-220
wip/j12-edge-update-v2
nix-package
status-crash-loop-237
fix-dev-test-failures
fix-fixture-socket-path
wip/mesh-relay-220-pre-dev-advance-rebase
fix-canvas-side-clip
research-head-to-head
frosted-real-256
review-mesh-pins-258
wip/mesh-relay-220-pre-final-rebase
review-layout-per-tab-250-r2
broad-suite-triage
nix-devshell
review-status-crash-loop-237-r3
wip/nix-devshell-pre-p3
taste-frosted-real
wip/code-serve-demo-2026-10-30
wip/code-serve-glass-2026-10-30
wip/code-serve-clusters-2026-10-30
wip/code-merge-release94-dev-2026-10-30
wip/rproxy-delta-scratch-2026-10-30
wip/stale-note-257-staged-2026-10-30
wip/d241-size-staged-2026-10-30
review-broad-suite-207
wip/mesh-pins-258-pre-split
wip/mesh-relay-220-pre-rebase
mesh-measure-220
plan-211-update
review-edge-autoupdate-r3
perf24-reconnect
review-stale-note-r2
release-prod-live
d241-shell-marker
review-mesh-pins-server-258
review-revocation-r3
review-setup-token-r2
review-sync-journal-r3
wip/review-mesh-pins-258-prune-review-1995fee46
frosted-scroll-256
sync-journal-r3
revocation-per-account-wip
review-tokio-asyncfd-0386
review-sync-p3s
wip/stash-8by64-stash0-2026-10-30
mesh-drag-r2
setup-token-data-wip
release-9-4
auth-busy-1013
mesh-view-overlap
sync-p3s
review-auth-busy-wake
review-readd-r8-wip
mesh-drag-bounds
audit-auth-93
app-windows-stream-start
review-authority-cap-r3
app-windows-r7-measure
readd-minimal-r6
readd-minimal-r5
app-windows-r7-soak
readd-minimal-r4
app-windows-r7
app-windows-r6
readd-minimal-r3
readd-minimal-r2
readd-minimal
merge-release8-dev
review-busy-page-liveness-r2
review-busy-page-liveness
mesh-view-trim
review-authority-read-retry-r2
review-remove-uninstall-r2
release-retry-reattach
release-retry
review-remove-uninstall
review-authority-read-retry
app-windows-ship
mesh-autoswitch-p2
mesh-ssh
mesh-relay
mesh-macos-node
screen-and-control
session-labels
review-real-logins-r5
review-real-logins-r4
review-real-logins-r3
shim-memory-r2
review-real-logins-r2
shim-memory
shadow-addhost-instant
shadow-real-logins
shadow-same-session-flood
shadow-prod-perf-fix
hosts-from-server
app-windows-real
app-windows-transport
release-prod-6w
mesh-policy-verify
quiet-close-toast
dev-merge-r4
review-mesh-agent-transport
review-app-windows-transport
settings-roundtrip
review-release-pipeline-r4
mesh-agent-transport
ended-strip-fix
review-audit-ca-update-d244
settings-rows
phone-width
signout-clear
fix-restart-tests
dev-merge-rp
signer-hardening
review-audit-ca-update
terminal-fidelity
review-audit-ca-update-wip
mesh-integrate
review-mesh-agent-transport-515d3712
review-mesh-dataplane-feec1988
mesh-dataplane
signout
research-canvas-node
wip/review-renderer-cost
wip/review-terminal-fidelity
padlock-glyph
review-release-pipeline-2e95b689
phone-polish
sidebar-polish
inbox-generic
review-mesh-dataplane-378d4055
prod-batch
ui-inbox
ui-ca-unlock
ui-machine-sections
review-mesh-dataplane-a5f744fb
tls-session-prod
session-bounds-prod
fix-dead-session
session-bounds
tls-session
palette-polish
shortcuts-d222
research-appmeta
phone-gutter
ui-centre-button
wip/ui-centre-button
wip/tls-session
review-addhost-breakit
ui-platform
renderer-cost
remote-agents
mesh-api
ssh-bootstrap
prod-hotfix2
prod-fixes
review-remote-agents
review-mesh-dataplane
session-life
research-browser
review-mesh-api
research-capture
review-mesh-api-round3
review-mesh-api-round1
gate-stability
review-gate-stability
review-relay-direct
review-mesh-api-findings
proto-sessions
font-picker
proto-addhost
proto-windows
mesh-core
latency-gate
research-bsd
research-discovery
keytray-mods
mock-core
ui-tweaks
fix-motion-sel
fix-motion
integrate2
fix-robust
t-web
t-server
vt-ghostty
fix-snap-echo
t-web-auth
review-vt2
t-agent
lane-protocol
lane-shim
lane-auth
s1-base
s1-contracts
s1-workspace
No results found.
No labels
launch-qa
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
kayg/infraternal#227
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Owner report 2026-10-16 (video while signing in to agy on production,
492e1505abinary +f210a4f25web): the indicator dot in a terminal window's machine pill ("kayg@10.69.69.125 · shell ●") keeps flickering.Measured from the owner's 120 fps recording (12×12 px at the dot, luma per frame): the dot sits at Y≈80 (grey) and jumps to Y≈133 for 1–2 frames about every 14 frames, ≈8.5 flashes/s, steadily over 6.5 s while agy's sign-in TUI is on screen (it redraws continuously). So something toggles the dot's state on every output burst / redraw.
Expected: the dot shows a stable state; it never blinks with output. (TASTE: results never animate; nothing flickers.)
Start: branch
pill-flicker, basef210a4f25055a1c6ee4e624e36f99f3f20ead640(origin/dev). Read all #227 comments (0); issue metadata exposes no parent issue link. Reproducing the reported state change before editing.Mock baseline finding: the product canvas over the mock PTY received 19 alternate-screen, cursor-addressed redraw frames at 111 ms intervals (9.0 frames/s) over 2.1 s. The machine pill stayed
data-status=live; the dot stayedrgb(62, 158, 41)for all 151 animation-frame samples, with zero status or dot-class transitions. This workload does not reproduce the production flicker in the test double. Receipt: mock-reproduction.log.Real-server redraw finding: the 19-frame ANSI workload (~9 Hz by the scripted 111 ms cadence) produced one brief live → connecting → live interval (~67 ms in this host trace), not the reported ≥10 flips in 2 s. The final bridge record shows two protocol sockets, each with one AttachTerm and one replay.
body.tsmaps the pill directly from the binding's connected flag; I’m tracing the connection transition source next. Real traceI ran the actual
agyTUI for 2 s inside the real-server PTY with a fresh empty HOME. The 2.5 s status/frame trace recorded no pill mutations; the repeated socket handoff had already settled before tracing. The reported ≥10 flips/2 s still does not reproduce here. Sanitized agy traceCorrection to the prior finding: the ~67 ms interval in that attached raw trace came from the shared build host and is diagnostic only, not performance evidence. I will use the perf VM for all reported frame-rate and timing measurements.
Connection finding (correction to the prior sanitized run): a later real-server trace recorded one live → connecting → live pair. Its bridge records show a server-closed socket followed by Hello/Resume/AttachTerm; the pill is driven by the binding's connected state, which changes on disconnect/reattach. The isolated
agyinvocation emitted one screen render (2,158 bytes) and no periodic redraw, so it still does not reproduce the reported rate. Control and connection summaryTrace step completed at
f210a4f25055a1c6ee4e624e36f99f3f20ead640: the mock and real harnesses now use 24×80 cursor-addressed redraws; the real trace records pill mutations, per-frame samples, output-frame times and socket closes. No product code changed.bun run check(exit 0):Measurement correction: all frame rates, RAF sample counts and elapsed intervals in the earlier mock/real host traces are diagnostic only; the shared build host is not the source of performance evidence. I’ll use the perf VM for the required timing and frame-rate results.
Trace harness update at
f210a4f250: the real-server spec launches a generated PTY script by short path, records pill mutations and frame samples for a configurable window, and timestamps output frames/socket closes.bun run checkpassed ($ bun x tsc --project tsconfig.json). No product code changed. The next run will measure the actual TUI and synthetic redraw on the perf VM.Real trace instrumentation now timestamps terminal recovery notices, stream closes, and AttachTerm requests alongside pill mutations and output frames.
bun run checkpassed ($ bun x tsc --project tsconfig.json). The isolated TUI executable and its runtime libraries are staged under the perf VM job directory; the next capture runs there with no inherited credentials.Production-equivalent server artifact built at
f210a4f250. Server crates are unchanged from production commit492e1505a. Static x86_64 binary: 21,422,096 bytes; SHA-256beb50f0804f14f6ac85c712b613362f2047a95d4f874418b88b037cc8fb0d603. Build output:Finishedreleaseprofile [optimized] target(s) in 24m 01s.Perf VM capture has waited 45 minutes for
/var/lock/perf-bench.lock; it remains queued and I am continuing to wait. Current/var/lock/perf-bench.ownercontents:process-per-user ppu-soak-r8c-shared 2026-10-16T08:06:03.157433+00:00Perf VM runner acquired the lock at 08:48:20Z, then exited before tests with
error: Module not found "test/mock-server.ts"from the staged tracer source. No measurements were produced. I’m staging the missing test sources and requeueing.The corrected Perf VM runner has now waited 45 minutes for
/var/lock/perf-bench.lock; it remains queued and I am continuing to wait. Current/var/lock/perf-bench.ownercontents:mesh-autoswitch candidate-natural31-floor-r7 venue=150 sample=4 started=2026-10-16T09:32:29Z sample_started=2026-10-16T09:33:59ZThe corrected Perf VM retry acquired the lock at 09:44:08Z, then exited before tests because the runner referenced
src/apps/tracer/playwright.pill-flicker.config.ts, which is absent from the staged app tree. No tests or measurements ran. I’m correcting the runner to use the staged config and requeueing.Resume: branch
pill-flicker, basef210a4f25055a1c6ee4e624e36f99f3f20ead640(origin/dev). Read all #227 comments; issue metadata has no parent link. Continuing the interrupted real-server investigation by capturing each server Close frame and matching server log line against the agy byte stream and replay/control workloads.Client finding:
apps/tracer/src/connection.tsrenewsfastRecoveryUntilafter each quick close while a stream is attached, so repeated successful-but-immediately-closed resumes never reach exponential backoff. Added a failing regression inapps/tracer/src/connection-restart.test.ts: the second quick server close still schedules 5 ms (expected >=50 ms). Test output:Expected: >= 50; Received: 5. No product code changed.Status finding:
apps/tracer/src/connection.tsreports the server close reason beforeforceClose(), but disconnect handling immediately replaces it with the generic reconnect status;apps/tracer/src/canvas/status.tsthen dismisses the reason toast on that routine status. Added a failing regression inapps/tracer/src/canvas/status.test.ts: a close reason is dismissed once while the terminal is reconnecting, before it attaches. No product code changed.Client retry/status step is in the worktree at
f210a4f250. A previously attached stream now gets one prompt retry; repeated short-lived retries back off. Server close reasons remain visible through reconnect status changes, and resumed attachments only mark the pill live after a 300 ms settle. The retry regression failed before the change (5 ms repeat) and now passes.bun test src/connection-restart.test.ts src/canvas/status.test.ts: 6 pass, 0 fail.bun run check: pass.Design note for the orchestrator: TASTE/DECISIONS had no settle interval for a resumed connection. I used 300 ms of uninterrupted attachment before the dot turns live; the 110–120 ms close loop then stays steadily grey, with no animation. The exact interval can be made a named design choice if this behavior is retained.
Build-host repro status: the single wrapped real-server Playwright baseline has waited 45 minutes in the shared build-slot queue. No test child has started and
real-synthetic-baseline.logremains 0 bytes. The next dependency is a free host build slot; I have not bypassed the wrapper or moved the repro to the perf VM.The earlier real-server traces recorded an upstream WebSocket close with code 1005 and no application Close frame.
MuxConnectiondiscarded the browser CloseEvent, leaving only the generic reconnecting status. The new clean-close regression failed before the change (Received: "Connection lost. Reconnecting…") and passes now: the fallback reason is retained through the retry; internal connection rotation stays quiet.bun test src/connection-restart.test.ts: 4 pass, 0 fail.bun test src/canvas/status.test.ts: 3 pass, 0 fail.bun run check: pass. This stabilizes the client reason; the server-side close cause is still under investigation.Build-host repro update: the wrapped real-server Playwright baseline has remained queued for 60 minutes; it has not started and
real-synthetic-baseline.logis still 0 bytes. I am continuing to wait for the shared host build slot. The current client unit/type checks pass, but the server-close cause remains unverified until the real-server run begins.The real-server baseline has waited 71 minutes for the host build slot. Its test process has not started and its log remains 0 bytes. Current owners:
slot 1: multiplexer-skip — scripts/verify fast
slot 2: app-windows-ship — browser-and-size-gate.sh
slot 3: link-tooltip — scripts/verify fast
slot 4: mesh-unreachable-review — scripts/verify fast
priority slot: addhost-retry-review — build-review.sh
I am keeping the queued run alive and continuing source review while it waits.
The real-server baseline has now waited 90 minutes. The test process has not started; its log is still 0 bytes. Current build-slot owners:
slot 1: mesh-unreachable-review — cargo clippy --locked -p infraternal-server --tests -- -D warnings
slot 2: app-windows-ship — browser-and-size-gate.sh
slot 3: mesh-unreachable-review — run-scale.sh
slot 4: mesh-unreachable-review — scripts/verify fast
priority slot: review-addhost-retry — build-review.sh
The queued Playwright command remains alive and will run through the wrapper when a slot opens.
The real-server baseline has now waited 90 minutes. The test process has not started; its log is still 0 bytes. Current build-slot owner records:
2026-10-16T13:10:05+02:00 slot 1: mesh-unreachable-review pid 300900: cargo clippy --locked -p infraternal-server --tests -- -D warnings
2026-10-16T12:50:13+02:00 slot 2: ? pid 4017255: bash /home/kayg/.cache/codex-scratch/app-windows-ship/evidence/browser-and-size-gate.sh
2026-10-16T13:09:50+02:00 slot 3: mesh-unreachable-review pid 321087: netbird ssh --no-browser root@10.69.69.208 bash /root/infraternal-bench/mesh-unreachable-review/run-scale.sh
2026-10-16T11:55:47+02:00 slot 4: mesh-unreachable-review pid 3737231: scripts/verify fast
2026-10-16T13:00:52+02:00 slot prio: review-addhost-retry pid 258601: bash /home/kayg/.cache/codex-scratch/addhost-retry-review/build-review.sh
The queued Playwright command remains alive and will run through the wrapper when a slot opens.
Finding: the real agy trace records a server WebSocket close at 483 ms with code 1005 and an empty reason, and no app-level mux Close frame (
agy-timed-output.log).crates/infraternal-server/src/websocket.rssendsMessage::Close(None)for every completed socket loop, including normal exit andloop_resulterrors, so this close event does not identify the server branch. I will add focused diagnostic branch logging for the real-server replay before attributing the closure to terminal output.The real-server baseline has waited 2h15m through the host build-slot wrapper and has not started;
real-synthetic-baseline.logremains 0 bytes. Current wrapper owner records:2026-10-16T13:51:34+02:00 slot 1: multiplexer-skip pid 333163: scripts/verify fast
2026-10-16T12:50:13+02:00 slot 2: ? pid 4017255: bash /home/kayg/.cache/codex-scratch/app-windows-ship/evidence/browser-and-size-gate.sh
2026-10-16T13:09:50+02:00 slot 3: mesh-unreachable-review pid 321087: netbird ssh --no-browser root@10.69.69.208 bash /root/infraternal-bench/mesh-unreachable-review/run-scale.sh
2026-10-16T13:44:59+02:00 slot 4: ? pid 4013097: bash /home/kayg/.cache/codex-scratch/process-per-user/r8/verify-aged.sh
2026-10-16T13:29:41+02:00 slot prio: mesh-unreachable-review pid 256386: scripts/verify iterate
2026-10-16T14:01:22+02:00 slot prio2: mesh-unreachable-review pid 742606: netbird ssh --no-browser root@10.69.69.208 bash /root/infraternal-bench/mesh-unreachable-review/run-browser.sh
The original queued test remains alive under the build-slot wrapper and will continue waiting.
Harness setup finding: the queued real-server baseline environment had
TRACER_TEST_PORT=4787and scratch state roots, but noINFRATERAL_BIN; the checkout defaulttarget/x86_64-unknown-linux-musl/release/infraternalis absent. The existing scratch binary is/home/kayg/.cache/codex-scratch/pill-flicker/target/x86_64-unknown-linux-musl/release/infraternal(21,422,096 bytes, mtime 2026-10-16 09:32 +0200). I am restarting only my queued wrapper with that binary path set explicitly.Harness setup correction: the corrected queue acquired a slot but Playwright exited before launching tests because its
playwright.real.config.tspath is relative toapps/tracer; the command had been started from the repository root. I am rerunning the same production-base server/web baseline with working directoryapps/tracer.Harness setup correction: the test now ran but failed before server setup because I passed
INFRATERAL_BIN; the fixture readsINFRATERNAL_BIN(with thenafterr) inapps/tracer/real/server.ts:112. The baseline server binary path was therefore not overridden. I am rerunning with the fixture's exact variable name and a fresh test port.Harness setup correction: with the binary override fixed, the fixture stopped before launching because the socket path under
INFRATERNA_TEST_STATE_ROOT=/home/kayg/.cache/codex-scratch/pill-flicker/runtime-realexceeded the 108-byte Unix socket limit (apps/tracer/real/server.ts:120-122). I will use the shorter job-local scratch root/home/kayg/.cache/codex-scratch/pill-flicker/rand a fresh test port.Runtime-path correction: the proposed
/home/kayg/.cache/codex-scratch/pill-flicker/rroot still yields a 113-byte session socket path, above the 108-byte limit. I will keep build artifacts, captured logs and evidence under the job scratch directory, while using the fixture's short/tmpruntime root only for its ephemeral Unix socket directory (as itstmpdir()default does).Real-server production-base baseline completed (binary 21,422,096 bytes; web 327,181 bytes / 83,577 Brotli). Synthetic 9 Hz redraw sent 40,795 output bytes with 19 title changes, 39 synchronized-output toggles and 475 cursor-addressed writes. The pill changed live→connecting at +478 ms and back live at +619 ms: one reconnect, not ≥10 flips/2 s. The upstream socket ended first with WS 1006/empty reason and no mux Close; resume attached at output cursor 183 and replayed once. The resumed socket carried 40,542 bytes without another close. The server emitted no close-cause line, so source-level diagnostics are still needed before attributing the single disconnect. summary, raw receipt.
The first staged agy replay did not reach the control corpus: Playwright’s Ctrl+C went to the terminal foreground process group and stopped the runner along with agy, so the completion marker timed out after 20 s. I changed the scratch workload to time-limit only the agy child and let the script continue; no cause is inferred from that failed harness attempt.
bun run checkpasses after the test harness adjustment, and no agy process remains running.Real-server staged replay completed. The only close was before agy started: upstream WS Close 1005/empty reason at +431 ms; the browser close event was 4000/empty, with no mux Close. Browser Ping/Pong continued through +426 ms. One resume attached at output cursor 182 and replayed once. agy began at +619 ms; its sign-in screen and the full OSC 8/title/synchronized-output/mode/query/65 KiB line corpus completed at +10.4 s, sending 134,324 output bytes with no further close or replay. The pill changed live→connecting→live once; this does not reproduce 8.5 Hz. The staged input did not trigger this close. I am instrumenting the browser close call to identify the source of the pre-workload
4000. summary, receipt.Close-origin evidence: the browser called
WebSocket.close(4000, "connection unresponsive")at +356 ms; the upstream protocol bridge reported WS 1005/empty reason at +318 ms, and the pill changed live→connecting at +370 ms then back live at +441 ms. Ping/Pong frames were logged through +308 ms; maximum RAF gap during the first 700 ms was 19.3 ms. The hook did not capture the target socket path, so this does not yet prove which socket the local close call targeted. I’m adding the safe path/readyState fields before assigning cause. summary, receipt.Finding: the staged agy sign-in screen plus the requested control corpus did not reproduce an input-triggered close. The real-server capture recorded 134,324 terminal-output bytes and completed the OSC 8, repeated OSC 0/2 + synchronized-output frames, mode toggles, device queries and 65 KiB line. One upstream EOF occurred at +431 ms before the
agy-startmarker; Resume used output cursor 182 and replayed once, then the socket stayed open through the remaining 10.4 s. Evidence:agy-control-replay-summary.jsonandagy-control-replay.logare attached above. An earlier run captured a client close call4000 / connection unresponsiveat +356 ms, but the subsequent path-instrumented run had no page-side WebSocket close call. I am capturing the server loop/reader exit reason now because the close is intermittent and precedes the workload.Correction: the preceding real-server traces used a binary built from the worktree's f210 source, not the deployed server commit
492e1505a. Their web assets weref210a4f25. I canceled the queued run before it started and am rebuilding the diagnostic server from492e1505awhile keeping the f210 web assets, then replaying the same staged agy screen and control corpus. Do not treat the earlier server-side results as reproduction against the deployed server.Current production tracer assets match the
f210a4f25base at 327,181 raw bytes; Brotli is 83,525 bytes versus 83,577 on base (−52 bytes). Receipt: web-current-size.json.The tracer's local liveness path retires a socket with close code 4000 and reason
connection unresponsiveafter a missed Pong. It previously calleddisconnect()beforeWebSocket.close(), clearing the current socket sooncloseignored that reason and status fell back to generic reconnect text. I now record and map the reason before retirement; the new assertion fails on base and passes with the fix (10 tests, 0 failures). This explains the lost reason; the instrumented 492e replay is still needed to confirm what delays or misses the Pong under agy's exact output. Receipts: old-path failure, fixed tests.Exact 492e instrumented real-server trace: the server socket ended at +522 ms with WebSocket 1005/empty reason; its log said
proof authority or transmit failedandterminal output handling failed. The browser recorded its ownclose(4000, "connection unresponsive")at +549 ms. This preceded the stagedagy-startmarker at +823 ms. The resumed socket attached at +792 ms, replayed once, and remained open. The staged agy step ended at +5,827 ms but emitted only 26 terminal-output bytes, so it did not exercise the sign-in redraw. Later control replay delivered 133,942 bytes with no further close. The current server diagnostics still collapse the proof/send error; I am instrumenting that path and replacing the ineffective agy stage before concluding on the trigger. Receipts: safe trace summary, real-server log.Proof-detail replay of binary
492e1505: over 11.1 s, the pill had 0 status mutations and all 641 sampled frames were live/rgb(62, 158, 41). The server accepted 494 Pong replies; no lease expiry or WebSocket write failure occurred before harness teardown. One resume replay attached once and remained open. The attached summary records the exact counts.The staged agy command ran 5.0 s but emitted only the 26-byte start marker, no TUI output. The 134,261-byte OSC/CSI control corpus also caused no close. The earlier close was before the agy stage, so its trigger is still unproven. I will replace that ineffective stage with a measured 9 Hz cursor-addressed redraw and keep the production server close cause open until reproduced.
The exact 492e server close is now isolated. The first close was caused by my real-server harness typing a long absolute launcher path as 92 separate one-byte terminal Input frames over ~261 ms: server probe Pongs stopped at 14:35:31.219Z, the 250 ms lease expired at 14:35:31.493Z, and the server closed with 1005/empty reason. This was before the redraw began, so it was a harness artifact, not evidence against the screen output.
After shortening the command, the exact production server handled the 10.628 s 9 Hz cursor-addressed redraw (191,025 bytes, 90 title updates, 2,250 addressed rows) with 0 pill status mutations, 632/632 live green frame samples, and no socket closes during the workload. The attached summary has both runs. The 134 KB control corpus also remained open. The staged agy invocation still emits no UI bytes; its output is only the fixed marker. I have not found evidence for a server close triggered by the sign-in output yet, so I’m continuing with an accurate agy invocation/capture and the required mock reconnect regression rather than making an unsupported server change.
The mock browser regression now reproduces the requested rapid state churn. With INFRATERNAL_TEST_RTT=0 and a 110 ms close after each attach, the exact
f210a4fsource produced 32 dot flips in the first 2 s (40 over the 2.5 s trace: 20 live and 20 connecting transitions) and failed the regression. The branch produced 1 connecting transition, 0 live promotions during retries and 0 live frames after the first disconnect; both browser specs passed. The default 300 ms proxy RTT masks this cadence, so the local repro explicitly sets RTT to zero. Attached comparison includes the counts.The clean production server build (static x86_64 musl, 21,421,840 bytes) passed the real-server redraw spec. Across 10.408 s and 191,025 output bytes (90 titles, 90 clears, 2,250 cursor-addressed rows), the dot had 0 status mutations, all 580 sampled frames stayed live/rgb(62, 158, 41), and no socket closed during the workload. The 10.000 s H.264 demo and a representative frame are attached.
The recorded workload is the synthetic sign-in screen; the staged agy executable still produced no screen output under its isolated home. I have not presented the synthetic demo as an agy capture.
The pre-existing branch dist was a readable diagnostic build, not the production bundle: transfer-budget measured critical JavaScript at 1,071,509 identity / 339,959 gzip / 285,088 Brotli bytes (Brotli budget 256,000), so I excluded it. A fresh minified production build will be used for the before/after measurement. The clean f210 base production build measures 961,716 identity / 297,686 gzip / 247,538 Brotli bytes.
Perf VM measurement harness finding: a 10.4 s production-server redraw produced 191 browser WebSocket Output frames. The initial 1,000-frame per-run guard rejected that sample. I changed the focused harness to require at least 100 frames per run and will aggregate six alternating trials per build, targeting at least 1,000 Output frames for each before/after total.
Perf comparison complete on infraternal-perf-test (4 vCPU / 7 GiB), each Playwright run under CPUWeight=10000. Load average: start 1.27/0.70/0.42; end 0.36/0.62/0.46. Both builds used the same production-equivalent server binary (21,422,096 bytes; SHA-256 beb50f0804f14f6ac85c712b613362f2047a95d4f874418b88b037cc8fb0d603).
Six alternating trials per frontend: base 1,003 Output frames / 6,915.073 ms main-thread TaskDuration = 6,894.390 ms per 1,000 frames; branch 1,025 / 7,032.175 ms = 6,860.659 ms per 1,000 frames (-0.489%). 12/12 real-server Playwright runs passed. All six trials per build had zero data-status mutations and zero client/server socket closes during redraw; sampled dot stayed rgb(62, 158, 41).
Summary, per-trial CSV.
Size comparison complete. Both minified production tracer builds passed
bun transfer-budget.ts; the compressed critical JavaScript path is 247,589 B Brotli on f210 and 247,544 B on branch (-45 B). Identity is +174 B and gzip +56 B; production Brotli is smaller. Stripped static x86_64-musl server binary is 21,422,096 B on base and 21,421,840 B on branch (-256 B), below the 16 KiB limit.Summary, base transfer receipt, branch transfer receipt.
Real-server replay finding: both the
f210a4f25baseline and branch completed the 90-frame control/query workload (829,393 output bytes per run) without any WebSocket close or pill mutation; all 613/614 sampled frames remained live green. The workload included OSC 8 links, OSC title changes, mode toggles, device/terminal/keyboard/graphics queries, cursor redraws and repeated 8 KiB lines. No server Close frame occurred, so this workload yielded no server close reason. I still cannot claim the exact agy reconnect trigger: the staged agy sign-in output has not been captured reliably as a repeating byte stream. Sanitized evidence: real-query-summary.txt.The current real-server attempt invoked the host
agyexecutable with a fresh isolated HOME and no inherited environment or user input. Its default invocation emitted 531 bytes including wrapper/setup output but no alternate screen, cursor redraw, clear-screen or title controls. Across 14.0 seconds the pill had 0 mutations and there were no socket closes. This did not exercise the reported full-screen sign-in TUI, so it does not identify the production close trigger. Summary: agy-plain-run-summary.txt. I’m checking the executable’s interactive invocation next.Follow-up agy finding:
agy --prompt-interactivein another fresh isolated HOME exited with status 2 after 0.28 seconds and emitted no full-screen terminal controls. The pill remained live green during its 16 sampled frames; no socket closed. This does not reproduce the reported TUI and cannot identify the production close trigger. Sanitized summary: agy-interactive-summary.txt. I’m retaining the real-server 9 Hz redraw fixture as the reproducible visual case and will record that separately.Correction to my prior redraw update: that run used a misspelled
INFRATERNAL_BINvariable and therefore selected the worktree default binary instead of the pinned static scratch build. With the correct binary path, the 9 Hz real-PTY redraw completed (191,025 bytes, 90 title updates, 2,250 row writes) with 0 socket closes and 0 pill mutations. The pinned run's exact result and 10-second recording are attached above. The earlier 1005/empty close was observed before the staged agy workload and remains unexplained; no close frame was available in that trace. Thus the exact production close trigger is still unisolated.The final mock browser regression passed with the preview-enabled test bundle: redraw output caused zero status mutations; the 110 ms close-after-attach storm made 6 sockets/6 attaches over 2.5 seconds but the pill only changed once from live to connecting, never flashed live again, and kept the reason toast. Summary: mock-browser-final-summary.txt.
Step complete at
1fd00a3c8: resumed streams now get one fast retry, then bounded exponential backoff; close reasons survive route retirement and retries. Targeted unit gate: 10 tests, 35 assertions passed.bun run checkpassed. This change addresses the 8 Hz reconnect presentation loop; the real-server agy close trigger remains unisolated.The first
scripts/verify iteraterun failed only in nine meshnode_statetests:Directory::newfell back to the package test process's current directory and tried to createcrates/plugins/mesh/target/..., which does not exist whenCARGO_TARGET_DIRis unset. The same test passes with the job scratch target explicitly set (CARGO_TARGET_DIR=/home/kayg/.cache/codex-scratch/pill-flicker/target). I’m rerunning iterate with that required per-job target path; no Rust source changed.Commit 1
1fd00a3c8passes the full iterate gate on the scratch target. Rust rows: fmt, VT native fmt, CLI plugin compile-out, clippy, test, and cargo-deny all PASS. Workspace test gate completed in 665 s; TypeScript suite reports 193 + 37 passing tests and 0 failures. Captured transcript: iterate-final.log.Commit
ba01d1266is pushed topill-flicker. The resumed pill stays connecting until a 300 ms stable attach; connection failure toast persists through retry statuses and clears only after 300 ms stable empty status.bun run checkpassed;bun test src/canvas/status.test.ts: 4 pass, 0 fail, 7 assertions.Production-version finding: server and agent source trees at
492e1505amatch basef210a4f25. In the 492e real-server trace, bridge b0 ended at +522 ms with WebSocket close 1005 and an empty reason; no mux Close frame was recorded. The page calledclose(4000, "connection unresponsive")at +549 ms. The resumed AttachTerm happened at +792 ms; the agy stage began at +823 ms. The 10.6 s agy/control replay then carried 134,319 output bytes with no further close or pill mutation. The actual agy invocation produced one screen render, not the reported repeated TUI redraw, so that close predates the workload and the exact production trigger remains unisolated. The synthetic ~9 Hz redraw and immediate-close regression coverage is attached: browser-mock-final.log.The real-server browser regression passes against the pinned static server build and production tracer bundle. From workload marker +341 ms to +11,107 ms, 600 RAF samples stayed
rgb(62, 158, 41), with zero status mutations and zero client/server socket closes. The PTY emitted 191,025 bytes, including 90 OSC title updates, 90 clear-screen frames, and 2,250 cursor-addressed writes. The only logged WebSocket closes were normal browser teardown after the workload (1001/1006). Transcript: browser-real-final.log.Commit
427c67a7ais pushed topill-flicker.bun run checkpassed. Browser regressions: mock 2 passed (19 redraw frames at ~9 Hz with zero mutations; immediate-close storm made 6 sockets, 1 live→connecting transition, 0 connecting→live flashes); real server 1 passed (600 frames, one color, zero workload mutations/closes). Receipts: mock trace, real-server trace.Commit
9e01a0331is pushed topill-flicker. The stripped static x86_64 artifact is 21,421,840 bytes versus the prior 21,422,096-byte ceiling (−256 bytes). The gate now uses that exact D243 ceiling; final fast will verify the reproducible build and both shipping targets.Final fast has one failure so far:
auth::tests::socket_tests::restart_open_liveness_review::responsive_socket_survives_a_held_terminal_open_with_an_existing_attachment; the neighboring socket tests pass. Host load average at the failure was7.66 11.00 14.85. This is an auth socket liveness timeout test from the allowed host-load comparison class. I will run it on a detacheddevbaseline checkout in the same load window after this gate exits; the full transcript is being retained for comparison.Final
scripts/verify fastresult: fmt, VT native fmt, CLI plugin compile-out, clippy, cargo-deny, and static builds PASS; workspace tests report 448 passed, 1 failed, 48 ignored. The lone failure wasauth::tests::socket_tests::restart_open_liveness_review::responsive_socket_survives_a_held_terminal_open_with_an_existing_attachment, panicking atcrates/infraternal-server/src/auth/socket_clock.rs:81withsocket test stopped before its observed event(test stage 568 s; server lib test result 356.91 s).The exact test also failed on detached
origin/dev650472b98with the same panic after 90.00 s. Baseline host load was 11.29/15.09/17.16 before and 20.21/17.37/17.54 after; branch fast failure load was 7.66/11.00/14.85. This is the documented host-load exception; the failure is unchanged on baseline. Receipts: branch fast transcript, baseline isolated test.TypeScript verification completed on branch
9e01a03316: compiler, tracer build/precompression/transfer budget, Rust stream differential and TypeScript suites passed; 37 tests and 34,463 assertions, 0 failures. Log: verify-ts-final.log.Normalized critical transfer against exact base
f210a4f250(same injected build SHA/date): Brotli 247,589 → 247,544 bytes (−45); gzip 297,669 → 297,725 (+56); identity 961,716 → 961,890 (+174). Receipt: size-comparison-normalized.txt.The final fast transcript and the exact same socket timing failure on the detached dev baseline are attached in my prior comment; all other fast rows passed. The failed test is the known host load exception.
Branch result at
9e01a03316, pushed to pill-flicker; not merged or deployed.The client now backs off after repeated short reconnects and keeps the machine pill steady while a resumed stream proves stable. The mock close-storm regression passed with 6 attaches in 2.5 s, one live→connecting transition, zero connecting→live flashes, and one total state flip. Main-thread cost per 1,000 output frames on the perf VM was 6,894.390 ms base vs 6,860.659 ms branch (−0.489%). With normalized build identifiers, critical transfer was identity 961,716→961,890 (+174 B), gzip 297,669→297,725 (+56 B), Brotli 247,589→247,544 (−45 B). The size receipt is attached above.
Current production-build real-server demo: video, frame, trace. It is 10.000 s; the 10.688 s workload produced 191,025 PTY bytes, 90 title updates, 90 clears, and 2,250 cursor-addressed writes. Across 416 sampled animation frames, the dot stayed
rgb(62, 158, 41), with zero pill mutations and zero socket closes.The server-side close trigger remains unisolated. On the exact production server build, the agy/control replay carried 134,324 bytes without another close; the only close preceded the workload and was traced to the harness typing a long launcher path as 92 one-byte terminal inputs over ~261 ms, exceeding the 250 ms proof lease. With that harness issue removed, the 9 Hz redraw completed without a close. The installed agy CLI in a fresh isolated HOME did not render the reported sign-in TUI under its documented interactive invocation. The video therefore uses the deterministic 9 Hz sign-in-style redraw fixture, not an authentic agy screen. No server change was made without a workload-caused close to fix; this root-cause portion remains blocked on a safe reproducible production close.
TypeScript verification passed: 37 tests, 34,463 assertions, 0 failures (log attached above). Final
scripts/verify fasttranscript: log. The sole test failure also reproduced on the detacheddevbaseline (baseline log: https://git.kayg.org/attachments/4b69a4e2-7ad6-4ed4-9152-7808d9e410e6):The failing test is
auth::tests::socket_tests::restart_open_liveness_review::responsive_socket_survives_a_held_terminal_open_with_an_existing_attachment; it panicked atcrates/infraternal-server/src/auth/socket_clock.rs:81:13withsocket test stopped before its observed event, identically on baseline.Start: branch
review-pill-flicker, based on review target9e01a0331614ec6777900c34bc6be9d72486b0a7; target difff210a4f25055a1c6ee4e624e36f99f3f20ead640..9e01a0331614ec6777900c34bc6be9d72486b0a7. Read all 67 #227 comments; issue metadata has no parent issue link. Beginning independent verification of the client retry, status, and x86 size-cap changes.P1 finding (verification in progress): a committed fake-clock regression on
review-pill-flicker(073d05ec2) fails on target9e01a0331. After a simulated 10 s route outage, the next successful Welcome is delayed 1,305 ms after the route returns; the test expects at most 50 ms for the local path under D265. This is a scheduler-level reproduction, not a perf-VM measurement yet; I’m checking the real restart and network-cut behavior before the final verdict.RF-1 base comparison: the same fake-clock test passes on base
f210a4f25055a1c6ee4e624e36f99f3f20ead640(1 pass, 0 fail), while target9e01a0331recovers 1,305 ms after the simulated route return against a 50 ms assertion. This isolates the scheduling regression to the target change; I’m still verifying it with the production browser on the perf VM.P2 finding: a committed toast-ownership regression (
bbb1c2e36) fails on target9e01a0331. When the shared toast is replaced by a newer message, the next retry shows the same connection reason again and replaces the newer message. The controller's documentedonClosecallback runs when another toast replaces a message; the test models that behavior.P1 confirmed on real production browser builds. On the 4 vCPU Intel i9-9900K perf VM (8.26 GB RAM; per-trial load averages 0.16–1.39), three alternating runs after a 10 s browser offline interval measured return-to-attach at base 1,863/188/572 ms (median 572 ms) and head 4,519/2,921/4,939 ms (median 4,519 ms). Return-to-live was base 1,871/203/580 ms (median 580 ms) and head 4,827/3,230/5,251 ms (median 4,827 ms). Head's median attach delay is +3,947 ms; median live delay is +4,247 ms. A separate fake-clock case also fails at 1,305 ms against the 50 ms reconnect allowance; the same test passes on base. Logs and all six trial screenshots are in
/home/kayg/.cache/codex-scratch/pill-flicker-review/evidence/real-browser/.Review evidence update (review branch head
bbb1c2e36): the real PTY server-restart typing test passed on base and head. A real-browser 10 s offline test queuedecho CUT_<variant>while disconnected and passed on both builds with exactly two server-captured marker occurrences (command echo and result), with replay complete before the pill returned live.Five real-PTY redraw trials per build on the 4 vCPU Intel i9-9900K perf VM (8.26 GB RAM) yielded 1,122 base output frames / 7,262.447 ms task time (6,472.769 ms per 1,000 frames) and 1,112 head frames / 6,826.133 ms (6,138.609 ms per 1,000). All ten trials recorded zero status mutations during redraw, 607–609 frame samples, and zero client/server socket closes. Loads before trials were 0.20–2.08 (base) and 0.21–1.61 (head). Raw logs:
/home/kayg/.cache/codex-scratch/pill-flicker-review/evidence/redraw/.Correction to the previous screenshot comment: the attached head-live screenshot link is https://git.kayg.org/attachments/5514abb3-7bc4-4f37-81eb-84cd288eb9f7.
Correction to the synthetic regression-test note: the first version advanced
Date.nowbut notperformance.now, so it did not advance base's ten-second fast-recovery deadline. I corrected the test to advance both clocks and return at 9.5 s, inside base's deadline. The corrected test passes onf210a4f25055a1c6ee4e624e36f99f3f20ead640(1 pass) and fails on9e01a0331at 1,805 ms against the 50 ms allowance. The correction is committed onreview-pill-flickeraseed9e64b4. The separate three-pair real-browser comparison after an exact 10 s cut is unchanged: median return-to-live 580 ms base vs 4,827 ms head.Representative raw perf-VM logs attached: base 10 s recovery, head 10 s recovery, base redraw, head redraw. Full per-trial logs remain under
/home/kayg/.cache/codex-scratch/pill-flicker-review/evidence/.Single-restart recovery update: three real production-browser runs measured ready-to-live at base 23/406/402 ms (median 402 ms) and head 324/326/335 ms (median 326 ms). Head replay completed 17–25 ms after server readiness (median 22 ms); live followed after the 300 ms stability interval. The one-restart path did not regress. Raw trial logs are in the attached
perf-vm-evidence.tarcontents and local evidence directory.P2 confirmed for D243: exact-head
scripts/verify fastbuilds the pinned Rust 1.99.0 x86_64 static artifact at 21,422,096 bytes, while commit9e01a0331sets the exact cap to 21,421,840 bytes. The gate output is:This artifact is statically linked and stripped. The changed cap does not match the artifact produced by the gate. Full output is being preserved under
/home/kayg/.cache/codex-scratch/pill-flicker-review/fast-gate.log.Review complete. Verdict: do not merge. P0: 0, P1: 1, P2: 2, P3: 0.
Reviewed target
9e01a0331614ec6777900c34bc6be9d72486b0a7; review-only branchreview-pill-flickeris ateed9e64b4. Three failing regression tests are committed there; no implementation files were changed.RF-1 is P1: after a 10 s network cut, three alternating production-build runs measured median return-to-attach of 572 ms on base vs 4,519 ms on head, and return-to-live of 580 ms vs 4,827 ms. The corrected clock test also fails on head at 1,805 ms against a 50 ms local-path bound. A single legitimate server restart remained fast: median ready-to-live 402 ms base vs 326 ms head. Redraw verification recorded zero status mutations and zero socket closes across five trials per build.
RF-2 is P2: a repeated close reason reclaims the shared toast after a newer message replaces it. RF-3 is P2: the exact x86_64 artifact is 21,422,096 bytes against the new 21,421,840-byte cap (+256 bytes); aarch64 is exactly at its cap.
TypeScript fast row: 37 tests across 10 files passed, 34,463 assertions, 0 failures. The Rust workspace fast row had 448 passed, 1 failed, 48 ignored; the failing queue test passed when run alone (1 passed, 496 filtered), so that failure did not reproduce in isolation. Full fast gate remains red.
scripts/verify fastoutput on the target head (verbatim summary):Static size failure output:
Full logs: fast-gate.log, ts-fast.log, isolated Rust retry. Recovery screenshots and benchmark logs were attached in earlier comments.
Resume start: branch
pill-flickerat9e01a0331614ec6777900c34bc6be9d72486b0a7; currentorigin/devbase is650472b987dbd1fe7192248dd1367d0766c5fb79. Read all 79 #227 comments; issue metadata has no parent link.origin/devis at the mesh-autoswitch night-release merge, but its log does not yet contain the mesh-unreachable merge or #229 R5. I’ll first cherry-pick and verify the three review regressions, then rebase and keep the size cap at the new base value until R5 lands.Review regression reproduction: cherry-picked
073d05ec2,bbb1c2e36andeed9e64b4as three test-only commits (53786be21,d636aa0eb,fdc4be474). On the prior branch implementation,bun test src/connection-restart.test.ts src/canvas/status.test.tsproduced 8 pass / 2 fail: after a simulated 9.5 s outage the first resumed Welcome was 1,805 ms late (expected <=50 ms); after a newer toast replaced the close reason, the next retry reclaimed the toast. This confirms both review findings before fixes. The branch also retains the prior 300 ms stable-attach behavior for the close-storm regression.Client fixes completed on rebased
pill-flicker:96bee808frestores a 5–10 ms retry cadence after unwelcomed path failures and reconnects synchronously on online/visible events;ea5cf6639preserves shared-toast identity so the same close reason cannot replace a newer message. The review regressions passed after the fixes:bun test src/connection-restart.test.ts src/canvas/status.test.ts— 11 pass, 0 fail, 33 assertions;bun run checkpassed. Currentorigin/devlacks mesh-unreachable and #229 R5, so the static cap remains exactly the base value, 21,422,096 bytes.Rebased browser regressions passed on the current production tracer build (
TRACER_TEST_PORT=4427,origin/dev650472b98plus the pill fix): 2 Playwright specs passed. The 9 Hz mock redraw produced zero status/dot mutations across 151 frame samples. The 110 ms immediate-close storm opened 4 sockets and 4 attachments; the pill changed once from live to connecting, with zero live promotions/flashes during retries. Receipt: playwright log.Iterate gate finding:
infraternal-serverpassed 452 tests and failed 2 after the full parallel suite ran 416.32 s.auth::tests::socket_tests::restart_open_liveness_review::responsive_socket_survives_a_held_terminal_open_with_an_existing_attachmentpanicked atcrates/infraternal-server/src/auth/socket_clock.rs:81(“socket test stopped before its observed event”);remote_runtime::tests::standby::control_queue::saturated_native_control_queue_does_not_hide_primary_terminal_outputfailedlive_connection.close_reason().is_none()atcrates/infraternal-server/src/remote_control_queue_tests.rs:216. The remaining 450 server tests passed (48 ignored). Isolated reruns are next.Iterate follow-up: both gate failures pass when isolated on this branch.
responsive_socket_survives_a_held_terminal_open_with_an_existing_attachment: 1 passed in 1.20 s.saturated_native_control_queue_does_not_hide_primary_terminal_output(with--features remote): 1 passed in 0.11 s (CONTROL_SATURATION active=true queued=1 native_primary_live=true shadow_output=0). The broad run had 452 passed, 2 failures, 48 ignored in 416.32 s; the parallel auth suite emitted multiple over-60-second warnings. No server code changed in this UI lane.Final
scripts/verify fastfinding, heada33f7d093: the Rust gate reportedtest FAIL (101)(server tests: 453 passed, 1 failed, 48 ignored; the failing row wasremote_runtime::tests::standby::control_queue::saturated_native_control_queue_does_not_hide_primary_terminal_output) andstatic builds FAIL (1). x86_64 measured 21,320,400 bytes at its unchanged cap (delta +0); aarch64 measured 19,517,536 against the unchanged 19,513,440 cap (delta +4,096).origin/devis650472b98and its history has the mesh-autoswitch merge but not #229/R5; I did not change either cap. Raw transcript: fast.log.Verbatim final Rust gate table:
| Rust gate | Result |
+---------------------------+--------------+
| fmt | PASS |
| VT native fmt | PASS |
| CLI plugin compile-out | PASS |
| clippy | PASS |
| test | FAIL (101) |
| cargo-deny | PASS |
| static builds | FAIL (1) |
+---------------------------+--------------+
Fast-gate rerun of the server row:
cargo test -p infraternal-server --features remote --lib saturated_native_control_queue_does_not_hide_primary_terminal_output -- --nocapturepassed 1/1 in 0.10 s (CONTROL_SATURATION active=true queued=1 native_primary_live=true shadow_output=0). The parallel workspace run is the only test failure; no Rust sources or dependency features changed in this branch.Critical-path transfer comparison (same production build recipe, baseline
origin/dev650472b98vs brancha33f7d093): identity 962,037→963,333 bytes (+1,296), gzip 297,814→298,087 (+273), Brotli 247,707→248,056 (+349). Both remain under the existing transfer budgets; this is growth on the critical path and is recorded for review. Full build and transfer-budget receipts: dist-comparison.log.Final real-server redraw run: production x86_64 binary (21,320,400 bytes) plus production tracer bundle; 10.564 s workload, 191,025 output bytes, 90 OSC titles, 90 clears, 2,250 cursor-addressed rows, 616 sampled frames, 0 pill-status mutations, and 0 client/server socket closes. The 10-second production real-server video and raw trace are attached: video, trace.
The staged
agy --prompt-interactiveattempt still exits before rendering the sign-in TUI in an isolated HOME, so this recording is the synthetic sign-in redraw fixture and is labeled accordingly; it is not an agy capture.Correction to the real-server demo note:
frameSamples=616is the browser harness’s functional trace count from the shared build host, not performance evidence. The attached redraw log supports only workload byte/control counts, stable pill state, and zero closes; timing and main-thread measurements are being taken on the perf VM.Perf-VM recovery harness finding: the first 10 base and 10 branch invocations never reached their initial
liveprecondition, so none produced latency samples. A focused base startup trace shows one WebSocket opened and sentHello, twoSubscribeframes, andCall; the bridge remained open with no attached terminal and no server-to-browser protocol frames before timeout. No performance result is claimed. Diagnostic trace: network-debug-base.log. I am checking warning-level server output to resolve the test setup before retrying the measurement.Perf VM real-server startup trace: the page opened its /v1/ws socket, but no AttachTerm was sent and the pill stayed connecting. The embedded agent repeatedly logged
infraternal agent failed: address in use; the test server ran as root, whose embedded-agent state resolves to shared/run/infraternaland collided with the host socket. This is a harness isolation issue, not a terminal-byte close. I am rerunning with/run/infraternalisolated in a private mount namespace. Receipt: network-debug-perf-vm.log.The isolated real-server rerun passed. With
/run/infraternalprivate to the test namespace, the browser sentAttachTermand terminal credit, then startup reachedlive; the Playwright diagnostic passed (6.8 s). This confirms the prior address-in-use failure was test-host socket sharing. Receipt: network-debug-perf-vm-isolated.log. I am now measuring 10 network-return trials per dist.Build-host real-server replay used the production-equivalent base web dist and real server binary. It emitted 90 redraw frames containing 16 KiB OSC 8 links and lines, OSC 0/2 titles, bracketed-paste/focus toggles, synchronized output, DA/DSR/XTVERSION/keyboard/graphics queries (2,972,055 output bytes). The trace recorded zero WebSocket closes, zero status mutations, and a single replay. This workload does not explain the production close. The staged
agy --prompt-interactiveattempt still exits before drawing, so no exact sign-in byte capture is available. Receipt: control-replay-base.log.Perf VM (infraternal-perf-test, x86_64, 4 vCPU, 7.7 GiB) completed 10 alternating 10-second offline trials per dist before the in-flight-handshake fix. Median return-to-live was 1,575.45 ms for base and 1,866.50 ms for branch (+291.05 ms); ranges were 462.5–8,810.5 ms and 447.3–4,736.3 ms. Load averages were 0.89/0.86/0.84 before and 1.37/1.51/1.18 after. This does not meet the ±100 ms target and confirms the need to wake a stalled opening socket. Receipt: network-recovery-before-fix.log.
The current production dist/server recording is attached: pill-flicker-real-server-after-fix.mp4. The 10.5-second synthetic sign-in-style redraw produced 611 frame samples, one stable green state, zero status mutations, and zero client/server socket closes. The staged agy command still exits before rendering, so the video is labeled synthetic.
git log origin/devstill shows650472b98at the tip and no mesh-unreachable merge or #229/R5 checkout-path change. I left both static size caps unchanged as instructed.Final rebuilt dist recording attached: pill-flicker-real-server-final.mp4. The 10.7-second real-server redraw produced 613 frame samples, one constant green state, zero status mutations, and zero client/server socket closes. This is the synthetic sign-in-style workload because the staged agy command exits before rendering.
Post-fix 10 s outage comparison, 10 alternating trials each on perf VM (4 vCPU, 7.7 GiB; load average 0.88/1.27/1.26 before, 1.16/1.22/1.25 after), current branch server 28212cdd and identical browser build except web variant: base median return-to-live 1,327.45 ms (268.1–3,789.8); branch 3,151.75 ms (1,503.5–5,314.1). This misses the ±100 ms target, so I am tracing the remaining recovery delay before proceeding. Full socket-event log: network-recovery-final.log.
Exact production server
492e1505acomparison, 10 alternating 10 s outage trials on the 4-vCPU, 7.7 GiB perf VM (load average 0.63/1.19/1.26 before, 1.59/1.56/1.38 after): base median return-to-live 1,878.75 ms (508.2–4,458.0); branch 3,945.0 ms (1,906.2–4,645.1). Socket traces show the online handler closed the in-flight CONNECTING WebSocket with code 1000, discarding a handshake the browser could otherwise resume. I am changing the hint to reset retry state while keeping that socket. Log: network-recovery-prod492e.log.After preserving an in-flight handshake, the 10-trial comparison on production server artifact
492e1505ameasured base median 1,462.45 ms (444.5–2,886.1) and branch 1,849.2 ms (1,102.7–5,316.4), delta +386.75 ms. The online event no longer retires the active socket, but this sample remains outside the ±100 ms target and has wide variance. I am extending the alternating run and inspecting per-trial socket events before changing retry behavior again. Log: network-recovery-prod492e-final2.log.Recovery comparison with handshake-preserving online hints: 20 alternating 10 s outage trials per variant against production server artifact
492e1505a(SHA-256 28212cdd96ef716d81502dc3991f8556460f75f8899de4a8f9b8f5e3243d4ea8, 21,320,400 bytes), on the 4-vCPU, 7.7 GiB perf VM. Combined base median 1,593.25 ms (84.5–3,056.1), branch 1,388.5 ms (337.0–5,316.4), delta -204.75 ms. In the second 10-trial window, base was 1,659.0 ms and branch 1,235.6 ms (delta -423.4 ms). Load averages were 1.35/1.40/1.34 to 1.71/1.61/1.45 in the first window and 1.94/1.57/1.45 to 1.98/1.65/1.52 in the extension. The browser-reported online event median was 2.8–2.9 ms. Full socket traces: network-recovery-prod492e-20.log.The final mock browser regression ran on the production web build: 2 passed, 0 failed. Across the 2.5 s redraw trace, the machine pill stayed live with no status/class mutations. Under repeated immediate server closes, 4 sockets and 4 attaches produced one live→connecting transition, zero live transitions, zero live frames during reconnect, and the persistent close reason stayed visible. Log: mock-final-browser.log.
Real-server redraw evidence on server artifact
492e1505aand the final web build: the video run covered 10.38 s and 191,063 output bytes (90 OSC title changes, 2,250 cursor moves, 181 synchronized-output toggles). It recorded 600 frame samples, only the live green color, zero status mutations, zero client closes and zero server closes. The video, sampled frame, and log are attached above.An earlier build-host run of the same harness saw one server-side transport close at 7.325 s (WebSocket code 1005, no close reason), with a connecting→live transition during the workload. The subsequent REAL_SERVER_LOG=1 repeat and final video run both completed without a close; logs contain only server startup, so the cause of that unrepeatable close remains unknown. The staged sign-in app still does not render here, so this workload is a synthetic sign-in-style redraw, not captured agy output.
Main-thread output cost on the 4-vCPU, 7.7 GiB perf VM, production server artifact
492e1505a(SHA-256 28212cdd96ef716d81502dc3991f8556460f75f8899de4a8f9b8f5e3243d4ea8), 10 runs per variant: base 2,075 output frames, median 8,016.083 ms/1,000 frames (7,347.330–12,551.627); branch 2,071 frames, median 8,081.567 ms/1,000 (7,557.507–11,805.214), delta +65.483 ms/1,000 (0.065 ms/frame). Load average was 1.57/1.14/1.29 before and 1.21/1.27/1.32 after. The changed code is only on retry/online-event paths; the output-frame path is unchanged. Per-run metrics and full trace: summary, raw log.Committed d9142366a: online/foreground hints reset retry state and preserve a handshake already in progress; unit regression was observed failing before the change. Focused TypeScript tests: 11 passed, 0 failed, 42 assertions;
bun run checkexit 0. Mock browser: 2 passed; close-storm trace held at one live→connecting transition, zero live flashes. Production-server outage comparison: 20 trials per variant, branch median 1,388.5 ms vs base 1,593.25 ms. Main-thread cost: 8,081.567 vs 8,016.083 ms/1,000 frames (+65.483 ms/1,000, within run spread).Pre-rebase
scripts/verify fastfinding. Full gate log: verify-fast-final.log.The only failed row was the AArch64 static-size gate: 19,517,536 bytes against the unchanged 19,513,440-byte cap (+4,096 bytes). x86_64 passed at its exact 21,320,400-byte cap. I left both caps unchanged.
origin/devnow includescb25dae3b(the mesh recovery merge, #229), so I am rebasing the seven pill-flicker commits onto current dev and will rerun the gate.Rebased the seven pill-flicker commits onto
origin/devat3b2183eaa; this includes the mesh recovery mergecb25dae3b(#229). No Rust source files differ from that base. Final clean gate transcript: verify-fast-final-clean.log.The gate still fails in inherited build checks. Fresh static artifacts measure x86_64 21,332,784 bytes versus cap 21,300,016 (+32,768) and AArch64 19,549,528 versus cap 19,500,376 (+49,152). The static metadata review also reports the Cargo registry path present in both artifacts. I left both caps unchanged. In
cargo test,access_updatescould not be executed because its test binary path was missing (No such file or directory); the separateagent_daemonrerun passed 1/1 in 33.94 s.Final
scripts/verify fastsummary, verbatim:Finished and pushed
pill-flickerat18ee29012547f809243fd643c8e40a9419dd927b, rebased onorigin/dev3b2183eaa. No deploy. The final screen recording and frame are attached: 10-second production-build recording, final frame. This uses the synthetic sign-in-style workload because the staged agy run exited before rendering; the actual agy bytes remain unavailable.The reconnect regression and browser checks pass; the final clean
scripts/verify fastdoes not. Its full log is attached in the preceding comment. The pill-flicker commits add no Rust or Cargo inputs relative toorigin/dev(13 changed files are tracer TypeScript/tests and taste documentation), so the Rust binary source delta is 0 B. The fresh artifacts still exceed current caps: x86_64 21,332,784 B (+32,768), AArch64 19,549,528 B (+49,152). I did not change the caps.Final
scripts/verify fastoutput, verbatim:Start: branch
review-pill-flicker-r2at target18ee29012547f809243fd643c8e40a9419dd927b, based onorigin/dev3b2183eaa. Read all 109 #227 comments; the issue has no parent link. The target already contains the corrected outage and toast regressions from the prior review, so their cherry-picks were empty/conflicting duplicates and were skipped. Starting the web-only delta review and perf-VM recovery comparison.Delta check at
18ee29012547f809243fd643c8e40a9419dd927b: the prior outage regression (9.5 s recovery-window case) and toast replacement regression both pass in the target: 11 tests, 42 assertions, 0 failures acrossconnection-restart.test.tsandcanvas/status.test.ts. The target already carries these corrected tests; no review change was needed for these cases.Production web builds completed for the comparison inputs: base
3b2183eaac8a9d8cc52a922dfbbbaecf895fd854and target18ee29012547f809243fd643c8e40a9419dd927b. Bothbun run buildcommands passed through the host build-slot wrapper. The built dist directories are staged under/home/kayg/.cache/codex-scratch/pill-flicker-review/build/pending transfer to the perf VM.Build receipt: production dist sizes are base
3,362,136bytes and target3,365,171bytes (+3,035 bytes). Both builds passed; source revisions remain base3b2183eaaand target18ee29012.Perf-VM base run completed on
3b2183eaac8a9d8cc52a922dfbbbaecf895fd854: ten 10-second offline trials against server artifact492e1505a(SHA-25628212cdd96ef716d81502dc3991f8556460f75f8899de4a8f9b8f5e3243d4ea8). Return-to-live median 1,076.350 ms (628.200–2,658.600); 12–30 WebSocket error events per outage (median 14.5). Perf VM: 4 vCPU, 7.7 GiB; load average 0.54/1.08/1.10 before and 0.95/1.08/1.09 after. Raw receipt is saved under the review scratch evidence directory; head run follows.Additional targeted checks at target
18ee29012: close-reason and connection-liveness tests passed (2 tests, 15 assertions, 0 failures). The outage and toast regressions remain green (11 tests, 42 assertions).Head real-browser network-cut measurement complete on the perf VM. Ten 10-second offline trials per build, with the same server artifact: base return-to-live median 1,076.35 ms (628.2–2,658.6); target 1,002.7 ms (541.8–3,175.6). Offline WebSocket error events were base median 14.5 (range 12–30), target median 14 (12–30). VM: 4 vCPU, 7.7 GiB; load average base before/after 0.54/1.08/1.10 and 0.95/1.08/1.09; target 1.59/1.59/1.30 and 1.05/1.38/1.26. Raw logs:
/root/infraternal-bench/review-pill-flicker/evidence/network-base.logandnetwork-head.log.R2-1 (P2):
a failed first recovery handshake exits the one quick retryfails on target18ee29012: after an attached route disconnects, the first fast retry fails beforeWelcome, and the next delay remains 5 ms (expected at least 50 ms).disconnectresets retryAttempt for unwelcomed sockets while retaining fastRecovery when no server Hello arrived, so immediate handshake failures can keep retrying at the fast interval. Test-only commit:a0068ce77; raw output:/home/kayg/.cache/codex-scratch/pill-flicker-review/retry-edge-test.log. The 10-trial offline browser run did not show a higher error count (base median 14.5, target 14); this finding is scoped to immediate handshake failures.Finished delta review. Job target:
18ee29012; review branchreview-pill-flicker-r2ends ata0068ce77with one failing-test-only commit. No implementation files were changed, and nothing was pushed.Earlier regressions pass: the corrected prolonged-outage retry test and shared-toast ownership test passed in the focused run (11 tests, 42 assertions); the close-reason and liveness tests passed (2 tests, 15 assertions).
bun run checkpassed. Fresh production builds passed: base 3,362,136 bytes, target 3,365,171 bytes (+3,035 bytes).The 10-trial, 10-second network-cut comparison recorded base/target median return-to-live of 1,076.35/1,002.7 ms and median offline WebSocket error events of 14.5/14. Finding R2-1 is P2: the first failed unwelcomed recovery handshake retains the fast retry interval (5 ms instead of at least 50 ms). Its failing test is committed as
a0068ce77and is recorded infindings.md.Verdict: MERGE (0 P0, 0 P1, 1 P2). The job report records this
scripts/verify fastsummary for18ee29012(Rust rows excluded from this web-only review):Start: branch
pill-flickerat18ee29012547f809243fd643c8e40a9419dd927b, based onorigin/dev3b2183eaac8a9d8cc52a922dfbbbaecf895fd854. Read all 118 #227 comments; issue metadata has no parent link. Continuing only R2-1: cherry-pick the unwelcomed-handshake regression and confirm it fails before changing retry scheduling.R2-1 reproduced at
459136e77: after an attached stream drops, the first reconnect fails beforeWelcome, and the next retry is still scheduled at 5 ms.bun test src/connection-restart.test.tsreports 6 pass, 1 fail; the regression expected >=50 ms and received 5 ms. I’m tracing the unwelcomed-socket reset and one-shot fast-retry state before the fix.R2-1 complete and pushed on
pill-flickerat291d431d2638c0cbc351e2f1a9299052a88947bb(test-first commit459136e77; no deploy). An online recovery handshake that fails before Welcome now consumes the one quick retry and retains the retry count, so repeated failures grow from the 50 ms schedule. Fast probes remain while the browser reports offline; online/visibility hints still make an immediate attempt.The regression failed before the fix with
Expected: >= 50; Received: 5. Final focused connection tests:bun run checkpassed. TypeScript gate results, verbatim:Critical transfer: identity 963430 B, gzip 298140/307200 B, Brotli 248013/256000 B. Full TypeScript gate exited 0. As requested for this web-only continuation, Rust gate rows were not rerun.
Receipts: connection regression log, TypeScript gate log.
Live in production 2026-10-17 (web only, no restart) at
c47a438be7, merged into dev. Server-side cause still unknown; close-reason logging job queued.Start: branch
stream-close-log, baseb231301396227821a992692410cf55c50abe04ef(origin/dev). Read #227’s complete comment history; it has no parent issue link. The owner’s 2026-10-17 rootless-host case is in scope. Tracing server WebSocket/session and server↔infraternal-agent stream exits before implementation.Finding: terminal close attribution is lost at three boundaries.
crates/infraternal-server/src/websocket.rs:1509logs onlyagent_streamand labels every attachment embedded, including remote hosts;websocket.rs:2004discards browser WebSocket close code/reason;remote_link.rs:826-845converts agent stream read failure to Close 1013 without logging host/session context. The attach path already readsremote_host_idand stores session cursors, so close records can include both IDs and existing counters without adding work to each output frame.Step 1 complete: 17c4c02d8 adds the content-free session close logger, 60 s summaries, bounded session state, and a 512-close storm test. Focused test: 1 passed, 0 failed. Server crate clippy: PASS.
Correction: step 1 commit body was amended for readable paragraphs. Current commit is
0e23cc5ea; it replaces 17c4c02d8. The logger test and clippy results are unchanged.Finding: the client close-reason table inherited object keys, so an unknown reason such as
constructorcould bypass the fallback.apps/tracer/src/close-reasons.tsnow accepts own keys only; tests coverconstructorand__proto__and unknown text. Client checks: typecheck passed; 17 tests passed.Step complete:
182a208c4updates the existing terminal status/toast mapping with safe close explanations, preserves neutral detach/rotation text, and discards unknown close text.bun run checkpassed; 17 focused client tests passed (4 files, 71 assertions).Production queue fix now tracked on #234, branch ping-fifo-fix, base
2a5302cc7, initial regression54d24be15from #172 RLR-13. The scheduled Ping bypasses native control FIFO readiness, and Full is propagated as connection failure. Audit also found server Pong admission, host control replies and terminal-command Ping admission need coverage. Fix and natural terminal-flood comparison are in progress; no production deployment.Rootless add-host finding: an attached terminal with no live mesh peer could return a terminal-stream refusal while leaving the control socket usable, so the prior logs did not identify why the window stayed reconnecting. The rootless refusal now logs the server as closer, reason, host/session IDs and stream counters, and the test confirms the control socket still answers Ping/Pong. Focused test
rootless_remote_host_without_a_live_peer_logs_the_terminal_refusalpassed; implementation:crates/infraternal-server/src/websocket.rs, coverage:crates/infraternal-server/src/auth/socket_tests/close_logging.rs.Focused server tests are queued in the required shared build-slot wrapper (15 minutes so far; four slots are occupied). The run is still waiting, not compiling; no owner action is needed. I’m continuing the read-only close-path review and will resume gates when a slot opens.
Client-map finding: server-generated reasons
the terminal reader fell behind and is reconnectingandthe terminal on the host stopped respondingfell through to the generic unsafe-reason text because the client only mapped the older peer strings. Regression evidence:bun test src/close-reasons.test.tsfailed the new assertion atapps/tracer/src/close-reasons.test.ts:18(4 passed, 1 failed). The mixed-reason summary text also lacked a specific mapping.Committed
0fdb45d96d6a4c50e2cf66eedb5666bce642788b: map the server-generated route and mixed-reason text in the shared client status surface.bun test src/close-reasons.test.ts src/canvas/status.test.ts src/connection-restart.test.ts src/connection-liveness.test.ts: 18 passed, 0 failed, 74 assertions.bun run check: exit 0.Logger unit tests passed on head
0fdb45d96d6a4c50e2cf66eedb5666bce642788b: 3 passed, 0 failed. The 512-close session storm emitted exactly 2 lines (one first-close record and one 60-second summary); the same suite verifies close counters and rejects untrusted peer text.Transient test finding:
review_full_input_writer_preserves_browser_control_progressmissed its attach ACK in the 9-test focused module run (8 passed, 1 failed atcrates/infraternal-server/src/auth/socket_tests/review_input_credit.rs:226). The same fully qualified test passed alone on immediate rerun (1 passed, 0 failed, 0.88 s). No code changed for this failure; I’ll recheck the module and note any matching final-gate baseline result if it recurs.Committed
2f2e59f9a2b03c8daa7a7061a6e3639679fdc922: classify and log browser/agent terminal closes, including rootless attach refusal and retained-session resume refusal. Gates:cargo fmt --all -- --checkandgit diff --checkpassed; strict server clippy exited 0. Focused tests: logger 3/3 (512 closes -> 2 lines), close paths 4/4, input/credit module 9/9 on rerun, transport end classification 1/1, protocol/size/idle classification 1/1. The initial input/credit module attempt had one transient ACK miss; the exact case and full module passed on rerun, as recorded above. Rootless test asserts host UUID, session UUID, refusal reason and live Ping/Pong./home/kayg/.local/state/codex-jobs/infraternal/orchestrator/build-slot scripts/verify iterateexited 1 with one test failure:daemon::gone_tests::a_vanished_session_gets_a_final_close_and_the_channel_stays_upatcrates/infraternal-agent/src/daemon_gone_tests.rs:111. The fixture appended 25 characters and a 47-character rendezvous suffix to the 38-character TMPDIR alias, producing a 110-character Unix socket path (over SUN_LEN). This is a test-root path issue, not a close-log assertion. I’ll rerun with a unique 35-character alias (107-character final path) and use that root for the final gate.Correction: the 35-character alias calculation omitted the separator before
g<id>; its actual rendezvous path was 108 characters. The isolated agent test passed with a 33-character TMPDIR root, giving a 106-character socket path: 1 passed, 0 failed, 0.03 s. I’ll use that direct short scratch directory for the full iterate rerun.Corrected-root
scripts/verify iteratereached the Rust auth/socket binary: 476 passed, 2 failed, 48 ignored. Failures were only the known auth/review3 group:review3_revoked_resume_must_not_welcome(crates/infraternal-server/src/auth/review3_tests.rs:794) andexpired_handshake_releases_user_permits(crates/infraternal-server/src/auth/tests.rs:1763). Both asserted close/no-Welcome outcomes and neither is a close-log test. The required dev-baseline comparison will be run if either recurs in finalscripts/verify fast.Root-cause finding from #234 (ping-fifo-fix, head
c179cdcff), measured on the perf VM:Under a 10-minute saturating terminal flood plus a paste, at 150 ms RTT with 1 % loss, origin/dev closes the host primary 12 times. At LAN it closes it 0 times. Each close comes from the server's liveness deadline expiring, not from a dead peer and not from the full-queue Ping that RLR-13 found. One lost packet on the ordered QUIC control stream holds back every later Pong for about 2 round trips (about 300–450 ms at 150 ms), and the deadline is about 250 ms (srtt + 4·rttvar + 50 ms, floor 250 ms). That fits "The connection to the server ended." and dot flicker on the India↔Germany link.
The queue fixes on ping-fifo-fix are real P1 gaps with regressions, but under load they do not change the close count. The deadline is an owner decision (D265, about 1-RTT detection). Options A (one-recovery deadline), B (doubt then retire) and C (probes in QUIC datagrams) are on #234.
Finish: head
d1c21ad312on branch stream-close-log (pushed). Continued from2f2e59f9a.Added since
2f2e59f9a:3f982adebserver tests accept the named application Close frame before the native close (fixes review3_revoked_resume_must_not_welcome, expired_handshake_releases_user_permits).6ee9205bcagent: one rate-limited line per server-link close or refusal (closed-by, link kind, path reason, lifetime); first line per 60 s window, repeats counted.081c3f8c7tracer: one rate-limited console.warn per socket close naming code, mapped reason, reconnect attempt and whether the connection was ever established (failed reconnect).Overhead (perf VM 10.69.69.208, release build, flock, load 3): suppressed repeat close (the storm case): 149-162 ns/close. First close of a new session with a full map: 71 us/close before the sweep fix (8,192-window scan per close), 5.4 us after (formats and writes one JSON line); one run 13.8 us under other load. Forwarded frames never call the logger (close_logs.record only at close sites). 512-close storm test: 2 lines. 8,692 distinct sessions: map stays <= 8,192, every first close logged.
Size (D243): static x86_64 21,337,104 B vs dev
7392b925bbuilt here 21,300,016 B = +37,088 B (aarch64 +33,104). OVER the 16 KiB goal and over the exact cap, so the static-size row fails. cargo bloat by function: stream_close ~14.2 KB (logger worker task, map, emit), websocket close handling ~7.8 KB, websocket_transport ~2.8 KB, rest ~11 KB strings/tracing/sqlx/agent. Needs an owner decision: accept and ratchet the cap, or cut (dropping the timer task for lazy summaries and the HashMap for a small Vec would save an estimated 5-7 KB; not enough alone).Gate (scripts/verify fast, rerun on head):
+---------------------------+--------------+
| Rust gate | Result |
+---------------------------+--------------+
| fmt | PASS |
| VT native fmt | PASS |
| CLI plugin compile-out | PASS |
| clippy | PASS |
| test | PASS |
| cargo-deny | PASS |
| static builds | FAIL (1) |
+---------------------------+--------------+
static builds failure is only the size cap above. Earlier runs on this branch had load-only failures (browser_input_overload_retires_only_the_terminal_stream, saturated_native_control_queue..., close-log 2 s wait, failed-resume test); each passed alone and the last full run passed.
Break-it review (Claude Opus) of the close-logging branch started.
Branch: review-stream-close-log, base origin/stream-close-log
d66f8041f.Focus: secrecy of logged/close text, log injection, limiter bypass, memory under churn, summary worker lifetime, blocking on the close path, client reconnect behaviour with the new Close frame.
Break-it review: stream-close-log (#227) at
d66f8041fReviewer: Claude Opus (independent). Review branch: review-stream-close-log.
Clean (checked, no finding)
&'static strfrom a fixed table, a number, or a UUID.Browser WebSocket close reasons, browser stream Close reasons and infraternal agent Close reasons go
through allow-lists (
safe_websocket_reason/safe_agent_close_reason), unknown -> "other". Nonewline/ANSI/huge text can reach the log or the browser. No tokens, terminal content or usernames are
logged. The agent lines print only enum Debug values and
io::ErrorKind. The client prints only a numberand a mapped key.
never by reason text, so no reason can stop reconnects. Old bundles handle the control Close the same way.
Inner::dropaborts it; no leak.P1-1 Authenticated client can flood the server log, one line per frame (limiter bypass)
AttachTermwith a session id the account does not own:session_host_id-> Ok(None),owned_writerfails,session_gone-> true ->close_logs.record(AGENT_STREAM_ENDED)+ Close 4404,socket stays up. The stream id can be reused (
EndedStreams::absorbsremoves it on AttachTerm), so asingle socket can loop forever. The limiter keys on the client-chosen session id, so every frame is a new
"first close" and writes a ~600-byte JSON line to stderr (sync writer,
runtime.rsconfigure_logging) fromthe socket's tokio worker. Before #227 this path logged nothing.
Also mislabels a never-existing session as who=server, reason agent_stream_ended, and lets a client write
another account's session id into the log (and pre-open its rate window, suppressing that session's real
first-close line for 60 s): the key has no user component.
Failing test:
auth::socket_tests::review_close_log::attaches_to_unowned_session_ids_cannot_flood_the_close_log(64 attach frames -> 64 lines, bound 8).
P1-2 A full map turns rate limiting off for every session
Once 8,192 live windows exist (P1-1 fills them in seconds), any session not already tracked is "logged but
not tracked" on every close: a real reconnect storm logs one line per close for up to 60 s. The comment
claims memory stays bounded (true) but the log volume is unbounded exactly when it matters.
Failing test:
stream_close::review_tests::a_full_map_does_not_turn_off_rate_limiting_for_a_storming_session(1000 closes of one session -> 1000 lines, bound 2).
P1-3 Client console line is skipped for every close the server names
The claim is "client console warn per close incl. failed reconnects". The server now sends a control-stream
Close before its native close on handshake refusal (1008), protocol/size/idle ends, and teardown (1011).
The client's
control()handles Close withforceClose()->disconnect(); the lateroncloseseesthis.socket !== socketand returns beforecloseNotes.note. So failed reconnects refused by the server,and all server-named closes, print nothing; only native-only/network closes print.
Failing test:
apps/tracer/src/review-close-log.test.ts"a reconnect the server refuses with a named Closeframe still prints its console line" (0 warnings, expected 1).
P1-4 The branch breaks an existing tracer test and reverts approved plain wording
apps/tracer/test/close-reasons.test.ts(fromcd7b07262"close reasons ... read plainly") passes onorigin/dev and fails on the branch: "terminal command queue full; ..." now maps to "Connection closed: terminal
input exceeded the host queue limit." (jargon the test forbids). The rewrite of
KNOWNalso brings back"flow-control limit", "invalid protocol frame", "control reply" wording and changes the fallback sentence.
The fast gate does not run
test/close-reasons.test.ts,src/close-reasons.test.tsor the connection tests,so
scripts/verify fastcannot catch it (that gate gap is itself worth fixing).Evidence:
bun test test/close-reasons.test.ts-> 0 pass / 1 fail on the branch; same file againstorigin/dev's close-reasons.ts -> 1 pass.
P2-1 Summary worker rescans the whole map on every first close
record_atcallschanged.notify_one()on each new window;runwakes and computesminover allwindows while holding the logger mutex: O(n) per first close (n up to 8,192), contending with
record.The shipped
close_path_costbench runs on a current-thread runtime, so the worker never runs inside thetimed loop; the 5.4 µs figure excludes this cost. Windows are inserted in time order, so a VecDeque of
(deadline, key) gives O(1). Hand bench added:
stream_close::review_tests::first_close_cost_with_the_worker_running.Measured (debug build, loaded build host at load ~35, NOT the perf VM, indicative only):
current-thread runtime 2,131 ns/first close; 4-worker runtime (worker actually runs) 53,607 ns/first close
with 4k-8k live windows: about 25x slower. Needs a perf-VM release number before merge.
P3
CloseNotessummary says "N more closes in the previous 60 s" even when those closes were hoursearlier. Failing test in
review-close-log.test.ts. Same wording issue in the agentLinkNotes.LinkNotesand clientCloseNoteshave no timer: suppressed closes are reported only ifanother close happens later; a burst followed by a stable link is never summarised (server has a timer).
run_socket'sInbound::SocketClose | Inbound::Failurearm is dead: the transport read taskconsumes both and only retires the context,
recvnever yields them. It duplicates the logging code(binary size) for nothing.
before the native close; a busy SQLite delays closing a refused socket.
terminal.reconnectingnow shows "Connection closed: ..." whichstatus.tstreats as a connectionfailure toast, for a reconnect that recovers by itself; agent reasons "session ended"/"detached" with a
non-gone code now show "The terminal session ended." while the client reattaches.
Branch and gate
Head: review-stream-close-log
1025ba97d(pushed), on top of stream-close-logd66f8041f. Only tests were added; no fixes.scripts/verify fast(verbatim excerpts; it did not reach its final table because the build host disk ran out):The only Rust failures are the two intended review tests. The release step failed with ENOSPC (host / was at 97%); I deleted my 22 GB target afterwards. Tracer (
bun test src/close-reasons src/connection src/terminal src/review-close): 63 pass, 2 fail (both intended review tests).Verdict: HOLD, 4 P1, 1 P2, 5 P3.
Start: fixing break-it review findings (P1-1..P1-4, P2, P3-1..5) on branch stream-close-log, base
d66f8041f, review tests cherry-picked (headd3e967003). Claude Opus job.Finish: break-it review fixes on stream-close-log, head
9c74ce405(pushed). Based66f8041f+ review tests (d95c35e0d,d3e967003).Commits, one per finding:
dd1cccc4b: approved plain close sentences back, new server reasons in plain words, fallback restored; fast gate now runs test/close-reasons, src/close-reasons, src/review-close-log, src/connection*.7b9f9309f: console line on every socket-retiring path (forceClose for named Close incl. refused reconnect, liveness timeout, wire violation).3a5b34929: "N more times since the previous line" + window-end timer.e946d0e8d: recovering link = routine "… Reconnecting…" status (no toast); non-final stream Close never says "The terminal session ended.".0fcb5ef28: limiter key = (account, reason category) from the authenticated user, never the client's session id; unowned attach logged as who=browser unowned_session_attach with session/host "none"; record() = try_send into a 1,024-record queue to one logger thread (drop and count, one warning per minute).0464b1d8e: full table -> server-wide window per category (limit_scope "server").bfefd4a33: LinkNotes timer + honest wording.fc46ae57d: refused-resume lookup after the close, 250 ms bound.cf456442a: dead socket-close arm removed.6a215e2da: deadline queue in start order (in P1-1) + multi-thread bench.6aa25c248,9c74ce405: test robustness (2 s authority bound in the flood test; settled() in the input-credit helper).6456aab86: size caps ratcheted down per D243/D264.P2 numbers, perf VM (4 vCPU, release, flock, 5 runs): record() on the socket worker 64-78 ns (first close of a new account), 44-64 ns (storm), 181-299 ns (4-worker flood far past the bound, excess dropped). Logger thread: 5.2-7.4 us per JSON line written, 292-383 ns per counted close. Baseline at
d3e967003(review bench, release, same VM): 1,022-1,266 ns current-thread, 1,679-2,642 ns 4 workers, on the socket worker, plus the line itself.Size: x86_64 21,306,416 B (D301 cap -30,688; +6,400 vs pre-#227), aarch64 19,492,672 B (-40,808; -7,704 vs pre-#227). Caps ratcheted to these.
New findings (evidence):
b23130139, 2/10 at1025ba97d(before these fixes), ~3/10 at head. So the original #227 branch introduced it. The hang is the last step: after advance(25ms) the server's probe after the open never comes. Not fixed; needs its own job.scripts/verify fast at
9c74ce405(verbatim):Tracer gate tests (bun, run separately since the gate stops at the Rust failure): 53 pass, 0 fail.
Start (Opus job, two items before review): branch
stream-close-log, base head9c74ce405.restart_open_liveness_review::responsive_socket_survives_a_held_terminal_open_with_an_existing_attachment(0/10 at merge baseb23130139, ~3/10 at head): measure base vs head, find the ordering cause, fix product or test.protocol_error/invalid frame: give it its own close reason.Finish (Opus job): head
4ac9e68fbonstream-close-log.1. Hang in restart_open_liveness_review: fixed in the product (
10432b599).The race is older than this branch: it exists in the proof loop at the merge base. The first attach flips the activity watch while a sink write's
proof_changedpermit is pending. The unbiased select can wake onproof_changedwith the loop's localactivestill false; the code then read the fresh value (*activity.borrow(), true), sent startup probe 1, and only on the next turn applied the activation, whosereset()clears issued probes. The browser's answer to probe 1 was refused, so the route stayed unsampled with sent=1 and the next probe fell due at origin+1 µs+25 ms: 1 µs after the test's single 25 ms advance, so under the frozen clock no probe ever came. Temporary trace from a hanging run:Fix: probe only when both the applied and the current activation are active (
active && *activity.borrow()). The test was right; it was not changed.Runs (40 s timeout each, same host): base
b2313013918 pass / 2 hang of 20; head9c74ce40516 / 4 of 20; head+fix 30 / 0 of 30. No bisect was needed: base hangs too, so the branch only changed timing.2. Authority timeout gets its own close reason (
85b4f987d,4ac9e68fb).New
AUTHORITY_UNAVAILABLE(server_busy / authority_unavailable, "the server was too busy to confirm the session"; on the page: "The server was too busy to confirm your session; reconnecting."). The frame path records whether the handler or only the authority wrapper failed; teardown confirms it with one bounded non-renewing read (Denied → resume refused, timeout/storage error → authority_unavailable, live → provisional reason). No change to while_authorized/authority_deadline/session_is_live; merge-tree against origin/authority-read-retry shows the same 2 pre-existing conflict hunks before and after. New test close_logging::authority_read_timeout_during_a_frame_logs_authority_unavailable (holds the pool's one connection): 10/10 alone, close_logging module 34/35 (one early failure before the teardown classifier existed; 30/30 since).Gate
scripts/verify fastat head:Static builds fail only on the exact size caps: x86_64 21307728 vs cap 21306416 (+1312 B), aarch64 19494176 vs 19492672 (+1504 B). The first version was +1888 B on x86_64; folding the classifier into a single teardown await site brought it down. Caps not raised (needs an orchestrator/owner OK, D243).
Break-it review round 2: stream-close-log (#227) at
300a1d6d9Reviewer: Claude Opus (independent). Review branch:
review-stream-close-log-r2e39549741(pushed). One commit, tests only, no fixes.Prior findings
unowned_session_attachwith session/hostnone;record()=try_sendto a 1,024-slot queue read by one thread.review_close_log::attaches_to_unowned_session_ids_cannot_flood_the_close_logpasses.a_full_table_keeps_rate_limiting_a_stormpasses; read the code, it is correct (see below).onerrorstill does not.test/close-reasons,src/close-reasons,src/review-close-log,src/connection*now ints.sh; they pass.VecDequein start order gives O(1). Perf VM numbers on the issue: 44–78 ns per close on the socket worker.cf456442a)e946d0e8d)P1-A A reconnect that fails at the network still prints no console line
socket.onerror(connection.ts ~438) callsdisconnect(socket)and does not callnoteClose. Browsers fireerrorand thenclose(1006) in two cases: when a WebSocket cannot be established, and when it drops uncleanly. Thecloseevent then failsthis.socket !== socketand returns. A failed reconnect (server down, network unreachable, TLS failure), the case the #227 console line was added for, therefore prints nothing. This path is older than the branch, but the branch claims "one console line per close incl. failed reconnects", and7b9f9309fsays "every path that retires a socket for a close calls it".Failing test:
apps/tracer/src/review-close-log-r2.test.ts"a reconnect that fails at the network (error, then close 1006) still prints its console line": expected 1 warning, got 0.Checked: account-key exhaustion (no finding)
The table holds (account, category) pairs and there are about 11 categories, so filling 8,192 windows takes at least 745 accounts closing within 60 s. One account cannot do it. Once the table is full, a new key goes to the window for its category, and at most about 11 server-scope first lines are written per 60 s. Those windows expire on their own deadline (
next_deadlineincludes them). Per-account tracking resumes as soon as account windows expire. Keys stay consistent: each key in the map has exactly one entry inorder, and onlyexpireremoves entries. A known design trade-off: during overflow, a real account's first close is folded into the server window.Checked: the logger thread blocking or dying
close-logthread waits. The queue tops out at 1,024 ×CloseRecord(about 200 KiB). Beyond that, closes are dropped and counted, and nothing pushes back on the sockets. The drop warning is written when the thread unblocks. OK.emitor the subscriber) orspawnfails (.ok()?storesNonein theOnceLock), every later close is counted intodropped, and nothing ever reports that count, because only the dead thread reports. Close logging then stops for the life of the process without a word. Suggestion: whentry_sendreturnsDisconnected, or the queue isNone, warn once from the caller with anAtomicBoolguard. No test: neither failure can be reached without a fault injection hook.Checked: teardown reads under a close storm
failure_reasonruns only with attachments, an Open transport, and a teardown reason of PROTOCOL_ERROR, AUTHORITY_UNAVAILABLE or SERVER_PROCESSING_FAILED. It does not run for ordinary closes such asclient_went_away, idle or size. Its cost is one SELECT, at mostAUTHORITY_READ_TIMEOUT(25 ms).session_is_live, a second read, which decides whether to send the 1011. For the frame path that is the same count as before (the old in-loopsession_is_livewas moved). For exits through the SERVER_PROCESSING_FAILED default (output, plugin and recovery arms whosewhile_authorizedfails), it is a new read. Under a store-wide stall every such socket now queues 2 pool waiters instead of 1, and its close reaches the browser up to 50 ms after the failure instead of 25 ms.failure_reason's result could decide the 1011 directly:liveorPROTOCOL_ERRORmeans send it,RESUME_REFUSEDmeans do not, a timeout means do not.P3-C A busy store can still be blamed on the browser
frame_failedis true wheneverhandle_client_framereturns Err. That includes failures inside the handler:session_is_livechecks (AttachTerm ×2, refused Call),session_host_idandowned_writerstore errors, andsession_gone(...)?. A read that misses its 25 ms bound inside the handler, followed by a teardown read that succeeds once the load spike has passed, is logged asprotocol_error/invalid_frame. The browser is then told "This window sent something the server did not understand". Thesession_host_idErr arm still writesPROTOCOL_ERRORinline with who=server. This is narrower than before85b4f987d, but its claim that a busy server never reads as a misbehaving browser holds only for the wrapper's own reads. No deterministic test: hitting it needs the handler's read to stall while the wrapper's 25 ms tick does not.P3-D
10432b599does not cover active → idle → activelet active = active && *activity.borrow();blocks probes for an activation the loop has not applied, when the applied state is idle. It does not block them when the applied state is active and the watch went false and then true again without the loop seeing it. That happens when the last attachment closes and a new attach follows, which is the routine single-terminal reattach after "reader fell behind" or a stream Close. The borrow then reads true and the local value is true, so a probe goes out. On the next turnchanged()runsreset(), which clearsissued, and the answer is refused: the same wasted sample and one-tick delay the commit describes. Idle → active → idle and active → idle are handled correctly. Suggested condition:active && !activity.has_changed().unwrap_or(true). No deterministic test: it needs the proof task to be parked insideauthority.runwhile the socket loop detaches and attaches.P3-E Wording of the new sentence
"The server was too busy to confirm your session; reconnecting." is the only sentence that ends in "; reconnecting." The recovering sentences read ". Reconnecting…". Failing test:
review-close-log-r2.test.ts"the busy-server sentence reads like the other recovering sentences".Gate
scripts/verify fastate39549741(verbatim table):The test failure is
infraternal-plugin-ssh --test config_isolation::configured_identity_paths_are_not_inherited_by_the_command_child("The SSH helper could not start."). The branch does not touchcrates/plugins/ssh, and the test passed when rerun alone (2 pass), so it is a host flake. Cargo stopped at that binary, so I rancargo test -p infraternal-server --libseparately: 294 passed, 0 failed, 23 ignored. Static size caps PASS at the ratcheted values: 21,307,728 B (x86_64) and 19,494,176 B (aarch64). The gate never reached the tracer step. Run by hand (bun test src/review-close-log-r2.test.ts src/review-close-log.test.ts test/close-reasons.test.ts src/close-reasons.test.ts src/connection): 53 pass, 2 fail, and the 2 failures are the intended review tests.Verdict: HOLD (1 P1, 0 P2, 5 P3). P1-A is a small fix:
noteClose(undefined, "", …)inonerrorbeforedisconnect.Round-2 fixes pushed to stream-close-log, head
57cf18adbb(base300a1d6d9; review testse39549741cherry-picked as38f2f1e66).scripts/verify fast (final table, verbatim):
Size: static builds FAIL on the caps only: x86 21309072 vs cap 21307728 (+1344), ARM 19495376 vs 19494176 (+1200). Not raised.
Release 3 deployed to production (~16:03 IST): release-prod-live
5b2a1b9ee, server sha256 aacc763d…, MainPID 88336, /healthz ok. Contains Retry after a failed instant setup (#230) and connection-close reasons (#227). Staging: Retry after injected instant failure passed rootless + root on the perf VM (prompt ~350 ms, fully joined ~33 s). CA locked after the restart until the owner unlocks it.kayg referenced this issue2026-10-28 17:53:56 +00:00