Skip to content

fix(heartbeat): GitHub-verify PR-review evidence before flagging pr_review_output_missing (BLO-10448) - #427

Merged
kkroo merged 1 commit into
masterfrom
omar/blo10448-output-missing-github-verify
Jun 15, 2026
Merged

fix(heartbeat): GitHub-verify PR-review evidence before flagging pr_review_output_missing (BLO-10448)#427
kkroo merged 1 commit into
masterfrom
omar/blo10448-output-missing-github-verify

Conversation

@kkroo

@kkroo kkroo commented Jun 15, 2026

Copy link
Copy Markdown

Follow-up to the BLO-10448 reviewer-reliability work (#421/#423/#425). Those fixed the retry classes; this fixes the #1 remaining reviewer failure: false pr_review_output_missing.

Related (searched, no duplicates): #421/#423/#425 (merged BLO-10448 retry fixes). No open PR touches the evidence guard.

Thinking Path

evaluatePrReviewCompletionEvidence (server/src/services/heartbeat.ts) decides whether a finished PR-review run actually left a review by text-matching the agent's free-text summary. It flags pr_review_output_missing whenever the summary lacks a recognized posted-review/skip marker. Production data shows this is mostly false positives: over 7 days, of 95 flagged runs, 0 claim a fresh post, ~34% say "already reviewed" (legitimate idempotency skips the regex misses — e.g. a backtick before the SHA, or a repo#n ref instead of a bare SHA), and only ~3 are genuinely empty. The flagged PRs did receive Ally reviews (NOP#409: 6 reviews, penstock#156: 1). Each false flag flips a correct run to failed/error, creating sweep + agent-health noise (BLO-3202) and making metrics show dropped reviews that weren't dropped. The BLO-8195/9293/3202 comment trail is a history of regex-patching this guard — whack-a-mole. The authoritative signal lives on GitHub, not in the summary.

What Changed

  • server/src/services/github-app-auth.ts (new): mint an RS256 GitHub App JWT (node:crypto, no new dep) → cached short-lived installation tokengithubHasReviewerEvidenceForPr() which queries /pulls/{n}/reviews (bot review with commit_id === headSha) and /issues/{n}/comments (bot comment referencing the head SHA, for comment-mode reviews on bot-authored PRs). Reuses gitHubApiBase/ghFetch.
  • server/src/services/heartbeat.ts: at the evidence seam in executeRun, when the heuristic returns missing, await the GitHub check; on found override to posted_review (+ a lifecycle event); on error/not-found keep missing.
  • server/src/config.ts: githubAppId / githubAppInstallationId / githubAppPrivateKey / prReviewerBotLogin (all empty-default → feature inert when unset).
  • deploy/helm/...: mount the existing paperclip-github-app-creds secret into the api + workers tiers (the workers statefulset runs executeRun) behind githubApp.enabled (default false).

Verification

  • New server/src/__tests__/github-app-auth.test.ts (11): JWT verifies against its public key; installation-token caches; evidence found-via-review / found-via-comment / not-found / non-OK→error / no-creds→error.
  • Regression: heartbeat-context-summary + heartbeat-process-recovery + heartbeat-retry-scheduling190 pass. tsc --noEmit clean (0 errors). helm template renders the three GITHUB_APP_* env on the workers tier when githubApp.enabled=true, nothing when false.
  • Deploy step (separate from merge): set githubApp.enabled=true in the Blockcast values + redeploy so the workers pod gets the creds; then watch for "suppressing false pr_review_output_missing" lifecycle events and the flagged-rate drop.

Risks

Low / strictly additive. The GitHub check runs only on the missing branch, only rescues a false missing, and never downgrades a heuristic success. Any unconfigured creds / token failure / API error / not-found → unchanged missing (today's behavior), so it cannot regress. Head-SHA precision avoids masking a genuine miss on a newer head. The App key lives in pod env (standard for GitHub Apps; tokens are 1h-TTL, minted from the existing secret — no new long-lived credential). Default-off via Helm flag.

Model Used

claude-opus-4-8 (1M context)

Checklist

  • I have included a thinking path that traces from project context to this change
  • I have specified the model used (with version and capability details)
  • I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work
  • I have searched GitHub for duplicate or related PRs and linked them above
  • I have either (a) linked existing issues with Fixes: # / Closes # / Refs # OR (b) described the issue in-PR — Refs: BLO-10448
  • I have run tests locally and they pass
  • I have added or updated tests where applicable
  • If this change affects the UI, I have included before/after screenshots — N/A (server + Helm)
  • I have considered and documented any risks above
  • All Paperclip CI gates are green
  • Greptile is 5/5 with no open P2s, recommendations, or follow-ups
  • I will address all Greptile and reviewer comments before requesting merge

🤖 Generated with Claude Code

…eview_output_missing (BLO-10448)

The evidence guard (evaluatePrReviewCompletionEvidence) is a text heuristic over
the agent's free-text completion summary; it flags pr_review_output_missing
whenever the summary lacks a recognized posted-review/skip marker. In production
that is mostly false positives: over 7d, of 95 flagged runs, 0 claim a fresh
post, ~34% say 'already reviewed' (idempotency skips the regex misses — a
backtick before the SHA, or a repo#n ref instead of a bare SHA), and only ~3
are genuinely empty. The PRs DID get Ally reviews. These false failed/error
flags create sweep + agent-health noise (BLO-3202) and distort metrics. The
BLO-8195/9293/3202 trail shows the guard has been regex-patched repeatedly.

Fix: before keeping a 'missing' verdict, authoritatively check GitHub for a
reviewer-bot review/comment at THIS head. The server has no ambient GitHub
token, so mint short-lived App installation tokens in-process (node:crypto
RS256, no new dep) from the existing paperclip-github-app-creds secret. The
check only runs on the 'missing' branch and only RESCUES a false missing —
never downgrades a heuristic success; any error / unconfigured creds / not-found
falls back to today's behavior, so it cannot make things worse. Head-SHA
precision (review commit_id===headSha, or comment body referencing the SHA)
avoids masking a genuine miss on a newer head.

- server/src/services/github-app-auth.ts (new): App JWT mint, cached installation
  token, githubHasReviewerEvidenceForPr (reviews + comments, head-SHA precise).
- server/src/services/heartbeat.ts: async override at the evidence seam in
  executeRun; fallback-on-error.
- server/src/config.ts: githubApp* + prReviewerBotLogin (all empty-default -> inert).
- deploy/helm: mount paperclip-github-app-creds into api + workers (the workers
  tier runs executeRun) behind githubApp.enabled (default false).
- Tests: github-app-auth.test.ts (JWT verify, token cache, evidence branches,
  error fallback). Full heartbeat guard/recovery/retry suites green (190).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kkroo
kkroo merged commit 7ae4a04 into master Jun 15, 2026
15 checks passed
@kkroo
kkroo deleted the omar/blo10448-output-missing-github-verify branch June 15, 2026 07:00
kkroo added a commit that referenced this pull request Jun 15, 2026
…ockcast (BLO-10448) (#428)

Activates the GitHub-verified pr_review_output_missing fix (#427) on the
Blockcast deploy by setting githubApp.enabled=true in values.blockcast.yaml.
The docker.yml release runs `helm upgrade ... -f values.blockcast.yaml`, so
this mounts the existing paperclip-github-app-creds Secret (app_id /
installation_id / private_key.pem) into the api + workers tiers. The workers
StatefulSet runs the heartbeat executeRun finalization where the verification
fires. Chart plumbing + the default-off flag landed in #427; this is the
deploy-side enablement. helm template -f values.blockcast.yaml renders the
three GITHUB_APP_* env on both tiers.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kkroo added a commit that referenced this pull request Jun 19, 2026
…og non-rescue (BLO-10878) (#454)

The GitHub-verify rescue for false pr_review_output_missing (#427) left two gaps,
both observed in the post-deploy residual:

1. When the reviewer-wake carried no head SHA, derivePaperclipPrReview returned
   headSha=null, so githubHasReviewerEvidenceForPr's `headPrefix` was null and the
   comment-mode check was skipped entirely — only the formal-review loop ran. Ally
   frequently reviews in comment mode (no formal review), so those PRs (NOP#395,
   tc#777, …) flagged a false missing. Fix: when input.headSha is null, fetch the
   PR's current head from the API and run the precise review+comment match with it.
   Falls back to the existing lenient any-bot-review path only if the fetch fails.

2. The non-rescue branch logged nothing, making residual false-missings
   unclassifiable. Add a lifecycle event recording the outcome (the {error} code or
   no_evidence_found) so the remaining residual is diagnosable from events.

Tests: github-app-auth.test.ts +3 (null-head fallback→comment match, lenient
fallback when head unresolved, not-found after resolve). 14/14 pass; tsc clean;
heartbeat-retry-scheduling + process-recovery 121/121 green.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant