fix(server): wake PR watches when no checks report#23863
Problem
A watched GitHub pull request whose workflow skips its changes can wait forever without waking the agent. A docs-only pull request with pull_request.paths-ignore: ["docs/**"] reproduces this: no checks appear, and the watch reports no update.
Change
Report once when a GitHub head has remained without checks beyond a grace period. Keep the existing read cadence, preserve suppression after checks have been observed, and retain notification state across restarts and rewatching. Older watch records are handled conservatively. Other forge providers retain their existing behavior.
Scope and approval
Fixes #23701
Triaged bug via the automated issue flow; GitCafe issue #23701.
Verification
- The regression fails on main with
expected +0 to equal 1and passes with this fix. It verifies one notice, no CI-success claim, and suppression after pending checks were observed. Added regressions cover legacy saved watches and providers that do not report checks. - Focused server suites, scoped lint and server/contracts typechecks passed during implementation. After merging current main, all three watch suites passed: 105 tests in
runtimeLayer.test.ts,pullRequestWatch.test.ts, andpullRequestWatch.forgejoComments.test.ts. - Runtime evidence report and sanitized transcript: the real dev server read a docs-only GitHub head with zero checks and emitted exactly one
updatednotice after 18 minutes. The notice made no passing-check claim. Its persisted clock cleared; rewatching and restarting retained that state without another notice. - The eligibility floor is 15 minutes; unchanged GitHub fingerprints normally defer the next detail read to about 30 minutes. A dev reload preceded the captured notice, so this run does not establish uninterrupted quiet-read timing.
- Server-only change with no changed render path: visual fixture matrix is empty. No screenshots are applicable. No platform-specific code changed.
Implemented with Claude Opus 5.5 in the Claude Agent harness; triage, review, and proof with GPT-6.1-Sol in the Codex harness.
Review and audit log
Read-only audit of 1fbb142 against refreshed merge-base 2c8bdf6. Two fresh parallel reviewers inspected the complete diff and relevant callers/contracts. No code changes, installation, server/browser launch, or suite reruns in this round.
- Reviewer A — Codex / gpt-6.1-sol — VERDICT: MERGE. No blocking defect. Read full diff and traced persistence, compatibility, check evaluation, provider gating, and delivery. Reports passing in-memory diagnostics for threshold boundaries, late success/failure, serialized state, schema compatibility and cadence; these are reviewer diagnostics, not a committed regression or the supplied runtime transcript. Suggested coverage: late checks and no-checks-specific restart/rewatch.
- Reviewer B — Claude Agent / claude-opus-5-5 — VERDICT: MERGE. No blocking defect. Independently traced full seven-file diff; no tests run. Optional nullable contract field preserves older records/servers; rewatch copies both timestamp and cleared null state. Legacy seen heads intentionally stay silent until the next push. Other providers do not enter the new GitHub-only branch.
- Timing/product decision: 15 minutes is an eligibility floor from the first empty head read, not a delivery guarantee. Unchanged GitHub fingerprints normally defer detail rereads to about 30 minutes. Both reviewers consider this conservative informational behavior mergeable; the notice reports actual elapsed minutes. The runtime test exercises the null-fingerprint/10-minute reread path, rather than uninterrupted GitHub fingerprint timing.
- Late checks: Code inspection indicates a second real success/failure update: no-checks clears only the clock, leaves
passedfalse, and keeps watching. The existing check-evaluation path remains active. The notice says “No checks have reported” and usesupdated, never claims CI passed. - All-green case: This PR does not fix a separate report of checks existing and all turning green without waking. The passing-check decision and wake-dispatch path are unchanged; normal pending-to-green still produces
checks-passed. That report requires a separate reproduction. - Test quality: New runtime coverage uses
TestClock.adjust; pure evaluator tests receive explicit time. No wall-clock waits. Existing GitCafe head status reports 35 successful checks; the author's 105-test result was not independently rerun here.
Proof audit — runtime transcript and evidence report fetched with curl. Line references below refer to the original pretty-printed JSON transcript.
| Required row | Evidence | Status |
|---|---|---|
| Zero-checks head notifies once | Transcript lines 60–128: persisted clock, cleared clock, one updated notice without pass wording; lines 2–3 record one unique notice. runtimeLayer.test.ts, test “wakes once when no checks report on the head, without saying they passed”. | Proven by transcript and test |
| Checks observed then none → no notice | Same named runtime test, lines 2834–2842: another head observed pending, then empty across an hour of TestClock sweeps; total notice count stays one. | Proven by test |
| Late checks after the notice | No supplied transcript or committed test takes the already-notified same head through late pending checks to success/failure and asserts the second wake. Reviewer A reports passing in-memory diagnostics and both reviewers traced the correct path, but no reproducible proof artifact was supplied for that sequence. | Unproven by transcript or named committed test |
| Restart keeps state | Transcript lines 133–168 retain null on ended watch and rewatch; lines 183–205 record restart, healthy sweep, restored null clock and notice count one. Proves startup/sweep survival; does not separately instrument a successful post-restart host reread. | Proven by transcript within stated limit |
| Legacy watch record | pullRequestWatch.test.ts, test “does not tell a watch saved before the no-checks clock that its seen head has none”: seen head remains silent, pushed head notifies once. | Proven by test |
| Non-GitHub forge unchanged | provider exclusion test, test “never reports missing checks on a host whose checks it cannot read”: Azure stays silent over an hour; existing Forgejo boundary test remains, and runtime logic is explicitly GitHub-only. | Proven by test |
SHA provenance: Transcript lines 170–171 and 183–184 identify implementation 171fec9, rather than current head. Compared all added/removed diff lines from evidence base ef92441 to that implementation against the current PR's merge-base-to-head diff: the only difference is export const PULL_REQUEST_WATCH_NO_CHECKS_MS becoming private const; no runtime semantic drift in the patch. Transcript's 6074dd4… is the watched scratch PR head, not the implementation SHA. The reload immediately before the captured notice prevents any claim of uninterrupted quiet-fingerprint delivery timing; the red result is supplied evidence, not independently rerun.
Audit VERDICT: INSUFFICIENT — late checks after the notice. This is a proof gap, not a confirmed implementation defect.
Models/harnesses: reviewer A gpt-6.1-sol / Codex; reviewer B claude-opus-5-5 / Claude Agent; proof audit and reconciliation gpt-6.1-sol / Codex. No reviewer fallback required.
Round 2
Added “wakes for checks that complete after the no-checks notice on the same head” in runtimeLayer.test.ts. It passes: the same head emits one no-checks notice after TestClock advances, then the normal checks-passed update when late checks complete, with no duplicate no-checks notice. This closes the audit’s missing late-checks row. All three watch suites pass (106 tests); scoped lint passes.
Test addition: GPT-6.1-Sol / Codex; read-only review: Claude Opus 5.5 / Claude Agent.
Checks
- SuccessfulCodeRabbitGitHub
Review completed
- Successful
Skipped - Not affected
- Skipped
- Skipped
- Skipped
- SuccessfulRemove preview downloadGitHub
- SkippedDeploy web previewGitHub
- Skipped
- Skipped
- Skipped
- SkippedRemove preview downloadGitHub
- SkippedBuild preview JS bundleGitHub
- SkippedEAS PreviewGitHub
- SuccessfulCheckGitHub
- Skipped
- SkippedRustGitHub
- SuccessfulBuild and TypecheckGitHub
- SuccessfulRelease SmokeGitHub
- SuccessfulTestGitHub
- SuccessfulTest WebGitHub
- Skipped
- SuccessfulLabel PR sizeGitHub
- SuccessfulLabel PR 23863GitHub
- Skipped
- SkippedEAS PreviewGitHub
- SkippedDeploy web previewGitHub
- Neutral
- SuccessfulTest Server 4GitHub
- SuccessfulTest Server 6GitHub
- SuccessfulTest Server 8GitHub
- SuccessfulTest Server 1GitHub
- SuccessfulTest Server 5GitHub
- SuccessfulTest Server 2GitHub
- SuccessfulTest Server 7GitHub
- SuccessfulTest Server 3GitHub
- SuccessfulChangesGitHub
- SuccessfulLintGitHub
- SuccessfulNative fingerprint diffGitHub
- SuccessfulCollect PR targetsGitHub
- SuccessfulPrepare PR size configGitHub
- Skipped
Added 4 commits
Approvability
Verdict: Needs Human Approval
Macroscope's review found this PR requires human approval — This focused server fix adds a persisted, hard-coded 15-minute policy for notifying on GitHub pull requests with no reported checks. Because that timing changes default product behavior for existing watches, the change warrants human review.
Notes:
- No code objects were reviewed. Approvability was decided on eligibility alone.
You can add or adjust custom eligibility rules. Learn more.
No actionable comments were generated in the recent review. 🎉
ℹ️ Recent review info
⚙️ Run configuration
- Source mode: Auto
- Repository: pingdotgg/t3code
- Branch: fix/issue-23701
- File: .coderabbit.config.ts
- Commit: 048e855
- Layers: Repository TS config
- Review profile: CHILL
- Plan: Team
- Run ID:
f5916c3f-579b-42b8-860a-56034c36e118
📥 Commits
Reviewing files that changed from the base of the PR and between 1fbb1426134e533d482bd930e865ff3058528f66 and 048e855e9e76bd8b4562cf2be92842187a022995.
📒 Files selected for processing: 1
apps/server/src/orchestration-v2/runtimeLayer.test.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 3 remain after this review.
📝 Walkthrough
GitHub pull-request watches now track how long the current head has no checks and emit a
no-checkschange after 15 minutes. The timer is stored in watch state and carried into a new watch when defined. Other providers do not apply the timeout.Changes: Pull-request no-checks notification
Layer / File(s) Summary Track and report missing checks
packages/contracts/src/threadPullRequest.ts,apps/server/src/orchestration-v2/pullRequestWatch.ts,apps/server/src/orchestration-v2/pullRequestWatch.test.ts,apps/server/src/orchestration-v2/pullRequestWatch.forgejoComments.test.tsThe watch contract adds an optional nullable checksAwaitedSincetimestamp. For GitHub, the evaluator tracks empty checks and emits ano-checkschange after 15 minutes. Tests cover elapsed time and provider behavior.Carry timer through watch lifecycle
apps/server/src/orchestration-v2/Orchestrator.ts,apps/server/src/orchestration-v2/PullRequestWatchReactor.ts,apps/server/src/orchestration-v2/runtimeLayer.test.tsThe orchestrator carries the timer into a new watch when defined. The reactor compares timer state and passes the current time to the evaluator. Runtime tests cover no-check notifications and later check reads. Priority: ➖ Normal
Estimated code review effort: 3 (Moderate) | ~20 minutes
Change: Bug fix
Sequence Diagram(s)
mermaid1 sequenceDiagram 2 participant PullRequestWatchReactor 3 participant evaluatePullRequestWatch 4 participant WatchState 5 PullRequestWatchReactor->>evaluatePullRequestWatch: Pass provider, pull-request details, remarks, and current time 6 evaluatePullRequestWatch->>WatchState: Read and update checksAwaitedSince 7 evaluatePullRequestWatch-->>PullRequestWatchReactor: Return changes and next watch state Possibly related issues
- Issue 23701: The PR adds the delayed informational no-check notice and preserves its state, addressing the reported missing wake when a GitHub head has no checks.
- Issue 18728: The PR does not address the Azure DevOps case; non-GitHub providers do not apply the no-check timeout.
Suggested reviewers:
maria-rcks
Merge Risk: ⚪ Minimal · up to 048e8
The same-head late-check sequence is covered, and no issue requiring a fix before merge is established.
Pre-merge checks: Passed 4
Check name Status Explanation Title check Passed The title is concise, uses the repository's conventional commit format, and clearly describes waking server pull-request watches when checks do not report. Description check Passed The description includes all required sections. It explains the problem, implementation, scope and approval, focused verification, limitations, runtime evidence, and agent details. It also clearly ide… Linked Issues check Passed Check skipped because no linked issues were found for this pull request. Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request. Full details: Description check
Explanation
The description includes all required sections. It explains the problem, implementation, scope and approval, focused verification, limitations, runtime evidence, and agent details. It also clearly identifies the remaining proof gap for late checks without presenting it as a confirmed defect.
✨ Finishing Touches
📝 Generate docstrings
- Commit to this branch
- Create a new PR
🧪 Generate unit tests (beta)
- Commit to this branch
- Create a new PR
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Comment @coderabbitai help to get the list of available commands.
Thread transfer impact
✅ Thread transfer remains within every enforced ceiling.
| Provider | Metric | Main baseline | This PR | Impact | PR ceiling | |
|---|---|---|---|---|---|---|
| Codex | Total thread wire | 5.0 KiB | 5.0 KiB | −7 B (−0.1%) | 6.8 KiB | ✅ |
| Codex | Thread snapshot wire | 3.8 KiB | 3.8 KiB | 0 B (0.0%) | 4.9 KiB | ✅ |
| Codex | Live turn WebSocket wire | 1.2 KiB | 1.2 KiB | −7 B (−0.6%) | 2.0 KiB | ✅ |
| Codex | Live turn WebSocket decoded | 20.9 KiB | 20.9 KiB | 0 B (0.0%) | 29.3 KiB | ✅ |
| Codex | Live turn messages | 2 | 2 | 0 (0.0%) | 8 | ✅ |
| Claude | Total thread wire | 5.0 KiB | 5.0 KiB | +24 B (+0.5%) | 6.8 KiB | ✅ |
| Claude | Thread snapshot wire | 3.8 KiB | 3.8 KiB | 0 B (0.0%) | 4.9 KiB | ✅ |
| Claude | Live turn WebSocket wire | 1.2 KiB | 1.2 KiB | +24 B (+2.0%) | 2.0 KiB | ✅ |
| Claude | Live turn WebSocket decoded | 21.2 KiB | 21.2 KiB | +41 B (+0.2%) | 29.3 KiB | ✅ |
| Claude | Live turn messages | 1 | 2 | +1 (+100.0%) | 8 | ✅ |
Baseline: 2c8bdf6 · PR result: 048e855 · Source CI: success
Scenario and decoded snapshot size
10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
- Codex decoded thread snapshot: 108.5 KiB
- Claude decoded thread snapshot: 108.8 KiB
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.
Added 1 commit
- fix(server): keep the no-checks wait constant module-private1fbb142
Added 1 commit
- test(server): cover checks arriving after no-checks notice048e855