antiwork/gumroad · error · ResponseError

Sorry, failed to duplicate '${productName}': ${json.error_me

Error message

Sorry, failed to duplicate '${productName}': ${json.error_message}

What it means

ResponseError thrown at product_dashboard.ts:68 after pollForProductDuplication GETs Routes.product_duplicate_path(permalink) every 2 seconds and the status comes back 'product_duplication_failed': the asynchronous copy job failed server-side. The message embeds the product name plus json.error_message when present, otherwise the 'Please try again.' fallback. Note the poll loop has no attempt cap — it recurses every 2s indefinitely while status stays 'processing'.

Source

Thrown at app/javascript/data/product_dashboard.ts:68

  const json = typia.assert<{ success: true } | { success: false; error_message: string }>(await response.json());
  if (!json.success) throw new ResponseError(json.error_message);

  await pollForProductDuplication(permalink, productName);
}

async function pollForProductDuplication(permalink: string, productName: string) {
  const response = await request({
    url: Routes.product_duplicate_path(permalink),
    method: "GET",
    accept: "json",
  });

  const json = typia.assert<{ status: string; error_message?: string }>(await response.json());
  if (json.status === "product_duplication_failed") {
    const reason = json.error_message
      ? `Sorry, failed to duplicate '${productName}': ${json.error_message}`
      : `Sorry, failed to duplicate '${productName}'. Please try again.`;
    throw new ResponseError(reason);
  } else if (json.status === "product_duplicated") {
    return { status: json.status };
  }
  await new Promise((resolve) => setTimeout(resolve, 2000));
  return pollForProductDuplication(permalink, productName);
}

View on GitHub (pinned to afeacbd394)

Solutions

  1. Read the thrown reason — when error_message is present it names the actual job failure; then check server logs for the duplication worker for the full backtrace
  2. Retry duplication once (the usual fix for transient file-copy failures), via the same duplicateProduct entry point
  3. Verify object-storage credentials/bucket policies server-side if the message points at file copying
  4. Add a max-attempt cap to the 2s poll loop so a wedged 'processing' job surfaces as a timeout error instead of polling forever (see exampleFix)
  5. If failures cluster for one product, inspect that product's records (orphaned file blobs, oversized content) rather than blaming the job system

Example fix

// before
await new Promise((resolve) => setTimeout(resolve, 2000));
return pollForProductDuplication(permalink, productName);

// after — cap the poll so a wedged job fails loudly
async function pollForProductDuplication(permalink: string, productName: string, attempt = 0) {
  // ... existing status checks ...
  if (attempt >= 150) throw new ResponseError(`Sorry, duplicating '${productName}' timed out. Please try again.`);
  await new Promise((resolve) => setTimeout(resolve, 2000));
  return pollForProductDuplication(permalink, productName, attempt + 1);
}
Defensive patterns

Strategy: retry

Type guard

import { assertResponseError } from '$app/utils/request';
assertResponseError(e);

Try / catch

try {
  await pollForProductDuplication(permalink, productName);
} catch (e) {
  assertResponseError(e);
  const transient = /timed out|try again/i.test(e.message);
  if (transient && attempt === 0) return duplicateProduct(permalink, productName); // one retry
  showError(e.message); // includes the server's job failure reason when present
}

Prevention

When it happens

Trigger: The duplication job enqueued by the product_duplicates endpoint fails and the record's status flips to 'product_duplication_failed': file copy from object storage fails mid-job, a DB constraint trips while cloning records/variants/blobs, the job worker dies after the record was marked in-progress, or the job hits a bug cloning an unusual product shape.

Common situations: Large products (many files/variants) timing out in the copy job; S3 file-copy permission or bucket-policy drift in an environment; job queue retries exhausted after a transient DB issue; browser tab left open polling overnight if the job wedges in 'processing'; error_message arriving empty, leaving users with only the fallback wording.

Related errors


AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21). Data as JSON: /api/errors/f57a806e943848a6. Report an issue: GitHub.