Skip to content

CSV Loom import: rate-limit tokens consumed before dedupe check, making retries import nothing #2010

Description

@corematters

Summary

Bulk Loom import via CSV cannot complete for large libraries. The per-user rate
limit empties partway through the CSV, all remaining rows fail, and — because
the rate-limit check runs before the already-imported check — retrying the same
CSV spends the entire rate-limit budget on rows that already succeeded and
imports zero new videos.

Environment

  • Cap Pro (web), CSV import at /dashboard (Loom importer)
  • Library: ~1,500 Loom videos, importing in CSVs under the 500-row cap
  • Google Drive storage integration enabled

Steps to reproduce

  1. Import a CSV with more rows than the rl_loom_import_per_user window allows
    (~60 in my case).
  2. First ~60 rows import; every subsequent row fails with
    "Too many Loom imports started. Please wait a few minutes, then try again."
  3. Wait, then re-submit the same CSV to pick up the failed rows.
  4. Result: {"success":false,"importedCount":0, ...} — every row fails with the
    same rate-limit error. No new videos import.

Cause (from source)

In apps/web/actions/loom.ts, the CSV loop calls isRateLimited() per row
before importLoomVideoForOwner(), and the duplicate check
("This Loom video has already been imported.") lives inside
importLoomVideoForOwner(). So on a retry, every already-imported row consumes
a rate-limit token at the gate and never reaches the dedupe short-circuit. For
a 500-row CSV where 60 succeeded, the retry burns the whole window on rows
1-60 plus rows that will fail anyway, and the user can never make progress
without manually trimming completed rows out of the CSV.

The loop also has no pacing or backoff, so a single large CSV always drains
the bucket and fails the tail in one shot.

Suggested fixes (any one of these unblocks large imports)

  1. Reorder the checks: run the dedupe lookup before isRateLimited() so
    already-imported rows return early without spending rate-limit budget.
    Retrying the same CSV then becomes safe and idempotent.
  2. Queue instead of fail: when the limit trips mid-CSV, pause and resume
    rather than failing the remaining rows (the job is already async
    server-side).
  3. Surface the budget: return remaining quota / retry-after in the response
    so the UI can tell users how many rows to submit and when to resume.

Actual response sample

{"success":false,"importedCount":0,"failedCount":10,"results":[
  {"rowNumber":112,"userEmail":"","success":false,
   "error":"Too many Loom imports started. Please wait a few minutes, then try again."},
  
],"error":"No Loom videos were imported."}

Happy to test a fix — I have ~1,440 videos still to import and an active Loom
account to reproduce against.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions