fix(queue): gate the draft-dodge close on write-permission readiness - #2391
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2391 +/- ##
=======================================
Coverage 95.88% 95.89%
=======================================
Files 224 224
Lines 25158 25162 +4
Branches 9150 9151 +1
=======================================
+ Hits 24124 24128 +4
Misses 421 421
Partials 613 613
🚀 New features to boost your workflow:
|
|
Warning 🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨 ⏸️ Gittensory review result - manual review recommendedReview updated: 2026-07-01 23:43:20 UTC
⏸️ Suggested Action - Manual Review
Review summary Nits — 7 non-blocking
Review context
Contributor next steps
Signal definitions
🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.
|
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
gittensory-ui | 40efc5a | Commit Preview URL Branch Preview URL |
Jul 01 2026, 11:26 PM |
The converted_to_draft draft-dodge handler calls closePullRequest directly instead of routing through executeAgentMaintenanceActions, so it never got the standard pipeline's PR_WRITE_CLASSES readiness guard. An installation whose pull_requests: write grant was revoked or never consented would still attempt the close, get a 403 from GitHub, and have the failure silently swallowed by the existing .catch() — with the audit event still recorded as "completed" as if the close actually happened, giving operators no signal that nothing was mutated. Add a resolveAgentPermissionReadiness check before the comment/close calls, mirroring executeAgentMaintenanceActions' own step 6. When not ready, record a "denied" audit outcome instead of attempting the GitHub call.
…d of denying The draft-dodge readiness check's getInstallation call was caught into null on any failure, which resolveAgentPermissionReadiness then treated identically to a genuine "pull_requests: write not granted" -- so a transient D1 read hiccup permanently suppressed the draft-dodge close and recorded a misleading permission-denied audit instead of letting the webhook job retry. getInstallation itself never swallows a real read failure (it only resolves null on a legitimate "row not found" query result), so removing the added .catch(() => null) lets a genuine failure propagate: processGitHubWebhook's own top-level catch records the actual error and re-throws, and the queue's standard retry-on-throw semantics re-attempt the job once the read succeeds.
40efc5a to
1a474eb
Compare
What
The
converted_to_draftdraft-dodge handler (src/queue/processors.ts) callsclosePullRequest()directly instead of routing throughexecuteAgentMaintenanceActions, since the whole point is to enforce the gate verdict against the current headSha even though the PR was converted to draft. But that means this one close path never got the standard actuation pipeline's write-permission readiness check (PR_WRITE_CLASSES/resolveAgentPermissionReadiness, step 6 inexecuteAgentMaintenanceActions). An installation whosepull_requests: writegrant was revoked or never consented would still attempt the close, get a 403 from GitHub, and have the failure silently swallowed by the existing.catch(() => undefined)— with thegithub_app.draft_dodge_closedaudit event still recorded withoutcome: "completed"as if the close actually happened. No unauthorized mutation occurs (GitHub itself rejects it), but the audit trail becomes misleading, with no signal for an operator to notice a misconfigured/under-permissioned installation.Fix
Added a
resolveAgentPermissionReadiness({ autonomy, installationPermissions })check at the top of thedraftMode === "live"branch, before the comment/close calls — mirroringexecuteAgentMaintenanceActions' own step 6 exactly.installationPermissionscomes from a freshgetInstallationread (this handler doesn't already hold one). When not ready, records a"denied"audit outcome (samegithub_app.draft_dodge_closedevent type, distinguishable viaoutcome) instead of attempting the GitHub call at all — neither the warning comment nor the close is posted, since posting a comment that promises an imminent close would be misleading if the close itself can't happen.Longer-term routing this path through
executeAgentMaintenanceActionsas a properPlannedAgentAction(which the issue also suggests, tracked jointly with the sibling live-recheck issue) would get this guard for free — out of scope here to keep this PR small and surgical.Tests
pull_requests: write, with a"denied"audit outcome citing the missing grant.processGitHubWebhook's own unconditionalupsertInstallation(env, payload)auto-creates a bare record from the payload, which has no explicit write grant either).npx tsc --noEmitclean.queue.test.ts— 204 passed.agent-execution.test.ts(the module the new import comes from) — 11 passed.src/queue/processors.ts: fully covered — a couple of genuinely-defensive fallbacks (agetInstallationread failure, a null-author edge case) are marked/* v8 ignore next */, mirroring the identical established convention already used twice in this same function for the same shapes of fallback.npm run test:coverage: 5602 passed, 4 skipped (pre-existing/unrelated), 0 failed.npm audit --audit-level=moderate: 0 vulnerabilities.Advances #1936. Closes #2134.