Skip to content

fix: scope maintainer-discover to user installations and bound sweep (#784) - #832

Merged
jakharmonika364 merged 5 commits into
Coder-s-OG-s:mainfrom
namann5:fix/784-maintainer-discover-bounded-calls
Aug 1, 2026
Merged

fix: scope maintainer-discover to user installations and bound sweep (#784)#832
jakharmonika364 merged 5 commits into
Coder-s-OG-s:mainfrom
namann5:fix/784-maintainer-discover-bounded-calls

Conversation

@namann5

@namann5 namann5 commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #784 — the daily cron sweep ran unbounded O(users x installs x repos) GitHub API calls per tick, enabling cross-installation rate limit exhaustion.

Vulnerability

The \maintainer-discover\ function had three critical flaws:

  1. Unscoped installations: \discoverForUser()\ loaded ALL global installations (\github_installations), not scoped to the user's junction rows. This meant every user discovery checked every org's repos.
  2. Force bypass: The sweep used \ orce: true, bypassing the 1-hour dedup cache and ensuring every sweep tick ran full discovery for all users.
  3. Unbounded scale: 100 users x 50 installs x 20 repos = 100k+ API calls per sweep, exhausting GitHub rate limits for unrelated organizations.

Fix

Scoped installations (primary fix)

Changed \discoverForUser\ to query installations through the \github_installation_users\ junction table with a join to \github_installations, so only installations the user already has a relationship with are checked.

Sweep improvements

  • *Removed \ orce: true* from sweep events — respects the 1-hour dedup cache
  • Reduced batch from 100 to 20 users per tick
  • Skip recently-discovered users in sweep instead of force-processing them
  • *Return { swept, skipped }* for observability

Changes

  • \src/inngest/functions/maintainer-discover.ts: Scope installations via junction table join, remove force:true from sweep, reduce batch size
  • \src/inngest/functions/maintainer-discover.test.ts: Update tests for new query patterns, add tests for sweep dedup and batch limit

Test Plan

  • All 602 tests pass
  • New test: skips recently discovered users in sweep
  • New test: does not use force:true in sweep events
  • New test: only processes 20 users per sweep tick

…oder-s-OG-s#784)

The maintainer-discover sweep ran unbounded O(users x installs x repos)
GitHub API calls per cron tick, enabling cross-installation rate limit
exhaustion. The function loaded ALL global installations instead of
scoping to the user's junction rows, and used force:true which bypassed
the dedup cache.

Fixes:
- Scope discoverForUser installations to user's junction rows via
  github_installation_users join, eliminating cross-org API calls
- Remove force:true from sweep events, respecting the 1-hour dedup cache
- Reduce sweep batch from 100 to 20 users per tick
- Skip recently-discovered users in sweep instead of force-processing
@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

@namann5 is attempting to deploy a commit to the codersogs-3057's projects Team on Vercel.

A member of the Team first needs to authorize it.

@jakharmonika364 jakharmonika364 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Callers in process-membership-events.ts should resolve and pass that install's id (org membership -> look up by organization.login; repo collaborator -> the installation_repositories row already looked up at line 71–76) instead of only {userId, githubHandle}.

Comment thread src/inngest/functions/maintainer-discover.ts
Comment thread src/inngest/functions/maintainer-discover.ts
@jakharmonika364 jakharmonika364 added the Needs author reply Author need to reply label Jul 29, 2026
@namann5

namann5 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

@jakharmonika364 resolved -- 1) added installationId to DiscoverEvent type 2) target install fetched and included when installationId is passed (even if not yet in junction table) 3) processMembershipEvent resolves install by org login 4) processMemberEvent passes install_id from installation_repositories row

@namann5
namann5 requested a review from jakharmonika364 July 29, 2026 06:44
Comment thread src/inngest/functions/maintainer-discover.ts Outdated
Comment on lines +107 to +116
const installRows = targetInstall?.data
? [...knownInstalls, targetInstall.data].filter(dedupeById)
: knownInstalls.length > 0 || installationId
? knownInstalls
: ((
await sb
.from('github_installations')
.select('id, account_login, account_type')
.is('uninstalled_at', null)
).data ?? []);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still leaves a gap: src/app/actions/profile.ts's sign-in trigger (void inngest.send({ name: 'maintainer/discover', data: { userId, githubHandle } }), not touched by this PR) never passes installationId - it can't, it's the catch-all path. For any user who already has ≥1 known install, that means installRows = knownInstalls forever, and a new org grant only surfaces if a membership/member webhook fires and isn't missed. That was previously sign-in's whole job per the comment above it ("including orgs where they were added as admin after a different teammate created the install"). Either scope this PR to also handle that caller, or update the JSDoc/comment to document that sign-in and sweep no longer discover new installs, only revalidate known ones - right now the code silently does the latter while the comments still promise the former.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call — I went with the documentation option. Pushed 8f62a4e: the JSDoc in maintainer-discover.ts now states sign-in + cron only revalidate known installs (webhook triggers pass installationId and pick up new grants; first-discovery falls back to a full scan), and the profile.ts sign-in comment was updated to match instead of promising cross-install discovery.

@namann5

namann5 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

@jakharmonika364 re: the sign-in gap — I went with documenting the behavior rather than expanding sign-in scope, in 8f62a4e:

  • maintainer-discover.ts JSDoc now states that webhook triggers (installation.created, membership.added, member.added) pass an installationId and pick up NEW grants, while sign-in and the daily cron only revalidate already-known installs, and that first-discovery (no known installs, no installationId) still falls back to a full scan.
  • profile.ts sign-in comment updated to match: it revalidates known installs; new grants come through the webhook paths.

All three webhook callers (process-installation-event.ts transferred path, both handlers in process-membership-events.ts) already resolve and pass installationId, so new grants are still discovered in real time there.

@jakharmonika364 jakharmonika364 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@jakharmonika364 jakharmonika364 added level:advanced Advanced level difficulty quality:clean Clean, well-structured contribution type:bug Bug fix gssoc:approved Approved by GSSOC admin SSoC26 Hard ECSoC26 ECSoC26-L3 Hard good-pr and removed Needs author reply Author need to reply labels Aug 1, 2026
@jakharmonika364
jakharmonika364 merged commit 2f70bf1 into Coder-s-OG-s:main Aug 1, 2026
2 of 3 checks passed
@ecsoc-sentinel ecsoc-sentinel Bot added ECSoC26-L3 Hard and removed ECSoC26-L3 Hard labels Aug 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ECSoC26-L3 Hard ECSoC26 good-pr gssoc:approved Approved by GSSOC admin Hard level:advanced Advanced level difficulty quality:clean Clean, well-structured contribution SSoC26 type:bug Bug fix

Projects

None yet

2 participants