{"record":{"id":"c0c381d19b02ecc8","repo":"paperclipai/paperclip","slug":"request-failed-with-status-response-status","errorCode":null,"errorMessage":"Request failed with status ${response.status}","messagePattern":"Request failed with status (.+?)","errorType":"exception","errorClass":"ApiRequestError","httpStatus":null,"severity":"error","filePath":"cli/src/commands/client/asset.ts","lineNumber":130,"sourceCode":"async function downloadAsset(apiBase: string, apiKey: string | undefined, assetId: string): Promise<Buffer> {\n  const response = await fetch(buildApiUrl(apiBase, apiPath`/api/assets/${assetId}/content`), {\n    headers: apiKey ? { authorization: `Bearer ${apiKey}` } : undefined,\n  });\n  if (!response.ok) {\n    await parseFetchResponse(response);\n  }\n  return Buffer.from(await response.arrayBuffer());\n}\n\nasync function parseFetchResponse(response: Response): Promise<unknown> {\n  const text = await response.text();\n  const parsed = text.trim() ? safeJson(text) : null;\n  if (!response.ok) {\n    const message =\n      typeof parsed === \"object\" && parsed !== null && \"error\" in parsed && typeof parsed.error === \"string\"\n        ? parsed.error\n        : `Request failed with status ${response.status}`;\n    throw new ApiRequestError(response.status, message, undefined, parsed);\n  }\n  return parsed;\n}\n\nfunction buildApiUrl(apiBase: string, path: string): string {\n  const url = new URL(apiBase);\n  url.pathname = `${url.pathname.replace(/\\/+$/, \"\")}${path.startsWith(\"/\") ? path : `/${path}`}`;\n  return url.toString();\n}\n\nfunction safeJson(text: string): unknown {\n  try {\n    return JSON.parse(text);\n  } catch {\n    return text;\n  }\n}\n","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/paperclipai/paperclip/blob/120ae5428fa29bee300bcf806491cd4d965fbb7c/cli/src/commands/client/asset.ts#L112-L148","documentation":"HTTP 404 with body {\"error\":\"Provider vault not found\"} from POST /api/secret-provider-configs/:id/health, second guard (secrets.ts:551). The vault passed the earlier getAccessibleResource existence/tenant check, but svc.checkProviderConfigHealth(id) returned null - the provider config row was no longer readable when the health probe ran, almost always because a concurrent DELETE removed it in between. Board-only route; note this 404 is about the config record, not the external provider being unhealthy.","triggerScenarios":"Health-checking a vault while another admin deletes it; periodic health pollers hitting vaults mid-teardown; running health sweeps during provider migration where old configs are removed as new ones are created.","commonSituations":"Monitoring dashboards polling all vaults on an interval racing config cleanup; smoke tests after deploy referencing a vault that decommissioning removed.","solutions":["On 404, re-check GET /api/secret-provider-configs/:id; if also 404, drop the vault from the health-check roster.","Pause or debounce health polls during planned provider-config maintenance windows.","Distinguish this 404 (config record gone) from a real provider connectivity failure (route returns health payload with error details) before alerting.","Keep the monitored vault list derived from a fresh list call each cycle instead of a static config."],"exampleFix":"// before\nconst health = await api.post(`/api/secret-provider-configs/${id}/health`, {});\n\n// after\nconst vault = await api.get(`/api/secret-provider-configs/${id}`);\nif (!vault) {\n  monitoredVaultIds.delete(id); // vault removed; stop probing\n  return null;\n}\nconst health = await api.post(`/api/secret-provider-configs/${id}/health`, {});","handlingStrategy":"validation","validationCode":"async function healthCheckRoster(api: ApiClient): Promise<string[]> {\n  const res = await api.fetch('/api/secret-provider-configs');\n  const list = await res.json();\n  return (Array.isArray(list) ? list : list.items ?? []).map((v: { id: string }) => v.id);\n}\n// probe only IDs returned by healthCheckRoster() this cycle","typeGuard":"function isApiErrorBody(body: unknown): body is { error: string } {\n  return typeof body === 'object' && body !== null &&\n    typeof (body as Record<string, unknown>).error === 'string';\n}\nconst isVaultNotFound = (b: unknown): boolean => isApiErrorBody(b) && b.error === 'Provider vault not found';","tryCatchPattern":"try {\n  const health = await api.post(`/api/secret-provider-configs/${id}/health`, {});\n  return health;\n} catch (err) {\n  if (err instanceof ApiError && err.status === 404 && isVaultNotFound(err.body)) {\n    dropFromRoster(id); // vault deleted mid-cycle; stop probing\n    return null;\n  }\n  throw err;\n}","preventionTips":["Rebuild the monitored vault list from the API every polling cycle.","Do not conflate this 404 with provider unhealthiness - it means the config record is gone.","Pause health sweeps during vault maintenance windows to avoid noise.","Auto-retire vault IDs from monitoring after a 404; they never reappear with the same ID."],"tags":["http-404","express","secrets","provider-config","health-check","race-condition","paperclip"],"backgroundTag":"http-404-resource-not-found","analyzedSha":"120ae5428fa29bee300bcf806491cd4d965fbb7c","analyzedAt":"2026-08-18T22:49:45.177Z","contentChangedAt":"2026-08-18T22:49:45.177Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}