{"record":{"id":"26ed832c0898905b","repo":"paperclipai/paperclip","slug":"invalid-name-json-err-instanceof-error-err","errorCode":null,"errorMessage":"Invalid ${name} JSON: ${err instanceof Error ? err.message : String(err)}","messagePattern":"Invalid (.+?) JSON: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/src/commands/client/approval.ts","lineNumber":258,"sourceCode":"      }),\n  );\n}\n\nfunction parseCsv(value: string | undefined): string[] | undefined {\n  if (!value) return undefined;\n  const rows = value.split(\",\").map((v) => v.trim()).filter(Boolean);\n  return rows.length > 0 ? rows : undefined;\n}\n\nfunction parseJsonObject(value: string, name: string): Record<string, unknown> {\n  try {\n    const parsed = JSON.parse(value) as unknown;\n    if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n      throw new Error(`${name} must be a JSON object`);\n    }\n    return parsed as Record<string, unknown>;\n  } catch (err) {\n    throw new Error(`Invalid ${name} JSON: ${err instanceof Error ? err.message : String(err)}`);\n  }\n}\n","sourceCodeStart":240,"sourceCodeEnd":261,"githubUrl":"https://github.com/paperclipai/paperclip/blob/120ae5428fa29bee300bcf806491cd4d965fbb7c/cli/src/commands/client/approval.ts#L240-L261","documentation":"HTTP 404 with body {\"error\":\"Provider vault not found\"} from DELETE /api/secret-provider-configs/:id, second guard (secrets.ts:493). The preceding getAccessibleResource confirmed the provider config existed, but svc.removeProviderConfig(id) then returned null - the row was already gone by removal time (concurrent delete won the race). Board-only route.","triggerScenarios":"Two concurrent DELETE calls to the same vault config where the second passes the existence check but removes zero rows; a cleanup job and a manual operator deleting the same test vault simultaneously.","commonSituations":"Idempotency-unaware cleanup automation looping deletes; double-clicked delete buttons; provisioning scripts that remove default vaults while another pipeline recreates/removes them.","solutions":["Treat 404 on DELETE of a vault you already verified as success - the end state (vault gone) is achieved.","Make cleanup scripts idempotent: fetch the list, delete each ID once, swallow 404s.","Serialize destructive vault operations to avoid races with other admins.","If the vault unexpectedly disappears, audit the activity log (secret_provider_config.removed) to find which actor deleted it."],"exampleFix":"// before\nconst res = await api.delete(`/api/secret-provider-configs/${id}`);\nif (!res.ok) throw new Error(`delete failed: ${res.status}`);\n\n// after\nconst res = await api.delete(`/api/secret-provider-configs/${id}`);\nif (res.status === 404) {\n  logger.info(`vault ${id} already removed (race or earlier delete); success`);\n} else if (!res.ok) {\n  throw new Error(`delete failed: ${res.status}`);\n}","handlingStrategy":"fallback","validationCode":"async function listVaultIds(api: ApiClient): Promise<string[]> {\n  const res = await api.fetch('/api/secret-provider-configs');\n  if (!res.ok) throw new Error(`cannot list vaults: ${res.status}`);\n  const list = await res.json();\n  return (Array.isArray(list) ? list : list.items ?? []).map((v: { id: string }) => v.id);\n}","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  await api.delete(`/api/secret-provider-configs/${id}`);\n} catch (err) {\n  if (err instanceof ApiError && err.status === 404 && isVaultNotFound(err.body)) {\n    return { deleted: true, alreadyGone: true }; // idempotent success\n  }\n  throw err;\n}","preventionTips":["Code DELETE 404 as success in all vault cleanup automation.","Drive deletions from a fresh list call each cycle, deleting only present IDs.","Coordinate with other admins so deletes and edits do not interleave.","After deletes, re-read the vault list to confirm final state instead of assuming."],"tags":["http-404","express","secrets","provider-config","delete","idempotency","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"}