Skip to content

test(pi): a failed live Git Bash launch now says why (#504) - #505

Merged
SUaDtL merged 1 commit into
mainfrom
fix/504-diagnose-live-spawn
Jul 26, 2026
Merged

test(pi): a failed live Git Bash launch now says why (#504)#505
SUaDtL merged 1 commit into
mainfrom
fix/504-diagnose-live-spawn

Conversation

@SUaDtL

@SUaDtL SUaDtL commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

What

When the live Git Bash launch fails, the test now reports the spawn error instead of expected undefined to be defined.

Diagnosis first — the obvious reading was wrong

I filed #504 flagging a possible registration race: the runtime reporting success while owning nothing, which in a background-job tracker means leaked processes.

That branch is ruled out. launch is declared Promise<Snapshot | undefined>, and undefined is a designed outcome. All five refusal paths transition the job to a terminal state before returning:

# condition disposition
1 runtime disposed / stopping / unhealthy no job created
2 unparseable input, or a stale lease no job created
3 manager.createJob refused no job created
4 authorization went stale after publish job → cancelled
5 #openTree threw job → failed, ownership released

The runtime is fail-closed and correct — it never owns an untracked process. That AC is withdrawn on the issue.

What is actually wrong

Path 5 discards its error in a bare catch {} — not bound, not logged, not carried on the terminal snapshot. So a real environment failure is indistinguishable from a policy refusal, and the operator gets an assertion with no cause. The test's own message compounded it: "no job handle — the contained launch never returned" is false. The launch did return; it returned a refusal.

Scope, deliberately narrow

Surfacing the reason from the runtime means extending the published activity event ({kind, id, label, state} has no detail field) or changing one path's refusal semantics. Both are contract decisions that deserve a call rather than being slipped in behind a flake fix — so they stay on #504 as the revised AC-2.

This takes the part that needs no contract change: the live test wraps the tree opener, captures the throw, and reports it. Production code is untouched.

Verified by mutation, not inspection

Pointing shellPath at a nonexistent binary now fails with:

AssertionError: launch refused because the spawn threw: ENOENT: no such file
or directory, lstat '...\no-such-shell.exe'

instead of the bare assertion. The diagnostic also distinguishes the two cases — a spawn that threw, versus a refusal that never attempted one — so a future failure points at which of the five paths fired.

What this does not do

It does not fix the flake. It makes the next occurrence explain itself, which is exactly what was missing when it cost a re-run cycle on #503.

tsc --noEmit clean; background-jobs.test.ts 38/38.

Versions

No bumpplugins/ca-pi/tools/** is outside the shipped payload, and this is test-only. payload_scope.py reports unchanged.

Refs #504

https://claude.ai/code/session_01Y8UPz5RZwgnda3NbAVfd6L

The CI failure this addresses arrived as `expected undefined to be defined`
with no cause, and the test's own diagnostic - "no job handle, the contained
launch never returned" - actively misdescribed it. The launch DID return. It
returned a refusal.

Diagnosis first, because the obvious reading was wrong. `launch` is declared
`Promise<Snapshot | undefined>` and `undefined` is a DESIGNED outcome: all five
refusal paths transition the job to a terminal state before returning, so the
runtime is fail-closed and never owns an untracked process. The registration
race I feared when filing #504 does not exist, and that AC is withdrawn.

What is wrong is that path 5 - the `#openTree` spawn - discards its error in a
bare `catch {}`, so a real environment failure is indistinguishable from a
policy refusal.

Fixing that properly means extending the published activity event
(`{kind, id, label, state}` has no detail field) or changing one path's refusal
semantics, both of which deserve a decision rather than being slipped in behind
a flake fix. So this takes the part that needs NO contract change: the live test
wraps the tree opener, captures the throw, and reports it.

Verified by mutation rather than by inspection - pointing shellPath at a
nonexistent binary now fails with

  launch refused because the spawn threw: ENOENT: no such file or directory,
  lstat '...no-such-shell.exe'

instead of the bare assertion. The diagnostic also distinguishes the two cases:
a spawn that threw, versus a refusal that never attempted one.

This does not fix the flake. It makes the next occurrence explain itself, which
is what was missing when it cost a re-run cycle on #503.

Refs #504

Claude-Session: https://claude.ai/code/session_01Y8UPz5RZwgnda3NbAVfd6L
@SUaDtL
SUaDtL merged commit d11c745 into main Jul 26, 2026
38 checks passed
@SUaDtL
SUaDtL deleted the fix/504-diagnose-live-spawn branch July 26, 2026 16:20
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