Parent: #1936
Problem
installationPermissions is read once from the installations table at the top of the action loop and threaded through for the whole pass. That DB row is updated only by an installation App-lifecycle webhook carrying a permissions object, or by the ~30-minute health-refresh cron — GitHub sends no webhook at all for a maintainer-initiated permission downgrade (only for consented upgrades), so a downgrade can go undetected by the readiness gate for up to 30 minutes.
Importantly, this is a false-readiness/wasted-attempt window, not an actual unauthorized-mutation bypass — GitHub enforces the live installation scope server-side on the actual API call and returns 403, which the existing terminal-403 handling catches and holds correctly. The gap is that the local readiness signal can lag the true GitHub-side permission instead of being verified at mutation time.
Failure scenario: a maintainer downgrades pull_requests from write to read in the GitHub App settings. For up to 30 minutes, the local readiness check still reports write access, so the executor attempts merge/close/review calls that GitHub then rejects with 403 — wasted attempts rather than a security issue, but a real staleness gap in an otherwise fail-closed system.
Requirements
- The local permissions snapshot should self-heal promptly after a real-world downgrade is discovered, rather than waiting for the next scheduled cron tick.
Deliverables
- On receiving a 403 from a PR-write mutation, opportunistically trigger an installation-health refresh so the local permissions snapshot updates immediately instead of waiting for the next 30-minute cron tick, shrinking the wasted-attempt window for subsequent actions in the same sweep/PR.
- Add a regression test asserting a 403 on a mutation triggers an installation-health refresh.
Acceptance criteria
- After a 403 due to a stale permissions snapshot, subsequent actions on the same installation reflect the corrected (downgraded) permissions without waiting for the next cron tick.
Expected outcome
The local permission-readiness signal converges to reality quickly after any live rejection, minimizing (though not eliminating, since GitHub's own enforcement is already the real backstop) wasted mutation attempts.
Parent: #1936
Problem
installationPermissionsis read once from theinstallationstable at the top of the action loop and threaded through for the whole pass. That DB row is updated only by aninstallationApp-lifecycle webhook carrying apermissionsobject, or by the ~30-minute health-refresh cron — GitHub sends no webhook at all for a maintainer-initiated permission downgrade (only for consented upgrades), so a downgrade can go undetected by the readiness gate for up to 30 minutes.Importantly, this is a false-readiness/wasted-attempt window, not an actual unauthorized-mutation bypass — GitHub enforces the live installation scope server-side on the actual API call and returns 403, which the existing terminal-403 handling catches and holds correctly. The gap is that the local readiness signal can lag the true GitHub-side permission instead of being verified at mutation time.
Failure scenario: a maintainer downgrades
pull_requestsfrom write to read in the GitHub App settings. For up to 30 minutes, the local readiness check still reports write access, so the executor attempts merge/close/review calls that GitHub then rejects with 403 — wasted attempts rather than a security issue, but a real staleness gap in an otherwise fail-closed system.Requirements
Deliverables
Acceptance criteria
Expected outcome
The local permission-readiness signal converges to reality quickly after any live rejection, minimizing (though not eliminating, since GitHub's own enforcement is already the real backstop) wasted mutation attempts.