antiwork/gumroad · error · ResponseError
Failed to unarchive product
Error message
Failed to unarchive product
What it means
ResponseError('Failed to unarchive product') thrown at product_dashboard.ts:38 when the DELETE to Routes.products_archived_path(permalink) answers { success: false, errors: [] } — the literal is the fallback after `json.errors[0] ||`, so the server refused the unarchive but its errors array came back empty and the true reason never reaches the user.
Source
Thrown at app/javascript/data/product_dashboard.ts:38
accept: "json",
data: { id: permalink },
});
const json = typia.assert<{ success: true } | { success: false; error: string }>(await response.json());
if (!json.success) throw new ResponseError(json.error || "Failed to archive product");
}
export async function unarchiveProduct(permalink: string) {
const response = await request({
url: Routes.products_archived_path(permalink),
method: "DELETE",
accept: "json",
});
const json = typia.assert<{ success: true; archived_products_count: number } | { success: false; errors: string[] }>(
await response.json(),
);
if (!json.success) throw new ResponseError(json.errors[0] || "Failed to unarchive product");
return json.archived_products_count;
}
export async function duplicateProduct(permalink: string, productName: string) {
const response = await request({
url: Routes.product_duplicates_path(),
method: "POST",
accept: "json",
data: { id: permalink },
});
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) {View on GitHub (pinned to afeacbd394)
Solutions
- Inspect the products/archived/:id DELETE response body in DevTools to confirm errors is empty and capture the status code
- Read server logs for the unarchive action to find which guard produced success:false
- Patch the endpoint so refusal payloads always carry a non-empty errors array (or a single error string)
- Add a server-side test asserting a failure response includes a reason, so the empty-field regression cannot return silently
Example fix
# server side — populate the refusal reason
# before
render json: { success: false, errors: [] }
# after
render json: { success: false, errors: [product.errors.full_messages.to_sentence.presence || 'Unable to unarchive product'] } Defensive patterns
Strategy: try-catch
Type guard
import { assertResponseError } from '$app/utils/request';
assertResponseError(e); Try / catch
try {
await unarchiveProduct(permalink);
} catch (e) {
assertResponseError(e);
if (e.message === 'Failed to unarchive product') logEmptyUnarchiveError(permalink); // empty errors[] bug
showError(e.message);
await refreshArchived();
} Prevention
- The exact fallback string signals an empty server errors array — capture permalink and status for the server team
- Keep list state server-authoritative: refetch archived products after every attempt
- Add endpoint tests that a failure response always includes a reason
When it happens
Trigger: The unarchive endpoint returns success:false with an empty errors array: an ActiveRecord/validation failure where errors weren't collected, a permissions guard that short-circuits without appending to errors, or the record state making unarchive a no-op that is still marked failed.
Common situations: Model refactor renames validations so the controller's hand-picked errors list no longer populates; unarchiving a product whose parent record (file, variant) was purged; concurrent unarchive from two dashboard tabs; every failure collapsing into the same generic string in user reports.
Related errors
- Failed to archive product
- json.message
- json.error_message
- responseData.error_message
- Sorry, failed to duplicate '${productName}': ${json.error_me
AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21).
Data as JSON: /api/errors/c9ebca0148df7c80.
Report an issue: GitHub.