Skip to content

fix(pstack): watch-pr --status-only reports a head with no checks instead of retrying - #769

Open
deanrie wants to merge 1 commit into
cursor:mainfrom
deanrie:pstack/watch-pr-no-checks
Open

deanrie wants to merge 1 commit into
cursor:mainfrom
deanrie:pstack/watch-pr-no-checks

Conversation

@deanrie

@deanrie deanrie commented Oct 22, 2026 •

Copy link
Copy Markdown

Fixes #750.

Problem

On a PR in a repository with no CI or check apps, watch-pr --status-only never prints a status. resolveChecks throws a retryable ChecksUnavailable when both gh pr checks and the GraphQL rollup come back empty, readSnapshot has no state for that case, and runSimple sends --status-only through pollUntilTerminal with the full --max-query-errors budget. So the caller sees RETRY: GitHub status query failed; retrying in 60s at 60, 120, 240 and 300 s (the default --max-query-errors is 5), then a BLOCKER: status-query failures=5 line and exit 7; never a status. Babysit's check mode calls --status-only and expects one status line, so the check stalls for minutes.

Change

  • readSnapshot gains noChecks: "report" | "retry". With "report" an empty check set becomes a new ci-none CI state (source: "none", no checks, merge assessment still read so the GitHub-side verdict is kept). The default keeps today's retry: a freshly pushed head has no checks yet either, and polling must not call it ready.
  • runSimple passes "report" only for --status-only, and clamps maxQueryErrors to 1 for that mode, so a status read ends on the first query error instead of sleeping through the backoff schedule. This is a behaviour change beyond the no-checks case: a transient gh failure during --status-only now ends the run at once (exit 7 with the failure named) rather than retrying for minutes. For a one-shot table that is the intended trade; callers that want retries keep using polling mode. Polling mode is unchanged.
  • --pretty renders ci-none as no checks reported; the --status-only help text says it reads GitHub once.

This follows the shape of the two downstream fixes linked in the issue (MaxFabian25/pstack-harness-adaptations #2 and #5), folded into one change against this tree. ci-none is not reachable from classifyPr / stack decisions, since only --status-only produces it; the exhaustive switch in render.ts covers it.

Tests

  • policy.test.ts: readSnapshot with noChecks: "report" yields ci-none with a merge assessment; the default still raises a retryable checks-unavailable error.
  • cli.test.ts: --status-only on a no-checks head prints one STATUS verdict with ci-none (JSON and --pretty); --status-only ends on the first query error without sleeping; plain polling on the same head still emits RETRY and sleeps.

bun test: 74 pass / 0 fail. tsc --strict clean. generate-pstack-harness-trees.mjs --check up to date. No overlap with #746 (git merge-tree clean).

Overlap: #592 (configurable review bots) also edits policy.ts; git merge-tree reports a conflict in that file only, the other four files merge cleanly. Whichever lands second needs a small rebase.

…tead of retrying for minutes

On a PR in a repository with no CI or check apps, resolveChecks throws a
retryable ChecksUnavailable when both gh pr checks and the GraphQL rollup
are empty, and --status-only went through pollUntilTerminal with the full
--max-query-errors budget. The result was RETRY lines at 60/120/240 s and
no status at all, which stalls babysit's check mode.

- readSnapshot gains noChecks: "report" | "retry". With "report" an empty
  check set becomes a new ci-none state (source "none", no checks, merge
  assessment still read); the default keeps today's retry, because a
  freshly pushed head has no checks yet either and polling must not call
  it ready.
- runSimple passes "report" for --status-only and clamps maxQueryErrors
  to 1 for that mode, so a status read ends on the first query error
  instead of sleeping through the backoff schedule.
- --pretty renders ci-none as "no checks reported"; --status-only help
  says it reads GitHub once.

Tests: readSnapshot report/retry, CLI --status-only on a no-checks head
(JSON and --pretty), first-error exit, and polling still emitting RETRY.
bun test 74/74, tsc clean, harness tree regenerated.

Fixes cursor#750.
@AlexNegrescu26

Copy link
Copy Markdown

Thanks for this. I reproduced the base behavior and checked it against this PR. With a head that has no checks, base exits 7 after 60, 120, 240 and 300 s retries and then a BLOCKER: status-query failures=5 line. With this PR, --status-only exits 0 with one STATUS line and ci.kind:"ci-none". I saw the same against a real gh on a PR with no checks.

Two notes.

The retry sequence in the description. The description says retries happen at 60, 120 and 240 s and then stop. On base I saw a fourth retry at 300 s, then the blocker, because the default --max-query-errors is 5.

The error budget under --status-only. This PR also sets the query-error budget to 1 for --status-only. A transient gh error now ends that run after the first failure, with no retry. That may be intended for a one-shot table. It is a behavior change beyond the no-checks case, so it may be worth a line in the description.

#592 also edits watch-pr/policy.ts. git merge-tree reports a conflict between the two in that file only. types.ts, render.ts, cli.ts and github.ts merge cleanly. Whichever lands second needs a small rebase.

@deanrie

deanrie commented Oct 23, 2026

Copy link
Copy Markdown
Author

Hey @AlexNegrescu26, thanks for running both sides against a real gh. All three points taken into the description: the base sequence now reads 60, 120, 240, 300 s then BLOCKER: status-query failures=5 and exit 7 (I had stopped counting one retry early), the one-error budget under --status-only is called out as a behaviour change of its own with the reasoning (a one-shot table should not sleep through a backoff schedule; callers that want retries use polling mode), and the policy.ts-only conflict with #592 is noted so whichever lands second knows to rebase. If the maintainers would rather keep the full budget for --status-only and only add ci-none, that is a one-line revert in runSimple and the tests split cleanly along that seam.

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.

pstack: watch-pr --status-only retries for minutes on a repo with no checks

2 participants