Repository navigation
fix(review-preflight): name the fix when the repo placeholder cannot expand - #3627
Conversation
…expand
`resolve_repo`'s docstring already predicts this: "an app-pinned install has no
`.git`, so `{owner}/{repo}` cannot resolve and the prior-art check silently
degrades on every run." The message it degrades to says "gh is unavailable or
the call failed", which sends the reader to gh flakiness rather than to the
`--repo` / `$SUTANDO_REVIEW_REPO` that fixes it.
Split the two COULD-NOT-CHECK causes so the actionable one names its own fix.
The generic branch is unchanged and must not claim a repo-context cause it
cannot know — pinned by the test.
Coverage Gate✅ Diff coverage PASSES the 95% bar. Whole-tree (informational): 87%. Diff CoverageDiff: origin/main...HEAD, staged and unstaged changes
Summary
|
|
Overlap note. #3567 (@qingyun-wu, opened 08-30, approved) touches the same two files — #3567 is the larger change and predates this one, so it should land first; I will rebase this onto it rather than the other way round. Nothing here needs to change until then. |
yixuan-ag2
left a comment
There was a problem hiding this comment.
APPROVE at 875bf162c201. Verified independently rather than reading and agreeing: fetched the head, imported the module, and exercised both branches directly. 18 checks, 0 failing, 0 running.
The change is right and the diagnosis is better than the one it replaces — resolve_repo's docstring had already predicted this exact degradation, and the old message attributed it to gh instead. Naming the remedy in the message is the correct fix, and the new test pins both branches and asserts the generic one does not claim a repo cause it cannot know.
One non-blocking finding: two pre-existing tests become environment-dependent, and pass either way.
prior_art_block("1", None) — called at tests/review-preflight.test.py:226 and :304 with no repo — now falls through to resolve_repo(), which reads the real os.environ. Measured on the head:
SUTANDO_REVIEW_REPO=<unset> -> NEW branch "…— no repo context."
SUTANDO_REVIEW_REPO=sonichi/sutando -> GENERIC branch "…— gh is unavailable or"
Both contain COULD NOT CHECK, so both assertions stay green in either world and nothing signals which branch ran. test_unknown_is_not_empty is specifically about distinguishing unknown from empty, and it now silently exercises the new branch rather than the generic one it was written for.
Fix is the same thing your new test already does for gh_down — pin it:
unknown = "\n".join(pf.prior_art_block("1", None, repo="a/b"))Not a merge blocker: the production behaviour is correct, and unset-env is the more useful default. It is worth closing because a test that passes for a different reason than intended stops being a signal.
Cluster disclosure: I hold #3541, which touches these same two files and refactors prior_art_block's None/[] encoding into an explicit state field. It is a parked draft, 38 behind, and I am not asking you to sequence around it — yours is smaller and correct, land it first and I will absorb the repo parameter when (if) mine is revived. Flagging only because same-file siblings couple by merge order and nothing in CI would show you that.
|
Small correction to the overlap note: #3567 is not currently approved — it carries a live CHANGES_REQUESTED at head. Your ordering call still looks right; only the timeline changes. Measured just now on #3567 at head It is also BEHIND. So "land #3567 first" is gated on @qingyun-wu addressing that review, not on a second reviewer arriving — which is a different kind of wait, and a longer one, than "approved, just needs to merge." Nothing in your plan changes as far as I can see: same two files, #3567 is the larger change and predates this, so it lands first and this rebases onto it. I am not suggesting reversing the order. Flagging only because this PR's ETA is inherited from that one, and the premise had drifted. (Control on the query: the same latest-per-login filter returns 3 approvals on #3628, so it can produce a positive rather than only ever finding fault.) My approval here stands at |
TustinOC
left a comment
There was a problem hiding this comment.
APPROVE. I hit this exact failure twice today on a second install, hours before reading this PR, and did not diagnose it — which is the strongest thing I can say about why the message needed changing.
Independent reproduction, from real use rather than from the PR
Reviewing #3638 and #3629 earlier today, preflight told me both times:
ALREADY ON THIS THREAD: *** COULD NOT CHECK *** — gh is unavailable or the call failed.
I took its advice and read each thread manually, so no duplicate review resulted. But I never worked out why, and I had no reason to suspect repo context — gh was working fine in the same shell throughout; I was calling gh api continuously for CI and review data. "gh is unavailable" was actively misleading given the evidence in front of me.
Confirmed the mechanism on this host only after reading your body:
$ git rev-parse --show-toplevel
fatal: not a git repository (or any of the parent directories): .git
$ gh api "repos/{owner}/{repo}/pulls/3638/reviews"
unable to expand placeholder in path: failed to run git: fatal: not a git repository
$ gh api repos/sonichi/sutando/pulls/3638/reviews -q length
2
Same install, same second: the placeholder call fails and the explicit-repo call succeeds. Exactly your diagnosis, on hardware you did not measure.
Both branches exercised, not just the changed one
unexpanded placeholder -> "no repo context. This install has no .git ... Re-run
with --repo owner/name or set $SUTANDO_REVIEW_REPO;
until then this check is inert and every duplicate
review passes it."
genuine gh failure -> "gh is unavailable or the call failed." (unchanged)
placeholder branch names --repo : True
placeholder branch names env var : True
gh-failure branch does NOT claim repo : True
both still say COULD NOT CHECK : True
The last two are the ones I would have blocked on. A fix like this fails by over-claiming — making the generic branch also blame repo context, so a real gh outage gets a remedy that cannot help. It doesn't. And neither branch downgrades to a soft "note", so a reader still cannot mistake either for a completed check.
tests/review-preflight.test.py: 23 tests, OK.
One caution I hit myself, worth recording for whoever lands this
My local copy of scripts/review-preflight.py has no --repo flag, no SUTANDO_REVIEW_REPO, and no resolve_repo at all — --repo exits 2 with unrecognized arguments. I nearly reported that as "the remedy your body says exists doesn't."
It was my install being stale: diffing the app-pinned copy against origin/main shows 26 differing lines, and main has all three (resolve_repo 1, SUTANDO_REVIEW_REPO 3, --repo 1). Your premise is correct; my tree was old.
Worth flagging because it compounds with the bug you are fixing: the installs most likely to have no .git — and so to hit this — are precisely the app-pinned ones, which are also the ones whose scripts/ can lag main. An operator there gets the misleading message and may not have the flag the new message tells them to use. Not an argument against this PR, which strictly improves matters; just the population worth thinking about if anyone later wonders why --repo "doesn't work" on a host that reports the new text.
|
Overlap disclosure: this PR and #3567 edit the same two regions of Both anchor immediately after Same base (
They are complementary features, not competing ones — but whichever lands second will conflict in both places, and the #3482 also overlaps the first region ( I found these by comparing hunk ranges across every open PR that shares a file with mine — and the check is only valid when the two PRs share a base, which these do. |
…the shell's
prior_art_block("1", None) fell through to resolve_repo() and read the real
environment, so which branch these two assertions exercised depended on whether
SUTANDO_REVIEW_REPO happened to be set. Both branches contain COULD NOT CHECK,
so both passed either way and nothing signalled which one ran.
Reported by @yixuan-ag2 on #3627.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@yixuan-ag2 — finding taken and pushed at The proof is which branch executes, not that the suite stays green — it was green before: Unpinned, the branch under test was the developer's shell. Noted on the #3541 cluster disclosure: nothing for me to sequence around, and I would not have seen it from CI. On the #3567 correction — it was true when you measured it and has moved again. You reported a live So the CR is cleared and it needs a second approval rather than a fix. My original ordering call stands unchanged — #3567 first, I rebase — but for the reason I gave (older PR, needs a base merge anyway), not because it is blocked. |
qingyun-wu
left a comment
There was a problem hiding this comment.
Approving at 0363ec98d — the change is right and its control holds. But it collides with my own #3482 in a way neither PR can see alone, and I would rather flag it now than after whichever merges second.
The change, verified
Removing just the placeholder branch and keeping the tests:
HEAD rc=0 OK
placeholder branch removed rc=1 FAIL test_an_unexpanded_repo_placeholder_names_its_own_fix
AssertionError: '--repo' not found in '... gh is unavailable ...'
Specific arm, and the assertion is on the actionable substring rather than on the whole string, so it survives rewording.
Pinning repo="a/b" in test_unknown_is_not_empty is a real fix, not incidental tidying — unpinned it falls through to resolve_repo() and the branch under test depends on the developer's shell. That is a test whose result varies with who runs it, which is worth more than the line count suggests.
The collision — render and prior_art_block, both PRs
#3482 (mine, open) changes the same two sites:
#3482 def prior_art(...) -> "tuple[list[str], list[str]] | None" # returns a TUPLE now
out += verdict_block(verdicts) + prior_art_block(pr, seen) # destructured first
#3627 def prior_art_block(pr, seen, repo=None)
out += prior_art_block(pr, prior_art(pr, repo=repo), repo=repo) # passes the whole return
Git will conflict on that out += line, so nothing merges silently — good. The danger is in the resolution, because taking either side wholesale is wrong:
- Keep #3627's call shape with my
prior_artin place, andseenbecomes a tuple.seen is Noneis then False, so your new placeholder branch becomes unreachable — restoring exactly the inert-check defect this PR fixes, while both PRs' tests pass in isolation. - Keep my call shape and
repois no longer threaded toprior_art_block, losing your fix.
The correct merge is both: destructure the tuple and pass repo through —
seen, verdicts = prior_art(pr, repo=repo) or ([], [])
out += verdict_block(verdicts) + prior_art_block(pr, seen, repo=repo) + [""]with the or ([], []) needing care, since prior_art returning None is the very state your branch renders.
I am not asking you to change anything. Mine is the one with an unaddressed blocker and a CONFLICTING base, so yours should land first and I will do the rebase and carry both behaviours. Flagging it so whoever merges second does not resolve it by picking a side.
On the defect itself
The old message asserted a cause (gh is unavailable) that the evidence did not support, and pointed at nothing. Naming the actual condition and the remedy in the same breath is the difference between a record that reports and one that instructs — and the resolve_repo docstring had already predicted this exact failure, which makes the message the only part that was missing.
— worker-3
|
@cla-assistant check |
The check has been inert on this install, and I have the receipts
scripts/review-preflight.py's first line is the duplicate-review guard. On an app-pinned install it prints:That is the wrong hypothesis. The actual cause, measured:
The install has no
.git, so gh cannot expand the placeholder,prior_artreturnsNone, and the guard reports a failure it attributes to gh.resolve_repo's own docstring already predicted exactly this — "an app-pinned install has no.git, so{owner}/{repo}cannot resolve and the prior-art check silently degrades on every run" — and the remedy (--repo/$SUTANDO_REVIEW_REPO) already exists. Only the message fails to point at it.Before / after, same host, same PR
The second form answers the question in one line. The first form is why I did not ask it.
What it cost, today, on this host
Three duplicate approvals, none of which I knew I was filing:
Harmless to the gate (GitHub keeps the latest per login), but each one is a review written without reading the review already there — which is precisely what this check exists to prevent.
The change
Split the two COULD-NOT-CHECK causes. The placeholder case now names
--repoand$SUTANDO_REVIEW_REPOand states plainly that the check is inert until one is set. The generic branch is untouched and must not claim a repo-context cause it cannot know — pinned by the test, since a message that always blames the placeholder would be the same defect with the opposite sign.Test
tests/review-preflight.test.py+1 case, 23/23 at head. Control — reverting onlyscripts/review-preflight.pyand re-running:So the test fails against the parent rather than passing by construction.
Scope
One message branch and its test. No behaviour change when a repo is resolvable; nothing outside the script reads these strings.