Parent: #1936
Problem
The converted_to_draft draft-dodge handler (src/queue/processors.ts:3389) calls closePullRequest() directly instead of routing through executeAgentMaintenanceActions. Functionally this is partly intentional (the whole point is to enforce a verdict against the current headSha even though the PR was converted to draft), but it means this one close path has no equivalent of the write-permission readiness check (PR_WRITE_CLASSES / resolveAgentPermissionReadiness) every other close path gets — an installation whose pull_requests: write grant was revoked or never consented would still attempt this close, get a 403 from GitHub, and have the failure silently swallowed by an existing .catch(() => undefined), with the github_app.draft_dodge_closed audit event still written as if it succeeded. No unauthorized mutation occurs (GitHub itself rejects it), but the audit trail becomes misleading — a "completed" close that never actually happened, with no error signal for an operator to notice.
This is closely related to, but distinct from, the sibling "no live re-check" issue already filed for this same handler — that one is about state freshness; this one is about the missing permission-readiness gate specifically.
Requirements
- A close attempted without the required
pull_requests: write grant must be distinguishable in the audit trail from one that actually succeeded.
Deliverables
- Add a
resolveAgentPermissionReadiness check (mirroring PR_WRITE_CLASSES step 6 in executeAgentMaintenanceActions) to the draft-dodge branch before the closePullRequest call, recording a "denied — pull_requests: write not granted" audit outcome instead of attempting the call and swallowing the resulting 403.
- Longer-term, consider routing this path through
executeAgentMaintenanceActions as a proper PlannedAgentAction (tracked jointly with the sibling freshness-recheck issue), which would gain this guard for free.
- Add a regression test asserting the close is denied (not attempted) when
installationPermissions.pull_requests !== "write".
Acceptance criteria
- A draft-dodge close attempted without the required GitHub permission is denied before the API call, with a distinguishable audit outcome.
- No change to behavior when the permission is present.
Expected outcome
The audit trail for draft-dodge closes accurately reflects whether a close actually happened on GitHub, closing a fidelity gap that could otherwise mask a misconfigured/under-permissioned installation.
Parent: #1936
Problem
The
converted_to_draftdraft-dodge handler (src/queue/processors.ts:3389) callsclosePullRequest()directly instead of routing throughexecuteAgentMaintenanceActions. Functionally this is partly intentional (the whole point is to enforce a verdict against the current headSha even though the PR was converted to draft), but it means this one close path has no equivalent of the write-permission readiness check (PR_WRITE_CLASSES/resolveAgentPermissionReadiness) every other close path gets — an installation whosepull_requests: writegrant was revoked or never consented would still attempt this close, get a 403 from GitHub, and have the failure silently swallowed by an existing.catch(() => undefined), with thegithub_app.draft_dodge_closedaudit event still written as if it succeeded. No unauthorized mutation occurs (GitHub itself rejects it), but the audit trail becomes misleading — a "completed" close that never actually happened, with no error signal for an operator to notice.This is closely related to, but distinct from, the sibling "no live re-check" issue already filed for this same handler — that one is about state freshness; this one is about the missing permission-readiness gate specifically.
Requirements
pull_requests: writegrant must be distinguishable in the audit trail from one that actually succeeded.Deliverables
resolveAgentPermissionReadinesscheck (mirroringPR_WRITE_CLASSESstep 6 inexecuteAgentMaintenanceActions) to the draft-dodge branch before theclosePullRequestcall, recording a "denied — pull_requests: write not granted" audit outcome instead of attempting the call and swallowing the resulting 403.executeAgentMaintenanceActionsas a properPlannedAgentAction(tracked jointly with the sibling freshness-recheck issue), which would gain this guard for free.installationPermissions.pull_requests !== "write".Acceptance criteria
Expected outcome
The audit trail for draft-dodge closes accurately reflects whether a close actually happened on GitHub, closing a fidelity gap that could otherwise mask a misconfigured/under-permissioned installation.