Description
src/inngest/functions/issues-sweep.ts:
- Lines 26-35: selects all active installs with no cap.
- Line 24: runs on
cron: '0 */12 * * *' while the doc comment (line 14) claims "every 30 min".
- Lines 49-58: per install,
checkRateBudget, and when exhausted a step.sleepUntil(resetAt + 5s) that blocks the entire sweep for however long that install's reset is.
- Lines 62-272: wraps an install's entire processing — per-repo
octokit.repos.get + octokit.issues.listForRepo (lines 130, 165-171, 30 issues each), repo discovery/self-healing, and per-issue difficulty scoring — inside a single step.run('process-install-...'). A mid-loop failure retries the whole install, re-invoking every GitHub and LLM call.
- Lines 198-230: for every previously-unscored issue calls
scoreDifficulty with a mandatory llmFallback (src/lib/pipeline/score.ts:86-96 always invokes it when label confidence < 0.6) — one LLM call per issue, sequentially, every 12 hours.
- Lines 278-280: fans out
recommendations/build only at the very end — a late failure also loses the recommendation rebuild.
Expected Behavior
The sweep bounds its work per run, checkpoints per repo, respects budgets mid-loop, and limits LLM cost.
Actual Behavior
An install with N repos can trigger up to 30×N LLM calls per sweep (Groq/Gemini quota exhaustion), plus ~2 GitHub calls per repo. With hundreds/thousands of installs this is effectively unbounded cost and wall-clock time; the single step.sleepUntil for one exhausted install stalls every other install; the function has no concurrency config so overlapping runs can occur; and because the whole install is one step, retries repeat paid LLM work. Result: recurring LLM budget burn, GitHub 403s, and cron runs that time out and restart without making progress.
Affected Files
src/inngest/functions/issues-sweep.ts (lines 14, 24, 26-35, 49-58, 62-272, 278-280) — Unbounded per-install loop in one step
src/lib/pipeline/score.ts (lines 86-96) — Mandatory LLM fallback per issue
src/inngest/functions/pr-backfill.ts (lines 101-117) — Contrast: correct per-repo step structure
Proposed Fix
Mirror pr-backfill.ts's structure: one step.run per repo, budget re-checked before each repo, per-repo cursors, a global cap on repos/issues per sweep, concurrency: { key: 'issues-sweep', limit: 1 }, and either queue LLM scoring or reuse cached scores — persist scored_at and skip issues scored in the last 24h.
Description
src/inngest/functions/issues-sweep.ts:cron: '0 */12 * * *'while the doc comment (line 14) claims "every 30 min".checkRateBudget, and when exhausted astep.sleepUntil(resetAt + 5s)that blocks the entire sweep for however long that install's reset is.octokit.repos.get+octokit.issues.listForRepo(lines 130, 165-171, 30 issues each), repo discovery/self-healing, and per-issue difficulty scoring — inside a singlestep.run('process-install-...'). A mid-loop failure retries the whole install, re-invoking every GitHub and LLM call.scoreDifficultywith a mandatoryllmFallback(src/lib/pipeline/score.ts:86-96always invokes it when label confidence < 0.6) — one LLM call per issue, sequentially, every 12 hours.recommendations/buildonly at the very end — a late failure also loses the recommendation rebuild.Expected Behavior
The sweep bounds its work per run, checkpoints per repo, respects budgets mid-loop, and limits LLM cost.
Actual Behavior
An install with N repos can trigger up to 30×N LLM calls per sweep (Groq/Gemini quota exhaustion), plus ~2 GitHub calls per repo. With hundreds/thousands of installs this is effectively unbounded cost and wall-clock time; the single
step.sleepUntilfor one exhausted install stalls every other install; the function has noconcurrencyconfig so overlapping runs can occur; and because the whole install is one step, retries repeat paid LLM work. Result: recurring LLM budget burn, GitHub 403s, and cron runs that time out and restart without making progress.Affected Files
src/inngest/functions/issues-sweep.ts(lines 14, 24, 26-35, 49-58, 62-272, 278-280) — Unbounded per-install loop in one stepsrc/lib/pipeline/score.ts(lines 86-96) — Mandatory LLM fallback per issuesrc/inngest/functions/pr-backfill.ts(lines 101-117) — Contrast: correct per-repo step structureProposed Fix
Mirror
pr-backfill.ts's structure: onestep.runper repo, budget re-checked before each repo, per-repo cursors, a global cap on repos/issues per sweep,concurrency: { key: 'issues-sweep', limit: 1 }, and either queue LLM scoring or reuse cached scores — persistscored_atand skip issues scored in the last 24h.