Skip to content

fix(web): rate limit transcription and AI generation retries - #4168

Merged
richiemcilroy merged 17 commits into
mainfrom
fix/processing-retry-limit
Nov 13, 2026
Merged

richiemcilroy merged 17 commits into
mainfrom
fix/processing-retry-limit

Conversation

@richiemcilroy

@richiemcilroy richiemcilroy commented Nov 13, 2026 •

Copy link
Copy Markdown
Member

The agent API's processCap with retry: true, the share page's AI retry (POST /api/videos/[videoId]/retry-ai) and its transcription retry (POST /api/videos/[videoId]/retry-transcription) each start paid transcription or AI generation work. None of them limited how often a video owner could retry, so retries could be repeated without bound.

Fix

  • processingRetryLimited (apps/web/lib/processing-retry-limit.ts) is one per-user limit shared by all three paths: rl_processing_retry, 30 per hour.
  • processCap decides whether a request is a rerun from the video's current state (chargesProcessingRerun and isAiGenerationRerun in apps/web/lib/generate-ai.ts; the second mirrors the skip rules in startAiGeneration).
    • Counted: retry: true on a transcript in ERROR; retry: true on AI generation that failed or was skipped; and AI generation on a result left incomplete (the legacy fallback summary, or no chapters), which startAiGeneration reruns even without retry. AI reruns are counted only once the transcript is complete, because the mutation refuses AI work before that.
    • The charge runs in the mutation's existing admit hook, which only runs for an idempotency key with no saved result, before the transaction. A refused request therefore stores no idempotency record, and a request the mutation would refuse as a conflict (a failed AI run without retry) is not charged.
    • A replay of a saved request is never charged, and it no longer starts AI generation again on a video whose last run failed. Before, replaying an earlier key after a failure started a new run. Replays of work that is queued, running or done behave as before.
    • The refusal is the agent API's existing RATE_LIMITED error, with retryAfterMs set to one hour to match the window. First-time processing is unchanged.
  • The two share page routes check the same limit after their existing owner, state and plan checks, before any status reset or AI start. A refusal is a 429 with { "error": "Too many processing retries. Try again in an hour." }, which both buttons already show.

Limit chosen

30 retries per user per hour, the same budget as video edit saves, which also re-run AI work. Retries are manual, one button press or one confirmed agent request each, and a failed job is usually retried once or twice. Because the budget is shared, the agent API and the share page cannot be combined to go past it.

Why normal use is unaffected

First-time processing, and processing of a video whose AI result is already complete, never counts. Each retry path is a manual action: both share page routes are called only from their retry buttons, and the automatic transcription retry in get-status.ts does not go through these routes. A person retrying a failed job a few times stays far below 30 in an hour.

Checked against production data before keeping 30 (the same check that raised the space limit on #4167): agent process_cap reruns are not in use today, so the agent path cannot block anyone now. Owners who could want many share page retries in one hour are those with many failed jobs on one day. Almost every owner stays well under 30 failed videos on any day; the few days above that were server-side failures, which are re-driven server-side rather than retried by hand. Even then a refusal is a 429 that clears within the hour and changes no video state, and the agent error is retryable with retryAfterMs.

Tests

  • processing-retry-limit.test.ts:
    • 30 retries pass and the 31st is refused, and another user is unaffected;
    • both share page routes still work for the owner;
    • past the limit they answer 429 with the message, without starting AI generation or resetting the transcription status;
    • the two routes share one budget;
    • a source check confirms processCap computes the charge after the AI entitlement check, charges inside admit with a one-hour rateLimited, and starts AI generation on a replay only when it would not be a rerun.
  • ai-generation-rerun.test.ts:
    • first runs, queued or running work, and finished results are not reruns, while ERROR, SKIPPED, the legacy fallback summary and a result without chapters are;
    • the charge rule covers first-time processing (never charged), transcript and AI retries (charged), conflict-bound requests (not charged), incomplete results (charged), and AI work still waiting on transcription for either target (not charged).
  • agent-api-handler.test.ts and agent-api-contract.test.ts pass unchanged.
  • tsc --noEmit for apps/web: no new errors.
  • __tests__/integration/processing-retry-database.test.ts drives the real v1 POST handler (processCap) and the real share page routes against a real migrated MySQL database. Only the session lookup, the transcription provider call and the AI workflow start are stubbed; the idempotency store, the rerun charge, the rate limiter and startAiGeneration's own state checks run for real. It skips unless CAP_INFRA_TEST_DATABASE_URL points at a local cap_infra_* database, so CI is unaffected. Run locally against a freshly migrated scratch database:
    • 35 fresh videos processed through the agent API with target: "all" in a row: every one returns 200, starts AI generation once and moves to QUEUED; nothing is charged, so none is refused. A transcript-only request on an unprocessed video starts transcription.
    • One retry of a failed transcription and one retry of a failed AI run, through the agent API and through each share page button: all 200, the transcription status resets and AI moves to QUEUED.
    • 15 share page retries plus 15 agent retries pass; the 31st on either path is refused with 429 and leaves the video in ERROR. Another owner is unaffected, and fresh processing for the limited owner still works.
    • Replaying a saved retry: true key after the run failed again returns 200 without starting AI.
    • With route.ts, retry-ai, retry-transcription and generate-ai.ts from main and the same test, the three normal-use tests pass unchanged (so normal calls behave exactly as on main), and the limit and replay tests fail.
  • Whole apps/web unit suite (npx vitest run, local Node 26): this branch 9558 passed, 17 failed, 243 skipped; main at 18d84db 9541 passed, 17 failed, 238 skipped. The 17 failures are the same tests in the same 8 files on both (local Node 26 and environment noise). The difference is the new unit tests and the 5 database tests skipped without their environment.

Merge with main

lib/rate-limit.ts conflicted with main's rl_organization_create and rl_space_create. Both sides are kept; against main the file only adds PROCESSING_RETRY and its fallback (30 per hour). No existing export or limit changes. rateLimited's new retryAfterMs parameter defaults to the old 60 s, so its other callers are unchanged.

Rollout

Optional: add a Vercel Firewall rate limit rule rl_processing_retry (30 per hour, keyed). Until it exists, the in-process fallback applies the same number on each instance.

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; no new actionable issues were found.

Summary

Adds a shared per-user budget of 30 processing retries per hour across the agent API and both share-page retry routes.

  • Owners share one hourly budget across processing retry paths.
  • The agent API counts AI work only when it starts a rerun.

All three supplied prior findings are addressed: failed AI replays are blocked, the agent reports a one-hour wait, and AI requests waiting for transcription do not spend retry attempts. The prior threads were unnumbered, so they have no entries in previousFindings.

Reviews (8) · Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." · Reviewed by Greptile

@vercel

vercel Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
cap-status Ignored Ignored Preview Nov 13, 2026 1:49pm UTC

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Capy found 1 potential issue (1 medium).

Open in Capy

Comment thread apps/web/app/api/v1/[...route]/route.ts Resolved
Comment thread apps/web/app/api/v1/[...route]/route.ts Resolved
Comment thread apps/web/app/api/v1/[...route]/route.ts Resolved
@sandchest-com

sandchest-com Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

Sandchest · Sandchest couldn't finish reviewing 02ece69. Comment /sandchest review to try again; nothing was charged.

Details · earlier results on this pull request no longer apply to the latest commit.

@vercel
vercel Bot temporarily deployed to Preview – cap-status November 13, 2026 05:41 Inactive
@richiemcilroy

Copy link
Copy Markdown
Member Author

hey @greptileai, please re-review the PR

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Capy found 1 potential issue (1 medium).

Open in Capy

Comment thread apps/web/app/api/v1/[...route]/route.ts Resolved
@vercel
vercel Bot temporarily deployed to Preview – cap-status November 13, 2026 05:45 Inactive
@richiemcilroy

Copy link
Copy Markdown
Member Author

hey @greptileai, please re-review the PR

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Capy found 1 potential issue (1 medium).

Open in Capy

Comment thread apps/web/app/api/v1/[...route]/route.ts Resolved
@vercel
vercel Bot temporarily deployed to Preview – cap-status November 13, 2026 05:50 Inactive
@richiemcilroy

Copy link
Copy Markdown
Member Author

hey @greptileai, please re-review the PR

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Capy found 1 potential issue (1 medium).

View 2 other findings in Capy.

Open in Capy

Comment thread apps/web/lib/generate-ai.ts Resolved
Comment thread apps/web/lib/generate-ai.ts Resolved
@vercel
vercel Bot temporarily deployed to Preview – cap-status November 13, 2026 06:05 Inactive
@richiemcilroy

Copy link
Copy Markdown
Member Author

hey @greptileai, please re-review the PR

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Capy found no issues.

Open in Capy

@richiemcilroy

Copy link
Copy Markdown
Member Author

@greptileai review

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Capy found no issues.

View 1 other finding in Capy.

Open in Capy

@vercel
vercel Bot temporarily deployed to Preview – cap-status November 13, 2026 13:31 Inactive

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Capy found no issues.

View 1 other finding in Capy.

Open in Capy

@richiemcilroy

Copy link
Copy Markdown
Member Author

@greptileai review

# Conflicts:
#	apps/web/lib/rate-limit.ts
@vercel
vercel Bot temporarily deployed to Preview – cap-status November 13, 2026 13:45 Inactive
@richiemcilroy

Copy link
Copy Markdown
Member Author

@greptileai review

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Capy found no issues.

View 1 other finding in Capy.

Open in Capy

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Capy found no issues.

Open in Capy

@richiemcilroy
richiemcilroy merged commit e7e04cf into main Nov 13, 2026
38 of 39 checks passed

This branch was successfully deployed

1 active and 1 inactive (outdated) deployments
Preview – cap-web — 02ece694 Deployed Nov 13, 2026 by vercel[bot]
Preview – cap-status — 1418b277 Deployed Nov 13, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant