Skip to content

[BUG] issues-sweep Is an Unbounded, Non-Replayable, LLM-Burning Cron That Scales Linearly with Every Install #860

Description

@ionfwsrijan

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions