fix(doctor): a failed workflows read is UNDETERMINED, never 'no CI workflows' (#4347) - #4363
Conversation
…rkflows' (#4347) Checks `2b` and `3b` captured `gh api … --jq '.total_count' 2>/dev/null || echo 0`. `gh` does not apply `--jq` to an error response — it writes the raw error body to stdout — so `|| echo 0` APPENDED to that body instead of replacing it. The numeric test then printed `[: integer expression expected` and fell into the negative arm: "no CI workflows defined", with a fix ("add a workflow") that addresses a problem never established. The real cause — repo resolution or auth — stayed hidden. Both sites now use the three-outcome read discipline the file already holds at the Tier-2 governance checks: keep gh's exit status separately (`RC=$?`), admit only a literal integer on the pass/absent arms, and route everything else to a distinct READ FAILED … UNKNOWN arm whose `fix:` addresses the read. Verdicts are unchanged: `2b` still counts toward `fails`, `3b` still never does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
No preview deploy
|
|
review-skill: advisory — blocking-set PR (manual merge) PR #4363 touches the control plane ( Reviewed-head: @ 5281728 Verified against #4347's acceptance criteria + the skill-rigor checklist. Gate contract read from Empirical runs — the three arms, at this head
Both true-negative arms remain reachable — the fix narrows the pass/absent arms, it does not swallow them. Acceptance criteria
Scope boundaries — checked for honest statement, not silent widening
Skill rigor
Note, non-blocking (not a defect in this PR): the PR body says "PR #4337 (for #4300) is still open" — it has since merged and is now the tip of All checks pass. review-skill does not merge, and this §CP verdict authorizes nothing: a Verdict-written: 2026-07-27T02:40:01Z |
Banked for control-plane approval@cansirin — this PR is §CP by path match ( Review verdict: PASS — advisory form per ADR 0111/0151, bound to head What the reviewer actually didNot a diff read — all three arms were verified empirically by running the guard:
The exit-code contract was proven live: on the unreadable arm, exactly four blocking The load-bearing safety propertyThe new guard is strictly narrower than what it replaces — the pass arm now additionally requires a zero exit status and a literal-integer shape. So no input that previously failed can now pass: the change can only move a result toward "undetermined", never toward a false green. Known staleness (non-blocking)The PR body says PR #4337 is still open. It has since merged and is now at the tip of If the head moves for any reason, an approval given at the old head is dismissed (ADR 0058) and a fresh approval at the new head is required. |
Fixes #4347
What changed
doctor.shchecks2b(CI workflows) and3b(run-evidence producer) captured agh api … --jqread as… 2>/dev/null || echo 0.ghdoes not apply--jqto an error response — it writes the raw error body to stdout and exits non-zero — so|| echo 0appended0to that body rather than replacing it. The subsequent numeric test then printed[: integer expression expectedto stderr, evaluated false, and dropped into the negative arm: "no CI workflows defined", with afix:telling the operator to add a workflow. A read that never ran was being reported as a definite negative answer, and the real cause (repo resolution / auth) stayed hidden behind it.Both sites now use the three-outcome read discipline the file already holds at the Tier-2
allow_auto_merge/ merge-queue checks (and, perclaude-plugins/pipeline-crew/PROBES.md, "a probe that cannot itself execute must resolve to 'unknown', never 'down'"):gh's exit status is captured separately from the value (WF_RC=$?/RE_RC=$?) —|| echo 0is gone from both.grep -Eq '^[0-9]+$'); anything else routes to the undetermined arm.READ FAILED: … — UNKNOWN, which is neither a pass nor 'no …'line, quoting what the read actually returned via the existingbrief()helper (from ship-it's enqueue needs repo auto-merge + a queue ruleset no fresh repo has #4303 / PR feat(doctor): preflight the two merge-queue governance preconditions ship-it needs (#4303) #4346, now merged).fix:on the undetermined arm addresses the read — resolve the repo, checkgh auth status, re-run — and explicitly says the line establishes nothing about whether CI exists.Verdicts are unchanged.
2bstill counts towardfails;3bstill never does (all three of its arms are WARN-only). This changes reasons and fixes, not the exit-code contract.doctor.shstays write-free — every remediation is still a command it prints.Verification — all three arms, run live
Unreadable repo (
CLAUDE_PIPELINE_REPO=kamp-us/definitely-not-a-real-repo-4347), the issue's reproduction. No[: … integer expression expectedline anywhere in the output; neither site prints its negative answer:Exit 1, 4 blocking failures — same count and same direction as before the change.
Present (
kamp-us/phoenix):✓ CI workflows defined (44)/✓ run-evidence producer present, verdict pipeline-ready, exit 0.Genuinely absent, readable:
octocat/git-consortium(a real repo whosetotal_countis0) still prints✗ no CI workflows definedwith the add-a-workflow fix;octocat/Hello-Worldstill prints⚠ no run-evidence producer. The true-negative arms are intact and reachable.pnpm lint:worktreeis a clean skip (shell-only diff);pnpm typecheckgreen (29/29).Check
3a— deliberately left out of scopePer the acceptance criteria's explicit call:
3a(npm view "$pkg" version >/dev/null 2>&1) is NOT brought in scope. It is the same defect class by a different mechanism — there is no|| echo 0collapse and no value to shape-validate; distinguishing "the registry was unreachable" from "the package does not exist" requires parsing npm's error output, which is a different fix with a different failure mode. It is also WARN-only and touches nothing. Triage measured this issue at exactly two sites, and #4246 / #4247 are the filed guards that mechanize the class repo-wide —3ais theirs, not this diff's, and folding it in would silently widen a §CP diff beyond the issue.Control plane
pipeline-cli cp-classify classify→control-plane [path-match]— BLOCKING (human merge). A.shunderclaude-plugins/kampus-pipeline/skills/matches the liveCONTROL_PLANE_RE. This PR stops at open; a human control-plane member approves and merges it.Sequencing
Branched off
mainafter PR #4346 merged, sobrief()is reused rather than reinvented and theREAD FAILED … UNKNOWNphrasing matches the convention that PR established. PR #4337 (for #4300) is still open and rewrites1c/ appends3c/3d— no textual overlap with2bor3b.🤖 Generated with Claude Code