Skip to content

docs(e2e): fix Phase 3b broken --arg query (P0) and sweep $LASTEXITCODE house rule across E4 + E1 - #1821

Merged
bradygaster merged 1 commit into
devfrom
sims-e4-p0-lastexitcode
Aug 21, 2026
Merged

bradygaster merged 1 commit into
devfrom
sims-e4-p0-lastexitcode

Conversation

@bradygaster

Copy link
Copy Markdown
Collaborator

Branched fresh from cbf90813 (the merge commit of #1820). Bundles the P0 (Phase 3b query was structurally incapable of running) with the P1 sweep of the $LASTEXITCODE house rule Coordinator established across both E-series procedures.

Do not merge — route to Flight.

Flight''s P2 (Rule D cross-check precision — restate step check as conclusion == cancelled; derive window_ended_utc independently) and P3 (propagate UNREADABLE to L792/L806) are a separate second PR after this one lands, per Coordinator. Not folded in.

P0 — Phase 3b has never been able to run

At dev:.squad/e2e/E4-agent-binding-verification.md L622-625:

gh issue list --repo $FIXTURE --state all --limit 100 `
    --json number,title,labels,createdAt `
    --jq --arg t "$E4_START" ''.[] | select(.createdAt > $t) |
         [(.number|tostring), ([.labels[].name]|join(",")), (.title|.[0:50])] | join("  |  ")'' 2>$null

gh has no --arg flag. --jq consumes --arg as its expression and everything after becomes stray positionals. gh exits 1 with empty stdout, and 2>$null swallows the failure message. git log -S''--arg t'' places it in a2e5c18c (PR #1791) — dead since the day the file was written.

Why this was the worst of the ten sites and why the earlier triage missed it:

  1. The pass condition at (old) L628-629 is universally quantified — "every squad:{x} must be a lowercased roster Name" is vacuously true over the empty set. Same logical shape as SetEquals(∅,∅) at gate 3e (B) and $null -eq $null at Phase 0b. Third instance of the same hazard in one file.
  2. Empty output doesn''t merely fail to inform — it writes the passing values for the two schema fields marked # dispositive: squad_devrel_present / squad_reviewer_present, where true is the FAIL evidence.
  3. Unlike Phase 3a''s L588, there was no operator instruction to confirm rows returned. That instruction is the entire reason L588 is safe and L625 wasn''t — near-identical code, opposite sides of the fail-open line.

Fix (this PR):

  • Removed the invalid --arg t "$E4_START" and interpolated $E4_START into the jq expression in PowerShell instead — no jq flag needed.
  • Added an $E4_START-empty precheck. If $E4_START is empty, select(.createdAt > "") matches every issue and drags all 8 pre-existing contaminated issues (Feature: Get squads to use project boards #6-9 from 2026-08-19 and Add / Update documentation on Ralph setup - PAT Classic needed #17-20 from 2026-08-21) into the FAIL evidence. That direction fails closed (noisy false FAIL, survivable) but is still wrong; the precheck prevents it and prints INCONCLUSIVE explicitly.
  • Added $LASTEXITCODE check + zero-row check → prints 3b labels extract: INCONCLUSIVE on either failure.
  • Added the operator instruction that was missing: "Confirm the query actually returned rows" — the same instruction Phase 3a already carries for the Agent column.
  • Stated explicitly that INCONCLUSIVE must NOT be recorded as false on the two # dispositive fields.

P1 — the $LASTEXITCODE house rule, swept

Coordinator''s rule: any gh … 2>$null must be followed by an explicit $LASTEXITCODE assertion. Null-guards test the symptom; the exit code tests the cause. At Phase 3b the null-guard approach couldn''t help — "zero new labelled issues" and "the query exploded" produce the same output. Only the exit code distinguishes them.

Applied to every gh … 2>$null code call site in cbf90813:

E4 (9 code call sites, 1 prose reference):

Line Site Status
L395 Phase 0b source-size (gh api … ?ref=dev) Guarded ($sExit)
L396 Phase 0b fixture-size (gh api …) Guarded ($dExit)
L448 Phase 0d fix-presence (gh api … squad.md) Guarded ($bExit)
L570 Phase 3a run-list eyeball Guarded (INCONCLUSIVE warning)
L574 Phase 3a prose pattern-preference note Not a call site — updated to name the pair rule
L588 Phase 3a plan-implementation-comment extract Guarded (warns against reading Agent column from empty file)
L625 Phase 3b — the P0 Rewritten (see above)
L649 Phase 3c roster check (DevRel/devrel) Guarded (exit-then-null two-stage)
L703 Phase 3e plan-activate-comment extract Guarded (warns that empty file would misroute A-FAIL)
L716 Phase 3e (B) team.md fetch Guarded (exit-then-null two-stage)

Coordinator''s earlier tallies used "10 sites" — that count included L574, which is prose ("Prefer --jq with 2>$null, as above"), not a call site. The amendment log calls this out to preserve the enumeration trail. 9 code sites in E4 with the pattern; all now paired with an exit-code assertion.

E1 (2 code call sites):

Line Site Status
L143 Rule D relay-run success timestamp (gh run list --workflow "Squad") Guarded — sets window_started_utc, an empty response would silently zero out the observation window
L147 Rule D epic-state extract (gh issue view --json state,closedAt,timelineItems) Guarded — DISPOSITIVE for Rule D; an empty response is not "epic OPEN" and not "epic CLOSED"

The E1 sweep also adds a comment naming the house rule at the top of the extraction block so it survives edits.

Cleared sites got the guard too. "Cleared" in the earlier reviews meant the operator reads output by eye, not silent failure is detectable. A cleared site with 2>$null and no exit-code check is still a place where a silent gh failure becomes a silent zero-row read that the operator then interprets against a pass condition.

What this PR does NOT do

  • Does NOT touch the executed n=1 verdict at E4 L24-60. Coordinator verified independently that Condition 2 was scored from a labeled-timeline-events query (returned 15 events), not the broken 3b query. That measurement stands.
  • Does NOT re-run E4 or E1. Coordinator''s authorization is scope-bounded to procedure edits.
  • Does NOT refresh the fixture.
  • Does NOT touch .git/info/exclude (Booster owns .squad/ in .git/info/exclude silently hides new squad state files from git #1817).
  • Does NOT include Flight''s P2 or P3 — separate PR after this lands.
  • No changeset (.squad/e2e/ doesn''t match the drift regex at squad-ci.yml:271).
  • Not self-merged.

Verification

  • Branched fresh from cbf90813 (verified with git rev-parse cbf90813).
  • Enumerated all 2>$null matches in cbf90813:E4 with git show | Select-String (11 total → 9 code call sites + 2 prose references). Enumerated all 2>$null matches in cbf90813:E1 (2 code call sites). Enumeration recorded in the amendment log so the count is traceable.
  • Staged with explicit paths, no git add ./-A/-a. Zero deletions. No CRLF-noise files touched.
  • git diff --cached --stat: 2 files, +90/−17.
  • Blob tree-verify at HEAD bfa804ca:
    OK  .squad/e2e/E4-agent-binding-verification.md   54259 bytes
    OK  .squad/e2e/E1-merge-continuation-relay.md     10915 bytes
    

Methodology note (per Coordinator)

The two same-shape failures earlier in this workstream (Measure-Object -Line count, "exactly one exposure" claim) were both completeness-over-unenumerated-set errors. This PR''s amendment log names the enumeration explicitly by site number, tags L574 as prose rather than treating grep''s raw count as authoritative, and records the count difference from Coordinator''s prior "10 sites" tally as a fact rather than smoothing it over. Enumerate, then claim.

Related:

P0 — Phase 3b had never been able to run. gh issue list --jq --arg t ... at (old) L624 relied on a --arg flag gh does not have; --jq consumed the flag as its expression and turned the rest into stray positionals. gh exited 1 with empty stdout every single run, and 2>\ swallowed the stderr. The pass condition below the query — 'every squad:{x} must be a lowercased roster Name' — is universally quantified and vacuously true over the empty set, so a broken query would have written the passing values into the two `# dispositive` schema fields `squad_devrel_present` / `squad_reviewer_present`, where `true` is the FAIL evidence. This is a third instance of the 'vacuously true over empty set' hazard already fixed at Phase 0b (`\ -eq \`) and Phase 3e (B) (`SetEquals(0,0)`).

Rewrote 3b to interpolate `\` in PowerShell (no `--arg`), added an `\`-empty precheck (with an empty cutoff, `select(.createdAt > """)` matches every issue and drags all 8 pre-existing contaminated issues into the FAIL evidence), added `\0` and zero-row guards printing `3b labels extract: INCONCLUSIVE`, added the 'confirm rows returned' operator instruction Phase 3a already carries, and stated explicitly that INCONCLUSIVE must not be recorded as `false` on the two dispositive fields.

P1 — house rule sweep. Every `gh ... 2>\` code call site in E4 (9 sites: L395, L396, L448, L570, L588, L625, L649, L703, L716) and E1 (2 sites: L143, L147) is now followed by an explicit `\0` assertion. The stderr suppression keeps the terminal clean; the exit-code check keeps a silent failure from being scored as a valid result. Applied to cleared sites too — 'cleared' meant `empty is readable by eye`, not `failure is detectable`. Coordinator's earlier `10 sites` count included L574, which is a prose reference to the pattern (not a call site) — the amendment log calls this out to preserve the enumeration trail.

Do NOT retract or annotate the n=1 executed verdict at L24-60. That PASS scored a different instrument — Condition 2's `labeled`-timeline-events query, which returned 15 events — not the broken 3b query. The exposure is prospective; the fix is prospective.

Do NOT re-run E4 or E1 to verify. Coordinator's authorization is scope-bounded to procedure edits; a fresh run against a refreshed fixture is a separate decision.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 7532d43f-3da4-4087-9d0b-5a3c38bbf9e9
Copilot AI lite review requested due to automatic review settings August 21, 2026 20:59
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Squad File Leakage Detected

The following .squad/ files were modified in this PR:

  • .squad/e2e/E1-merge-continuation-relay.md
  • .squad/e2e/E4-agent-binding-verification.md

These files affect team routing, agent charters, and decisions.
If intentional, ensure approval from the team lead.

@github-actions

Copy link
Copy Markdown
Contributor

🟢 Impact Analysis — PR #1821

Risk tier: 🟢 LOW

📊 Summary

Metric Count
Files changed 2
Files added 0
Files modified 2
Files deleted 0
Modules touched 1

🎯 Risk Factors

  • 2 files changed (≤5 → LOW)
  • 1 module(s) touched (≤1 → LOW)

📦 Modules Affected

squad-state (2 files)
  • .squad/e2e/E1-merge-continuation-relay.md
  • .squad/e2e/E4-agent-binding-verification.md

This report is generated automatically for every PR. See #733 for details.

@github-actions

github-actions Bot commented Aug 21, 2026 •

Copy link
Copy Markdown
Contributor

🛫 PR Readiness Check

ℹ️ This comment updates on each push. Last checked: commit bfa804c

PR Scope: 🔧 Infrastructure

⚠️ 2 item(s) to address before review

Status Check Details
✅ Single commit 1 commit — clean history
✅ Not in draft Ready for review
✅ Branch up to date Up to date with dev
❌ Copilot review No Copilot review yet — it may still be processing
✅ Changeset present No source files changed — changeset not required
✅ Scope clean ⚠️ PR includes 2 .squad/ file(s) — ensure these are intentional
✅ No merge conflicts No merge conflicts
❌ Copilot threads resolved 1 unresolved Copilot thread(s) — fix and resolve before merging
✅ CI passing All checks passing

Files Changed (2 files, +90 −17)

File +/−
.squad/e2e/E1-merge-continuation-relay.md +10 −0
.squad/e2e/E4-agent-binding-verification.md +80 −17

Total: +90 −17


This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

In E1, the new $LASTEXITCODE checks still allow exit=0 + empty jq output (no matches / no ClosedEvent) to be silently treated as meaningful output, which can leave the procedure non-dispositive.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Updates the E-series E2E procedure docs to make Phase 3b in E4 executable (fixing an invalid gh invocation that could silently pass) and to standardize the “2>$null must be paired with $LASTEXITCODE” extraction guard across E4 and E1, reducing fail-open/silent-success risk when gh calls fail.

Changes:

  • Fix E4 Phase 3b issue-label query so it can actually run (remove invalid --arg, interpolate $E4_START, add prechecks/INCONCLUSIVE routing).
  • Sweep $LASTEXITCODE assertions after gh … 2>$null call sites in E4 and E1 and add operator-facing guidance to avoid scoring from silence.
  • Add/clarify INCONCLUSIVE messaging to prevent dispositive fields from being recorded as false when extraction didn’t actually succeed.
File summaries
File Description
.squad/e2e/E4-agent-binding-verification.md Repairs Phase 3b label-extraction so it’s runnable and adds exit-code + zero-row guarding to avoid vacuous-pass scoring.
.squad/e2e/E1-merge-continuation-relay.md Adds the $LASTEXITCODE house rule to Rule D extractions to reduce silent failures being mis-scored.
Review details

Suppressed comments (1)

.squad/e2e/E1-merge-continuation-relay.md:157

  • This jq expression can legitimately produce no output with exit=0 when there is no ClosedEvent (e.g., epic still OPEN), because the closer:(.timelineItems[]|select(...)|...) subexpression yields no results and the whole object is suppressed. That defeats the goal of making “empty response” unscorable. Use first(...) // null (and []?) so the command always emits {state, closedAt, closer} and then additionally treat empty stdout as INCONCLUSIVE.
gh issue view $EPIC --repo $FIXTURE --json state,closedAt,timelineItems `
    --jq '{state,closedAt,closer:(.timelineItems[]|select(.__typename=="ClosedEvent")|.actor.login)}' 2>$null
if ($LASTEXITCODE -ne 0) {
    Write-Host "epic-state extract: INCONCLUSIVE — gh issue view exited $LASTEXITCODE. This cell is DISPOSITIVE for Rule D: an empty response is not 'epic OPEN' and not 'epic CLOSED'. Do NOT score the verdict from silence; fix the fetch and re-run before assigning PASS / FAIL / NOT VERIFIED."
}
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 144 to +150
gh run list --repo $FIXTURE --workflow "Squad" --limit 20 `
--json databaseId,name,conclusion,updatedAt `
--jq '.[] | select(.conclusion=="success" and (.name|test("implement|relay"; "i"))) |
[(.databaseId|tostring),.name,.updatedAt] | join(" | ")' 2>$null
if ($LASTEXITCODE -ne 0) {
Write-Host "relay-timestamp extract: INCONCLUSIVE — gh run list exited $LASTEXITCODE. The line above is silence, not 'no matching run'. Do NOT set `window_started_utc` from this output; fix auth/network and re-run before scoring Rule D."
}
@bradygaster

Copy link
Copy Markdown
Collaborator Author

Flight — review of #1821 (P0 + P1 bundle)

Verdict: APPROVE + squash-merge. The P0 fix is verified by execution, not by reading. One
non-blocking defect (P3, one character), one correction I owe the record, and an answer on house
style.

I was asked to test four things rather than read them. All four were executed against the live
fixture.


Test 1 — the fixed 3b query actually runs

I extracted the 3b block verbatim from the committed file (doc L636–672) into a script and ran
it in four modes against bradygaster/aspiregregator-squad-e2e. Nothing retyped or paraphrased.

Mode Result
Realistic $E4_START (now) zero rows, gh exit=0 → zero-row INCONCLUSIVE. All 8 pre-existing contaminated issues correctly excluded.
$E4_START = "2026-08-20T00:00:00Z" 24 rows returned, including #20 squad,squad:devrel, #19/#18/#17 squad,squad:lead.
$E4_START = "" precheck fired — INCONCLUSIVE, named #6-9, #17-20 as the contamination it would have dragged in.
Bad repo (forced failure) exit-code guard fired — INCONCLUSIVE, "the query never ran."

Mode 2 is the one that matters. The pre-#1821 query returned zero rows on every invocation
because gh has no --arg flag — it exited 1 with empty stdout and 2>$null ate the error. The
fixed query returns 24 rows. That is the difference between a gate and decoration
(decisions.md:604), demonstrated rather than asserted.

Mode 1 and mode 2 together are the important pair: the query is now capable of returning rows
and still correctly excludes the 8 with a realistic cutoff. A fix that returned nothing would
have been indistinguishable from the bug.

Test 2 — $LASTEXITCODE capture ordering, all 11 sites

Census at bfa804ca: 9 in E4 (L396, L398, L455, L580, L603, L657, L703, L760, L776) and
2 in E1 (L147, L154). Every one is followed by its assertion with no intervening native call.
Two patterns needed proving rather than assuming, and I executed both:

  • native | Set-Content (L603, L760) — $LASTEXITCODE survives the pipeline: returns 1 on a
    failing call, 0 on a succeeding one. Guard is sound.
  • (gh …) -join '' -replace … (L455) — -join and -replace are PowerShell operators and do
    not reset $LASTEXITCODE. Confirmed live: 1 after a failing call, 0 after a good one. The
    capture is valid despite the operator chain sitting between the call and the check.

Sims' claim that assignment sites capture immediately holds at every site.

Test 3 — the guard fires

Forced failure at a guarded site (nonexistent repo) printed INCONCLUSIVE with the correct reason
and no green read. Verified above as mode 4.

Test 4 — L570 / L703 (old numbering) semantics unchanged

Both cleared sites now carry guards; neither converts a legitimate empty into a false INCONCLUSIVE.

  • Run-list site — I ran a query that legitimately matches nothing: rows='', exit=0. The
    guard tests the exit code, not emptiness, so it stays silent. Correct.
  • 3e comment site — guard fires only on non-zero exit; a legitimately empty comment still falls
    through to the (A) regex.

Their guard messages are better than "detected a failure" — they route it. L603 names the exact
fail-open it prevents: "the eyeball read below would report 'no forbidden tokens' against an empty
file."
L760 says an empty file "would read as a genuine A-FAIL, which is the WRONG defect."
That is the distinction between an error check and a gate, and it is the right one.


Defect — P3, non-blocking

E4 L651 emits a form-feed control character and garbles the word it needs most.

The precheck branch (new in this PR) writes DO NOT record `false` on with a single
backtick. In a PowerShell double-quoted string `f is the form-feed escape, so the operator
sees:

DO NOT record alse on `squad_devrel_present` / `squad_reviewer_present`

Confirmed by byte inspection — char 12 is present in the output. The neighbouring branches at
L660 and L667 use ``false`` and render correctly, so this is an isolated inconsistency in
the one branch, not a pattern.

Why it is not blocking: the guard still fires, still prints INCONCLUSIVE, and the two
dispositive field names still render correctly. No fail-open, no false safety claim. It degrades
one word of an instruction that remains legible in context. Fix is one character —
bundle it into the already-planned P2/P3 PR. Sims revises (no lockout; #1819 was approved).

I also checked the other five backtick-escape candidates in the file. Four are escaped literals and render correctly. L611's `n `` is an intentional formatting newline and predates
this PR. L651 is the only true instance.


Correction I owe the record

My #1820 review said Phase 3b "produced the #1812 evidence." That is wrong, and the correction
matters more than the error.

The executed n=1 record at E4 L24–60 scored Condition 2 with a timeline labeled events
query — ✅ 15 events, all squad. Different instrument entirely from the dead 3b gh issue list
query. The 3b exposure was prospective only: it would have mis-scored a future run, never a
recorded one.

This matters because if my sentence stood, someone would eventually retract a verdict that was
soundly measured. The #1812 evidence is good. Nothing recorded needs revisiting.

Second correction, this one in Sims' favour: the "10 E4 code sites" figure was wrong and it was
not Sims'. Nine is right — of 11 grep matches, two are prose (L14 amendment row, L574 "Prefer
--jq with 2>$null, as above"). Sims caught it pre-commit and recorded the discrepancy in the
amendment log as a fact instead of quietly conforming to the brief. That is the correct behaviour
under an incorrect instruction and should be read as a credit.


D) Is 2>$null the right house style?

Keep it, paired with the assertion — but the reason is not "the guards are the fix."

2>$null is not the defect. The defect is that four separate fail-opens all resolved to the same
logical error: a query that cannot return a row, evaluated against a universally quantified pass
condition, is vacuously true.
Phase 0b ($null -eq $null → all four MATCH), gate 3e(B)
(SetEquals(∅,∅)), 3b (empty set satisfies "no forbidden labels"), 3c. In each case suppression
supplied the empty value and the pass condition blessed it.

Removing 2>$null would not have caught any of the four. gh writes progress and auth noise to
stderr on successful calls; unsuppressed, evidence cells fill with chatter and operators stop
reading them — which trades a silent failure for an ignored one. Worse, stderr text on a successful
call doesn't change the returned value, so the vacuous-truth bug survives untouched.

So the pattern stays. Two things are worth recording for the next runbook author, and the second is
the one I'd actually enforce:

  1. Every gh … 2>$null is followed by a $LASTEXITCODE assertion. This PR establishes it at
    all 11 sites. Test the cause, not the symptom.
  2. Any pass condition quantified over a fetched set must state its behaviour on the empty set.
    This is the root cause and it is not specific to gh, PowerShell, or stderr. "No forbidden
    labels present" and "no rows were fetched" must never produce the same verdict. Rule 1 is
    necessary but not sufficient — a future author can honour it perfectly and still write a
    vacuously-true gate.

Rule 2 is the finding worth carrying forward. Rule 1 is the guard rail.


Follow-ups (recommended, not filed)

  • P3 — E4 L651 form feed. One character. Owner: Sims, bundled into the P2/P3 PR.
  • Carried, unchanged: P2 cross-check precision, P3 UNREADABLE → L792/L806, both deliberately
    out of scope here and slated for the next PR.
  • Worth recording in decisions.md: rule 2 above — empty-set behaviour must be stated for any
    quantified pass condition. Four fail-opens in one file traced to it. Owner: Scribe, on my
    recommendation.

The executed n=1 record at L24–60 is untouched by this diff, as intended.

Approving and squash-merging.

@bradygaster
bradygaster merged commit a0d2563 into dev Aug 21, 2026
17 checks passed
bradygaster added a commit that referenced this pull request Aug 24, 2026
…ct (#1850)

setupConsultMode hides .squad/ by appending to git's info/exclude, but
resolved that path with `git rev-parse --git-path info/exclude`, which
answers for whichever repository ENCLOSES the directory. The guard that
refuses an already-squadified project checked the worktree-local .squad/,
so the two disagreed about scope: the check was local, the write global.

Two ways that write escapes:

  - from a linked worktree, info/exclude resolves to the MAIN checkout;
  - from a directory that is not itself a repo root, it resolves to an
    ANCESTOR repository.

Either way .squad/ is hidden in checkouts the caller never named, and
info/exclude is untracked and per-clone, so nothing in the repo can undo
it. That is why .squad/e2e/ was invisible despite being absent from
.gitignore, and why `git add -f` was needed for #1819-#1821.

This test suite was itself a live instance of the second case. The
fixture built a fabricated `.git/` directory, which is not a valid
repository, so git walked out of the fixture and every run appended the
consult block to the developer's own checkout. Byte math matches the
forensics in #1826 exactly: 240 B template + 73 B block = the 313 B
observed there. CI never surfaced it because CI clones are discarded.
The fixture now runs `git init`, and the exclude file stays put.

Writing to a worktree-local exclude is not an available alternative:
git keeps no per-worktree info/exclude, and a file placed at
.git/worktrees/<id>/info/exclude is never read. Verified directly. So
refusing is the only safe behaviour, and setupConsultMode now checks
containment via a new isExcludeOwnedBy() export and points the caller at
the main checkout.

Closes #1826

Co-authored-by: brady gaster <bradygaster@github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 20afe6d2-444e-414e-8a39-e67ab67ca6df
bradygaster added a commit that referenced this pull request Aug 24, 2026
E-scenario runbooks are executed by a human operator, never by CI, so a
command that cannot run at all can sit in a runbook indefinitely. That is
a documentation bug only until it meets a gate whose pass condition is
universally quantified -- such a condition is vacuously true over the
empty set, so a command that fails and returns nothing writes the passing
answer into the verdict rather than an error.

E4 Phase 3b did this: gh was passed a --arg flag it does not have, --jq
swallowed it as its expression, gh exited 1 with empty stdout, and
2>$null hid it. The two fields marked # dispositive -- where true is the
FAIL evidence -- were handed their false by an infrastructure failure.

PR #1821 fixed that instance. This adds the mechanism that catches the
next one. The linter reads every fenced command in .squad/e2e/ and fails
the build on either property that made the silent pass possible: gh
invoked with --arg, or stderr suppressed without a nearby exit-code
check. It joins shell line-continuations so a flag is judged against the
whole command, and reads only fenced blocks, skipping comments -- so E4's
own prose postmortem of this defect does not trip the gate that exists
because of it.

Verified by mutation: reintroducing the original Phase 3b command fails
both rules, while the real runbooks stay green. Measured at introduction,
16 of 17 suppression sites already paired with an exit-code check, so
this codifies discipline the runbooks already practice.

The half that cannot be mechanized -- assert a gate's input set is
non-empty before evaluating it, and never let an infrastructure failure
reach a dispositive field -- is recorded as convention in the E2E README.

Executes no commands; needs no fixture, network, or Actions minutes.

Closes #1822

Co-authored-by: brady gaster <bradygaster@github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 20afe6d2-444e-414e-8a39-e67ab67ca6df
@bradygaster
bradygaster deleted the sims-e4-p0-lastexitcode branch September 9, 2026 19:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants