{"record":{"id":"29bc63375ccf13ee","repo":"JuliusBrussee/caveman","slug":"artifactid-is-required","errorCode":null,"errorMessage":"artifactId is required","messagePattern":"artifactId is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/sdk/typescript/src/index.ts","lineNumber":2077,"sourceCode":"  try {\n    if (typeof response.text === \"function\") {\n      decoded = JSON.parse(await response.text());\n    } else if (typeof response.json === \"function\") {\n      decoded = await response.json();\n    } else {\n      throw new Error(\"missing response decoder\");\n    }\n  } catch {\n    throw new CaveRequestError(response.status, path, \"cave response was not valid JSON\");\n  }\n  if (decoded === null || typeof decoded !== \"object\" || Array.isArray(decoded)) {\n    throw new CaveRequestError(response.status, path, \"cave response must be a JSON object\");\n  }\n  return decoded as Record<string, unknown>;\n}\n\nasync function artifactGet(cave: Cave, artifactId: string, workflow: string, trace: TraceContext): Promise<unknown> {\n  if (typeof artifactId !== \"string\" || artifactId.trim() === \"\") throw new Error(\"artifactId is required\");\n  const path = `/sdk/v1/artifacts/${encodeURIComponent(artifactId)}`;\n  const response = await caveFetch(cave, `${cave.options.baseURL}${path}`, {\n    method: \"GET\",\n    headers: headers(cave, workflow, undefined, undefined, trace)\n  });\n  if (!response.ok) throw new CaveRequestError(response.status, path, `cave request failed (${response.status})`);\n  try {\n    return JSON.parse(await response.text());\n  } catch {\n    throw new CaveRequestError(response.status, path, \"cave artifact was not valid JSON\");\n  }\n}\n\nfunction headers(cave: Cave, workflow: string, upstreamKey?: string, hint?: Record<string, unknown>, trace?: TraceContext) {\n  return {\n    \"content-type\": \"application/json\",\n    authorization: `Bearer ${cave.options.apiKey}`,\n    \"x-cave-agent\": cave.options.agent,","sourceCodeStart":2059,"sourceCodeEnd":2095,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/sdk/typescript/src/index.ts#L2059-L2095","documentation":"Thrown by artifactGet() in the TypeScript SDK when the artifactId argument is not a string or is empty/whitespace-only. It is a local client-side guard fired before any network request to GET /sdk/v1/artifacts/{id}. The SDK refuses to build a URL from an unusable identifier.","triggerScenarios":"Calling CaveTrace.artifacts.get(id) (which delegates to artifactGet) with undefined, null, a number, an array, or an empty/whitespace string — typically the result of destructuring an id from an object that never had one, or using a checkpoint source_ref instead of an artifact id.","commonSituations":"Reading an id from an API response that renamed the field; passing a handle from a failed artifacts.page() call; default parameters evaluating to undefined; ids from JSON.parse typed loosely as any.","solutions":["Pass a non-empty string artifact id exactly as returned by artifacts.page() / the gateway","If the id comes from untrusted data, trim and validate it before calling get()","Type the id as string at call sites so the compiler rejects undefined at build time"],"exampleFix":"// before\nconst art = await trace.artifacts.get(resp.artifact_id); // undefined if field absent\n\n// after\nconst id = resp.artifact_id;\nif (typeof id !== \"string\" || id.trim() === \"\") throw new Error(\"no artifact id in response\");\nconst art = await trace.artifacts.get(id);","handlingStrategy":"validation","validationCode":"function isArtifactId(v) {\n  return typeof v === \"string\" && v.trim() !== \"\";\n}\nif (!isArtifactId(id)) throw new TypeError(\"artifact id missing\");","typeGuard":"function isArtifactId(v) {\n  return typeof v === \"string\" && v.trim().length > 0;\n}","tryCatchPattern":null,"preventionTips":["Type artifact ids as string (not any) everywhere they flow","Validate ids at the boundary where they enter your code (API responses, queues) instead of at the SDK call"],"tags":["typescript","sdk","validation","artifacts"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}