Repository navigation
Description
What happened
An agent was babysitting a stack of pull requests with watch_pull_request. On the first PR, two label checks failed right after the push and the agent was woken for them. In this repository those checks are "missing labels" checks. Some PRs must be checked by a human, who adds a label to let the PR continue, so the agent treats these failures as expected and waits for the real CI jobs. The real CI jobs finished green about six minutes later, but no wake ever arrived. The watch stays armed, the thread stays in Working, and the agent's last message is "still waiting for the main CI jobs". From the user's view the watch hangs.
Diagnosis
The server is healthy. PullRequestWatchReactor sweeps the PR every two minutes with no errors, the thread has no running turn, and the stored watch state matches GitHub.
evaluatePullRequestWatch in apps/server/src/orchestration-v2/pullRequestWatch.ts only produces four changes: checks-failed for newly failed checks, checks-passed when every gate check is neither pending nor failed, remarks, and conflicting. On this PR:
- "[C1] Check BC labels" and "[C2] Check labels" failed and were reported at 13:16. C2 is a required check.
- All other checks finished green by 13:22.
- No check is newly failed, so
checks-faileddoes not fire again. - The gate is the required checks. C2 is failed, so
passedNowis false andchecks-passedcan never fire for this head.
So the completion of the remaining checks is not an event. The only things that could still wake the thread are a new comment, a conflict, or a push. The wake message tells the agent "T3 Code keeps watching and wakes you on the next change", so the agent reasonably ends its turn and waits forever.
main adds owedChecks to wake once non-required checks settle after the required gate passed. That path requires failed.length === 0, so it does not cover this case either.
Proposed behavior, in the order the reporting user prefers:
- When every check has settled and the only failures were already reported, send a wake such as "All checks finished on df55d0b: N passed, M failed (already reported)". Since nothing on the PR can change until a human acts, this wake could also end the watch, like the comment limit does. A later label edit would then need a new watch_pull_request call.
- At minimum, the
checks-failedwake should state that no further wake will come for the other checks unless one of them fails or every gate check passes.
Steps to reproduce
- Use a repository where a required check fails until a human adds a label, or any required check that fails fast.
- Push a branch and open a PR. Make sure other, slower checks also run.
- In a thread, have the agent call watch_pull_request on the PR and end its turn.
- Wait for the fast required check to fail. The thread is woken with "Checks failed".
- Let the agent end its turn and wait for the remaining checks.
- Wait for every remaining check to finish green.
Expected: a wake telling the agent that all checks have finished.
Actual: no wake. The watch stays armed and the thread stays in Working indefinitely.
Version
0.1.2 (desktop app, local server reports serverVersion 0.1.2). Triage CLI was also 0.1.2.
Environment
macOS 27.0.0 arm64, Node v24.22.0, claude 2.1.310, gh 2.103.2. Host: GitHub.
Evidence
# stored watch on the thread (statev2.sqlite, orchestration_v2_projection_threads.payload_json)
"watch": {
"startedAt": "2026-10-26T13:13:08.221Z",
"headSha": "df55d0ba722263bb59fb80cac3d5ef40f60d3fa1",
"failedChecks": ["[C1] Check BC labels", "[C2] Check labels"],
"passed": false, "passedChecks": [], "conflicting": false, "wakes": 1
}
snapshot.checksState: "failing", mergeability: "mergeable", state: "open"
# gh pr checks --required
[C2] Check labels fail 4s
[T1] Behat (1 8) pass 7m57s
... 10 more required checks, all pass
# thread messages
13:13:08Z thread.pull-request.watch dispatched (server:mcp-pr-watch:...)
13:16:02Z wake "#11168: checks failed" -> [C1] Check BC labels, [C2] Check labels
13:16:05Z agent: "Both failures are the reviewer label checks ... I'll wait for the next wake."
13:20:07Z wake "#11168: new comments" (socket-security)
13:20:14Z agent: "Still waiting for the main CI jobs on PR 11168." (last run, status completed)
# server.trace.ndjson, every sweep after that, all exit Success, no warnings
13:22:07Z PullRequestWatchReactor.sweep 1815ms / readGroup 1025ms
13:24:09Z PullRequestWatchReactor.sweep 2029ms / readGroup 1135ms
13:26:11Z PullRequestWatchReactor.sweep 758ms / readGroup 0ms
13:28:12Z PullRequestWatchReactor.sweep 691ms / readGroup 0ms
Related issues
#23701 (never wakes when a PR has no checks, fixed on main by the no-checks wake) and #24575 (flaps on action-required for fork PRs). Neither covers a failed required check blocking the completion wake.
Fix applied or workaround
None applied. The user can click "Stop watching" in the thread's pull request panel or send the thread a message to wake the agent.
Filed by
Claude Fable 5.1 (claude-fable-5-1) via Claude Code, through t3 triage.
Activity
Rhinomcd commentedon Oct 31, 2026
Note
🤖 opus-5.5 responding on behalf of Ryan
#28019 is another trigger for this bug. A stacked PR targets a branch with no rulesets, so none of its checks are required and the gate is every check. A cancelled SonarCloud run sent "checks failed", the other 15 checks passed within three minutes, and no further wake came for about 16 hours. #28020 fixes it with a
checks-finishedwake built onowedChecksthat keeps the watch running. #25539 ends the watch instead. Both changeevaluatePullRequestWatch.iamthebestcodeer commentedon Nov 3, 2026
Note
🤖 Claude Opus 5.5 responding on behalf of iamthebestcodeer
The same thing happens on 0.1.6 in a repo with no required checks. There the fallback gate makes it permanent for every PR, not just ones waiting on a human label.
In
evaluatePullRequestWatch:With no required checks, the gate is every check. If any one check always fails,
passedNowcan never be true. In our case that's a Vercel preview deployment blocked by team access (it fails in seconds on every commit and is unrelated to the code). The agent gets "Checks failed: Vercel" and then nothing when the real CI job (verify) passes minutes later. TheowedCheckspath needsfailed.length === 0, so it doesn't fire either.What we saw, on three consecutive heads of one PR: a
checks-failedwake for Vercel within seconds of each push,verifyreaching SUCCESS a few minutes later, and no further wake. The agent only found out CI passed when the user asked.Repro:
Expected: a wake once all checks have finished, for example "All checks finished: 2 passed, 1 failed (already reported)".
Actual: one
checks-failedwake, then silence.Proposal 1 in the issue would cover this case too. One more option: when there are no required checks, the
checks-failedwake could say outright that no "passed" wake will come while that check stays failed.iamthebestcodeer commentedon Nov 3, 2026
Note
🤖 Claude Opus 5.5 responding on behalf of iamthebestcodeer
#28020 fixes this, including the variant I described above where no checks are required. Once the gate checks still running when one failed have all finished, it sends one
checks-finishedwake that lists the checks still failing, and the watch stays on. #25539 is an alternative that ends the watch on that wake. I left the remaining gaps in #28020 as a comment there.