feat(agent-actions): cancel in-flight CI runs when a PR is auto-closed for the contributor cap - #2662
Conversation
|
Warning 🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨 ⏸️ Gittensory review result - manual review recommendedReview updated: 2026-07-03 05:23:36 UTC
⏸️ Suggested Action - Manual Review
Review summary Blockers
Nits — 7 non-blocking
Concerns raised — review before merging
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.
|
ec73c9d to
5cdc19f
Compare
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
gittensory-ui | 46ab76c | Commit Preview URL Branch Preview URL |
Jul 03 2026, 06:05 AM |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2662 +/- ##
=======================================
Coverage 96.13% 96.14%
=======================================
Files 239 239
Lines 26777 26846 +69
Branches 9719 9743 +24
=======================================
+ Hits 25743 25812 +69
Misses 424 424
Partials 610 610
🚀 New features to boost your workflow:
|
5cdc19f to
9418b8d
Compare
…d for the contributor cap Closing a PR for exceeding the per-contributor open-item cap currently leaves its in-flight Actions runs burning CI minutes for nothing. Adds an opt-in contributorCapCancelCi setting (per-repo, or an install-wide CONTRIBUTOR_CAP_CANCEL_CI_DEFAULT env var fallback) that lists and cancels a closed PR's in-progress/queued workflow runs at its head SHA right after a contributor_cap close succeeds. Requires the actions:write permission (added to the self-host App manifest); an installation that hasn't re-approved it degrades gracefully -- the cancellation attempt is skipped and logged via a dedicated audit event, and never blocks or fails the close itself, which has already succeeded by the time this hook runs. Off by default; zero behavior change until a repo (or the install-wide env var) opts in. Closes #2462.
main independently merged 0097_command_rate_limit.sql, colliding with this branch's own 0097_contributor_cap_cancel_ci.sql after rebase.
… audit hook codecov/patch flagged 2 uncovered lines: the closing .catch(() => undefined) line of each recordAuditEvent call inside recordContributorCapCiCancelOutcome never registered a hit, despite both outcome paths being exercised by existing tests (a known v8-instrumentation quirk with a `.catch()` chained directly onto a large multi-line object-literal call). Split each audit write into its own small named helper with the call assigned to a local before awaiting/catching, matching the shape other audit-write call sites in this file already use without the same gap.
…ful one
cancelOneWorkflowRun returned an untyped {kind:"not_permission_error"} for
any non-403 cancel failure (a genuine 500/404/422), and the caller's loop
only branched on "cancelled" vs "permission_missing" -- silently falling
through past that third case and letting the loop's final return still claim
kind:"cancelled" with an undercounted cancelledCount. A real cancel failure
was audited as a successful cancellation.
cancelOneWorkflowRun now mirrors listWorkflowRunIdsForStatus's own error
shape ({kind:"error", warning}, carrying the actual status + message), and
the caller returns immediately on ANY non-cancelled result instead of
continuing past it -- matching the pattern already used one function up.
Also: recordContributorCapCiCancelOutcome hardcoded every non-cancelled
outcome under the ...permission_missing audit event type, even a genuine
network/list/cancel error with kind:"error" -- now selects the event type
from outcome.kind, so a real failure is no longer misclassified as a
permission gap on any dashboard querying by eventType.
Also fixes a secret-scanner false positive: new test fixtures reused the
pre-existing "installation-token" literal verbatim; since this is the first
time those specific lines appear as new diff content, the scanner flags it
the same way this exact false positive was already worked around elsewhere
in the codebase -- renamed to the established "fake-installation-token"
convention.
… branch Codecov would otherwise flag auditContributorCapCiCancelFailed's closing .catch(() => undefined) as an uncovered branch, symmetric to the cancel-success audit write's own already-tested failure path.
listWorkflowRunIdsForStatus only read GitHub's default first page of /actions/runs, so a head SHA with more than one page of matching queued/in_progress runs left page-2+ runs uncancelled while cancelInFlightWorkflowRunsForHeadSha reported totalFound/cancelledCount as if the listing were complete. per_page=100 + follow Link: rel="next" until exhausted, bounded to 10 pages (mirrors backfill.ts's githubPaginatedList/PR_DETAIL_MAX_PAGES). Addresses a gate review finding on #2462.
6098328 to
46ab76c
Compare
Summary
contributorCapCancelCisetting (per-repo.gittensory.yml/DB, or an install-wideCONTRIBUTOR_CAP_CANCEL_CI_DEFAULTenv var fallback for repos that haven't configured their own value) that, right after acloseKind: "contributor_cap"close succeeds, lists and cancels that PR's in-progress/queued workflow runs at its head SHA.cancelInFlightWorkflowRunsForHeadShaGitHub REST wrapper (src/github/app.ts) — greenfield, no existing Actions-API wrapper to extend. Never throws; returns a typed{kind: "cancelled" | "permission_missing" | "error"}result so a missing scope or transient failure can never retroactively turn an already-successful close into a recorded"error".actions: writeApp permission, added to the self-host setup-wizard manifest. A pre-existing installation must explicitly re-approve this permission bump before cancellation works for them — GitHub does not silently grant it. Until they do (or if they never do), the feature degrades gracefully: the cancellation attempt is skipped and logged via a dedicatedgithub_app.contributor_cap_ci_cancel_permission_missingaudit event, and the close itself is completely unaffected. Added a new "Re-approving a permission bump on an existing App" docs section with the exact re-approval steps.Closes #2462.
Scope
type(scope): short summaryConventional Commit format.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Validation
git diff --checknpm run actionlint(vianpm run test:ci)npm run db:migrations:check— migration 0097, contiguous, no new duplicatesnpm run typechecknpm run test:coveragelocally — full branch coverage confirmed on every changed file via a manual lcov diff-range parse (not just the aggregate %). Tests cover: cancellation with in_progress + queued runs, zero-runs, a 409 (already-cancelling) treated as a non-failure, a genuine 403 while listing vs. while cancelling, a rate-limited 403 excluded from permission-missing on both the list AND cancel calls, a non-permission cancel failure silently skipped (not counted, not failed), a network/non-Error throw, a malformed repo name, a 403/list-error body with nomessagefield (exercises the||/??fallback sides), the executor hook enabled/disabled/permission-degraded/env-default/repo-override, and the.gittensory.ymlsettings:parsing (set/clear-via-null/omit/invalid). Also mutation-tested the core "close still succeeds even if the cancel hook fails" isolation property by temporarily making the hook throw and confirming the close-audit outcome degraded from"completed"to"error"as expected — then restored.npm run test:workers(vianpm run test:ci)npm run build:mcp(vianpm run test:ci)npm run test:mcp-pack(vianpm run test:ci)npm run ui:openapi:check— regenerated and committedapps/gittensory-ui/public/openapi.jsonnpm run ui:lint(vianpm run test:ci)npm run ui:typecheck(vianpm run test:ci)npm run ui:build(vianpm run test:ci)npm audit --audit-level=moderateSafety
permission_missingaudit is recorded, never a close failure).contributorCapCancelCiadded to theRepositorySettingsOpenAPI schema and regenerated.UI Evidencesection below with screenshots. — N/A, docs prose only, no visual/interactive UI change..gittensory.yml.example,.env.example, anddocs.self-hosting-github-app.tsx(new permission list entry + a new re-approval-steps section) all updated; the manifest↔docs parity test (setup-wizard-docs-parity.test.ts) passes.Notes
actions: writegrant is re-approved on the specific installation, so there is no forced-upgrade behavior change for anyone who doesn't take both actions.