Parent: #1936
Problem
In maybeRecloseDisallowedReopen, both the warning comment and the actual close call are wrapped in .catch(() => undefined). Regardless of whether either GitHub API call succeeded or failed (403 from reduced permissions, 404, 5xx), the function unconditionally writes a github_app.reopen_reclosed audit event with outcome: "completed", describing the PR as re-closed. This directly mirrors the already-confirmed draft-dodge audit-fidelity gap, on the reopen-abuse path instead.
The practical impact is bounded, not indefinite: the PR row's state was already stamped "open" independently by the webhook upsert, and selectRegateCandidates orders by staleness/creation, not by this path's own (never-written) lastRegatedAt — so the next sweep will still pick the PR back up and run it through the full guard stack regardless. The real cost is a misleading audit trail: an operator or dashboard reading github_app.reopen_reclosed / outcome: "completed" believes the one-shot close was enforced when it may not have been.
Failure scenario: a non-maintainer reopens a PR gittensory previously closed. GitHub returns 403 on the close PATCH (reduced installation permissions, or a transient outage). The error is swallowed; the audit ledger still records a successful re-close. The PR is actually left open, and anyone trusting the audit trail believes otherwise — until the next sweep cycle naturally corrects it.
Requirements
- The audit outcome for this path must reflect whether the close actually succeeded.
Deliverables
- Capture the settled results of the comment and close calls (e.g.
Promise.allSettled or explicit try/catch per call) and branch the audit outcome accordingly: "completed" only when closePullRequest resolves successfully, "failed" (with the error in metadata) otherwise.
- Add a regression test:
closePullRequest throws — assert the audit event records a failed outcome, not "completed".
Acceptance criteria
- A failed close on this path is recorded as failed in the audit trail, not completed.
- A successful close is unaffected.
Expected outcome
The audit trail for reopen-reclose accurately reflects GitHub's actual state, closing an observability gap that could otherwise mask a permissions or availability problem.
Parent: #1936
Problem
In
maybeRecloseDisallowedReopen, both the warning comment and the actual close call are wrapped in.catch(() => undefined). Regardless of whether either GitHub API call succeeded or failed (403 from reduced permissions, 404, 5xx), the function unconditionally writes agithub_app.reopen_reclosedaudit event withoutcome: "completed", describing the PR as re-closed. This directly mirrors the already-confirmed draft-dodge audit-fidelity gap, on the reopen-abuse path instead.The practical impact is bounded, not indefinite: the PR row's
statewas already stamped"open"independently by the webhook upsert, andselectRegateCandidatesorders by staleness/creation, not by this path's own (never-written)lastRegatedAt— so the next sweep will still pick the PR back up and run it through the full guard stack regardless. The real cost is a misleading audit trail: an operator or dashboard readinggithub_app.reopen_reclosed/outcome: "completed"believes the one-shot close was enforced when it may not have been.Failure scenario: a non-maintainer reopens a PR gittensory previously closed. GitHub returns 403 on the close PATCH (reduced installation permissions, or a transient outage). The error is swallowed; the audit ledger still records a successful re-close. The PR is actually left open, and anyone trusting the audit trail believes otherwise — until the next sweep cycle naturally corrects it.
Requirements
Deliverables
Promise.allSettledor explicit try/catch per call) and branch the audit outcome accordingly:"completed"only whenclosePullRequestresolves successfully,"failed"(with the error in metadata) otherwise.closePullRequestthrows — assert the audit event records a failed outcome, not"completed".Acceptance criteria
Expected outcome
The audit trail for reopen-reclose accurately reflects GitHub's actual state, closing an observability gap that could otherwise mask a permissions or availability problem.