{"record":{"id":"e10065a9ca617247","repo":"paperclipai/paperclip","slug":"body-error-trace-download-failed-respons","errorCode":null,"errorMessage":"${body?.error ?? `Trace download failed: ${response.status}`}","messagePattern":"\\$\\{body\\?\\.error \\?\\? `Trace download failed: \\$\\{response\\.status\\}`\\}","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ui/src/api/heartbeats.ts","lineNumber":180,"sourceCode":"      {},\n    ),\n  deleteProviderTrace: (runId: string) =>\n    api.delete<{ ok: true }>(`/heartbeat-runs/${runId}/provider-trace`),\n  downloadProviderTrace: async (runId: string): Promise<Blob> => {\n    const response = await fetch(\n      `/api/heartbeat-runs/${runId}/provider-trace/download`,\n      {\n        credentials: \"include\",\n        headers: { Accept: \"application/x-ndjson\" },\n      },\n    );\n    if (!response.ok) {\n      const body = (await response.json().catch(() => null)) as {\n        error?: string;\n      } | null;\n      const recovery = tenantSessionRecovery.recoverIfNeeded(response.status, body);\n      if (recovery) return recovery;\n      throw new Error(\n        body?.error ?? `Trace download failed: ${response.status}`,\n      );\n    }\n    return response.blob();\n  },\n  workspaceOperationLog: (\n    operationId: string,\n    offset = 0,\n    limitBytes = 256000,\n  ) =>\n    api.get<{\n      operationId: string;\n      store: string;\n      logRef: string;\n      content: string;\n      nextOffset?: number;\n    }>(\n      `/workspace-operations/${operationId}/log?offset=${encodeURIComponent(String(offset))}&limitBytes=${encodeURIComponent(String(limitBytes))}`,","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/ui/src/api/heartbeats.ts#L162-L198","documentation":"The heartbeats API trace-download helper throws this Error when fetching a heartbeat trace file returns non-OK. It prefers the server's `error` field from the JSON body and otherwise reports `Trace download failed: <status>`. Tenant session recovery is attempted before throwing.","triggerScenarios":"Trace blob endpoint returns 404 (trace file pruned or never written), 403 (wrong company scope), or 500 (storage failure); response body is non-JSON so body is null.","commonSituations":"Old heartbeat whose trace artifacts were garbage-collected, trace larger than storage limits causing server 500, agent key lacking access to the company that owns the run.","solutions":["Confirm the heartbeat run still exists and its trace was persisted","Check the download URL/id corresponds to the right company","Check server storage/logs for trace write failures","Retry transient 5xx with backoff"],"exampleFix":"// before\nthrow new Error(body?.error ?? `Trace download failed: ${response.status}`);\n// after\nif (response.status === 404) throw new Error('Trace artifact is no longer available.');\nthrow new Error(body?.error ?? `Trace download failed: ${response.status}`);","handlingStrategy":"try-catch","validationCode":"if (!heartbeat?.traceAvailable) throw new Error('No trace artifact for this run');","typeGuard":"function hasTraceError(b: unknown): b is { error: string } { return typeof b === 'object' && b !== null && typeof (b as any).error === 'string'; }","tryCatchPattern":"try { const blob = await heartbeatsApi.downloadTrace(id); } catch (e) { if (/Trace download failed: 404/.test(e.message)) showToast('Trace expired'); else if (/failed: 5/.test(e.message)) retryDownload(); else throw e; }","preventionTips":["Check trace availability metadata before offering the download link","Verify company scoping on the trace URL","Retry 5xx downloads with backoff","Alert on server-side trace persistence failures"],"tags":["http","download","heartbeat"],"backgroundTag":"http-error-response","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}