Skip to content

fix(e2e): wall spec two-clients scenario answers the recovery offer on its raw second context (+ cloud image entrypoint mode pin) - #736

Merged
danshapiro merged 6 commits into
mainfrom
the-usual/duplicate-respawn-single-pty
Sep 7, 2026
Merged

fix(e2e): wall spec two-clients scenario answers the recovery offer on its raw second context (+ cloud image entrypoint mode pin)#736
danshapiro merged 6 commits into
mainfrom
the-usual/duplicate-respawn-single-pty

Conversation

@danshapiro

Copy link
Copy Markdown
Owner

What

Two small, evidence-backed fixes:

  1. e2e harness fixrestore-contract-wall-rust.spec.ts's two-clients-same-sessionRef: duplicate respawn must yield exactly 1 PTY failed deterministically (4 recorded runs) but never produced the 2-PTY signature it claimed to hunt. Mechanism: the spec runs file-wide recoveryOfferHandling: 'manual', and this test creates client B via raw browser.newContext(), bypassing the fixtures' installRecoveryOfferAutoDeclineOnContext watcher. B is legitimately offered A's live session (designed behavior); the unanswered modal intercepts B's sidebar click forever (no actionTimeout configured), the test burns its 300s budget, and dies before the server restart ever happens — argv log 1 entry, finally-masking hides it. The fix installs the documented shared adapter on contextB (canonical RESTORE-01 pattern per multi-client.spec.ts), running B through the real decline path. No assertion weakened; manual-mode panel-owner tests untouched.

  2. Infra fixdocker/cloud-run/Dockerfile\' pins chmod 0755instead ofchmod +xfor/usr/local/bin/e2e-entrypoint.sh. That path sits outside the chown -R node:node /app, so a checkout whose umask narrowed the source file (observed 700 in the wild; git tracks only the x bit, so invisible) produces images that fail instantly with EACCES as the node` user.

Investigation receipts (brief premise falsified by evidence)

The run brief suspected a product regression in duplicate-respawn 'introduced by the interactive-create queue (PR #706)'. Twin explorer investigations plus experiments at both ends of the window established: (a) the queue landed in #705, and respawn creates (restore: true) never travel it; #706's create-path diff is empty; the #706 label came from a mislabeled comment added by #708. (b) The one-PTY server machinery (D8 per-sessionRef single-flight, adoption, bounded redrive) is byte-identical across the window and converged in 12/12 runtime probes. (c) At pristine 3c52b2c the test fails with the identical pre-restart wedge signature; with this same one-hunk adapter it passes in 58.7s — the harness footgun predates the window, and no in-window product regression exists.

Validation

  • Focused red→green: scenario passes 1/1 in 1.7m on the fix (auto-decline observed firing); neighbor panel-owner test (SIGKILL-within-5s-of-pane-creation) passes 1/1
  • Fresh-eyes independent review: PASS, 0 findings
  • Full gate npm test green at head: 4/4 cloud vitest shards + electron suite 432/432 (cloud image built under the gcloud-robot identity), plus the entrypoint-mode fix verified end-to-end by that build

Full recap: .worktrees/.the-usual-logs/duplicate-respawn-single-pty/recap.md (validated). Follow-up hazard filed as kata y83s (finally-masking orphans the fixture server on test timeout).

…wser context

two-clients-same-sessionRef creates client B with browser.newContext(),
bypassing the fixtures' context override, in a spec that runs file-wide
recoveryOfferHandling:'manual'. B boots with fresh localStorage and is
legitimately offered the session A just opened; the unanswered modal
interdicts B's sidebar click forever (no actionTimeout configured), so
the test burned its whole 300s budget and died before restartAbrupt()
ever ran — presenting as '0 respawns' rather than the one-PTY contract
violating. Deterministic at both ends of the suspected window (base
00fec15 and 3c52b2c proven by identical probe): a long-standing
harness footgun, not a product regression — the server-side per-sessionRef
single-flight converges to exactly one PTY in every probe.

Install the documented installRecoveryOfferAutoDeclineOnContext adapter
on contextB (canonical RESTORE-01 pattern per multi-client.spec.ts), so B
declines through the real product path. No assertion weakened.
The e2e cloud image copies docker/cloud-run/entrypoint.sh into
/usr/local/bin (outside the chown -R node:node /app) and then runs as
USER node. A checkout with a restrictive umask (observed 700 on
entrypoint.sh in fresh worktrees and the main checkout) copies in as
700 root-owned; chmod +x cannot widen read permission, so the node
user gets EACCES and every Cloud Run task fails instantly with
'bash: /usr/local/bin/e2e-entrypoint.sh: Permission denied'. Git only
tracks the x bit, so the hazard is invisible in diffs. Pin 0755.
… /proc reads

spawn_record_carries_verifiable_proc_identity_and_freshagent_lane failed
twice on CI (kata w0xf): between fork() and execve the child's
/proc/<pid>/cmdline still shows the PARENT's argv, so the immediate
post-spawn record captured the test-harness binary while the re-read
observed [sleep 300]. Under current runner load the race loses 2/2.
spawn_sleep_child now waits (bounded 2s) for /proc to show the execed
sleep before returning, so every record/verify read observes post-exec
identity.
… child execs

Delta-round-2 review (Major): the test-side exec-wait I added earlier
would have masked a production race. The author's evidence poll waited
for a NON-EMPTY /proc/<pid>/cmdline, but between fork() and execve the
child's cmdline mirrors the SPAWNER's argv — non-empty and wrong — so a
record written mid-window persisted the server/test binary's argv as
the sidecar identity and read as Mismatch at the next boot reconcile
(CI receipt: spawn_record_carries_verifiable_proc_identity_* failed
2/2, LEFT was the test harness's own argv; kata w0xf).

Production now owns the fix: accept evidence only once it stops
matching our own argv (exec landed), and fail fast when the child
vanishes pre-evidence instead of burning the 2s budget on a dead pid.
The test helper reverts to immediate-after-spawn recording so the
immediate-call contract stays covered.
… deadline

Round-3 minors: (1) own_argv came from env::args(), which keeps empty
args and panics on non-Unicode, while proc_cmdline drops empties and
decodes lossily — read own argv through the same parser so the pre-exec
mirror predicate compares identical representations (panic on freshcodex
creation in weird-launch cases eliminated). (2) cap the poll sleep at
the remaining budget and decline to sample past the deadline, so the 2s
budget is a ceiling rather than a hint.
@danshapiro
danshapiro merged commit 990afc5 into main Sep 7, 2026
5 of 6 checks passed
@danshapiro
danshapiro deleted the the-usual/duplicate-respawn-single-pty branch September 7, 2026 09:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant