feat(agent-actions): add an install-wide contributor open-item cap across repos - #2678
Merged
Merged
Conversation
…ross repos A self-hosted install that gates multiple repos shares one database, but the per-repo contributorOpenPrCap/contributorOpenIssueCap only ever count open items on the same repo, so an actor spreading low-volume spam/farming PRs across several gated repos in one install never trips any single repo's cap. This adds an optional GLOBAL_CONTRIBUTOR_OPEN_ITEM_CAP env var, checked in addition to (not instead of) the existing per-repo caps via a same-database aggregate query over every repo the install tracks -- no cross-instance networking, off by default, reusing the existing contributor_cap closeKind and close-message shape (mirrors global_contributor_blacklist's install-scoped singleton pattern). Closes #2562
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2678 +/- ##
=======================================
Coverage 96.10% 96.11%
=======================================
Files 237 238 +1
Lines 26538 26563 +25
Branches 9624 9634 +10
=======================================
+ Hits 25505 25530 +25
Misses 424 424
Partials 609 609
🚀 New features to boost your workflow:
|
This was referenced Jul 3, 2026
JSONbored
added a commit
that referenced
this pull request
Jul 3, 2026
countOpenItemsForAuthorAcrossRepos (merged via #2678, closing #2562) counted an author's open PRs/issues across the ENTIRE D1 database with no installation scoping at all -- on a database shared by multiple installations (the hosted product's normal shape, and possible on self-host too), a contributor's activity on one installation could wrongly trigger GLOBAL_CONTRIBUTOR_OPEN_ITEM_CAP closes on a completely unrelated installation that never gated them. Scope the query through repositories.installationId first (matching the existing markRepositoriesRemovedFromInstallation precedent), then inArray(...) against the resulting repoFullNames -- this codebase has no Drizzle joins to lean on instead. Also fixes 5 AiReviewCacheInput test fixtures in queue.test.ts left broken by an unrelated already-merged PR (#2675, security-focused review profile) that added a required securityFocus field without updating these fixtures -- main's typecheck was red without this, which this PR's own CI would otherwise have inherited.
JSONbored
added a commit
that referenced
this pull request
Jul 3, 2026
…ion (#2687) * fix(agent-actions): scope the global open-item cap query by installation countOpenItemsForAuthorAcrossRepos (merged via #2678, closing #2562) counted an author's open PRs/issues across the ENTIRE D1 database with no installation scoping at all -- on a database shared by multiple installations (the hosted product's normal shape, and possible on self-host too), a contributor's activity on one installation could wrongly trigger GLOBAL_CONTRIBUTOR_OPEN_ITEM_CAP closes on a completely unrelated installation that never gated them. Scope the query through repositories.installationId first (matching the existing markRepositoriesRemovedFromInstallation precedent), then inArray(...) against the resulting repoFullNames -- this codebase has no Drizzle joins to lean on instead. Also fixes 5 AiReviewCacheInput test fixtures in queue.test.ts left broken by an unrelated already-merged PR (#2675, security-focused review profile) that added a required securityFocus field without updating these fixtures -- main's typecheck was red without this, which this PR's own CI would otherwise have inherited. * fix(agent-actions): audit a truncated installation repo list, don't drop it silently listRepoFullNamesForInstallation's .limit(20_000) meant an installation with more tracked repos than that would silently undercount toward GLOBAL_CONTRIBUTOR_OPEN_ITEM_CAP with no signal anything was dropped. Records an audit event on the rare install where the limit is still hit, mirroring the same observability pattern already used for the per-author item-count truncation in this file. Addresses a gate review finding on #2687.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
GLOBAL_CONTRIBUTOR_OPEN_ITEM_CAPenv var (src/env.d.ts, parsed by the newsrc/settings/global-contributor-cap.ts) so a self-hosted install that gates multiple repos can cap a contributor's SUM of open PRs+issues across every repo it tracks, not just one repo. Off by default (unset/invalid ⇒ no cap), mirroringglobal_contributor_blacklist's tenant-free-singleton design (src/db/repositories.ts:589getGlobalContributorBlacklist) but as an env var since the issue doesn't need a maintainer-editable value, just an install operator's own config.countOpenItemsForAuthorAcrossRepos(src/db/repositories.ts), a same-database aggregate query summing open PRs + open issues for one author with norepoFullNamefilter, unlike the existingcountOpenPullRequests/countOpenIssues. No cross-instance networking — purely a query against the same D1/SQLite database every repo on the install already shares.src/queue/processors.ts(runAgentMaintenancePlanAndExecutefor PRs,maybeCloseIssueOverContributorCapfor issues), IN ADDITION TO the existing per-repocontributorOpenPrCap/contributorOpenIssueCapchecks, in the same short-circuit block. Reuses the existingautoCloseExemptLoginsshared exemption list (src/settings/auto-close-exempt.ts) so a maintainer-named login is exempt from the install-wide cap exactly like the per-repo caps and review-nag cooldown.contributorCapMatch/closeKind: "contributor_cap"short-circuit insrc/settings/agent-actions.ts— no new disposition type. Added an optionalscope?: "repository" | "install"field so the close-comment wording is accurate ("this repository's configured limit"vs."this install's configured limit (across every repository it gates, combined)") without changing the closeKind, label, or overall message shape; absentscopedefaults to the original repository wording for back-compat with every existing caller.Scope
type(scope): short summaryConventional Commit format, for examplefix(api): restore profile access checks.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Validation
git diff --checknpm run actionlintnpm run typechecknpm run test:coveragelocally;codecov/patchrequires ≥99% coverage of the lines AND branches you changed (aim for 100% on your diff so CI variance does not fail near the threshold). Global coverage is a non-blocking trend with a loose 90% backstop, not the gate.npm run test:workersnpm run build:mcpnpm run test:mcp-packnpm run ui:openapi:checknpm run ui:lintnpm run ui:typechecknpm run ui:buildnpm audit --audit-level=moderateRan the full
npm run test:cigate (actionlint, migrations check, cf-typegen check, typecheck, full unsharded coverage suite, workers tests, MCP build/pack, miner build, REES tests, UI openapi/settings-parity/version-audit/lint/typecheck/test/build) — all green, 354 test files / 6751 tests passed, 0 failures.npm audit --audit-level=moderatereports 0 vulnerabilities.Safety
UI Evidencesection below with screenshots. (N/A — backend-only change, no visible UI.)CHANGELOG.mdedit.)UI Evidence
N/A — this is a backend-only change (no UI surfaces touched).
Notes
GLOBAL_CONTRIBUTOR_OPEN_ITEM_CAP), not a per-repo.gittensory.yml/DB field, because it aggregates ACROSS repos and therefore cannot be "this repo's" setting — it belongs to the install as a whole. It is not added towrangler.jsonc's hostedvarsblock (mirrors how other self-host-oriented numeric env vars likeAI_BYOK_DAILY_REPO_LIMITare handled — declared only insrc/env.d.ts), so it does not requirecf-typegenregeneration.Closes #2562