Repository navigation
fix(web): rate limit transcription and AI generation retries - #4168
Merged
Merged
Conversation
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
|
Sandchest · Sandchest couldn't finish reviewing Details · earlier results on this pull request no longer apply to the latest commit. |
Member
Author
|
hey @greptileai, please re-review the PR |
Member
Author
|
hey @greptileai, please re-review the PR |
Member
Author
|
hey @greptileai, please re-review the PR |
Member
Author
|
hey @greptileai, please re-review the PR |
added 2 commits
November 13, 2026 08:25
# Conflicts: # apps/web/lib/rate-limit.ts
Member
Author
|
@greptileai review |
Member
Author
|
@greptileai review |
# Conflicts: # apps/web/lib/rate-limit.ts
Member
Author
|
@greptileai review |
This branch was successfully deployed
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.
The agent API's
processCapwithretry: 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.processCapdecides whether a request is a rerun from the video's current state (chargesProcessingRerunandisAiGenerationReruninapps/web/lib/generate-ai.ts; the second mirrors the skip rules instartAiGeneration).retry: trueon a transcript inERROR;retry: trueon AI generation that failed or was skipped; and AI generation on a result left incomplete (the legacy fallback summary, or no chapters), whichstartAiGenerationreruns even withoutretry. AI reruns are counted only once the transcript is complete, because the mutation refuses AI work before that.admithook, 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 withoutretry) is not charged.RATE_LIMITEDerror, withretryAfterMsset to one hour to match the window. First-time processing is unchanged.{ "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.tsdoes 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_capreruns 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 withretryAfterMs.Tests
processing-retry-limit.test.ts:processCapcomputes the charge after the AI entitlement check, charges insideadmitwith a one-hourrateLimited, and starts AI generation on a replay only when it would not be a rerun.ai-generation-rerun.test.ts:ERROR,SKIPPED, the legacy fallback summary and a result without chapters are;agent-api-handler.test.tsandagent-api-contract.test.tspass unchanged.tsc --noEmitforapps/web: no new errors.__tests__/integration/processing-retry-database.test.tsdrives the real v1POSThandler (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 andstartAiGeneration's own state checks run for real. It skips unlessCAP_INFRA_TEST_DATABASE_URLpoints at a localcap_infra_*database, so CI is unaffected. Run locally against a freshly migrated scratch database:target: "all"in a row: every one returns 200, starts AI generation once and moves toQUEUED; nothing is charged, so none is refused. A transcript-only request on an unprocessed video starts transcription.QUEUED.ERROR. Another owner is unaffected, and fresh processing for the limited owner still works.retry: truekey after the run failed again returns 200 without starting AI.route.ts,retry-ai,retry-transcriptionandgenerate-ai.tsfrom 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.apps/webunit 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.tsconflicted with main'srl_organization_createandrl_space_create. Both sides are kept; against main the file only addsPROCESSING_RETRYand its fallback (30 per hour). No existing export or limit changes.rateLimited's newretryAfterMsparameter 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.

Confidence 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.
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