{"record":{"id":"3396926086893f47","repo":"tinyhumansai/openhuman","slug":"missing-artifact-id","errorCode":"MISSING_ARTIFACT_ID","errorMessage":"artifact id missing","messagePattern":"artifact id missing","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/services/artifactDownloadService.ts","lineNumber":191,"sourceCode":"    return { ok: true, artifacts };\n  } catch (err) {\n    const reason = err instanceof Error ? err.message : String(err);\n    return { ok: false, artifacts: [], error: reason };\n  }\n}\n\n/**\n * Resolve an artifact's absolute on-disk path (via `ai_get_artifact`)\n * and build the suggested filename. Shared by the Save-As and Downloads\n * export paths so both apply identical title/extension handling.\n */\nasync function resolveArtifactForExport(\n  artifactId: string,\n  fallbackTitle: string,\n  extension: string\n): Promise<ResolveExportResult> {\n  if (!artifactId.trim()) {\n    return { ok: false, code: 'MISSING_ARTIFACT_ID', error: 'artifact id missing' };\n  }\n\n  let resolved: AiGetArtifactData;\n  try {\n    const raw = await callCoreRpc<AiGetArtifactData>({\n      method: 'openhuman.ai_get_artifact',\n      params: { artifact_id: artifactId },\n    });\n    resolved = raw ?? {};\n  } catch (err) {\n    const reason = err instanceof Error ? err.message : String(err);\n    return { ok: false, code: 'RESOLVE_FAILED', error: reason };\n  }\n\n  const sourcePath = resolved.absolute_path;\n  if (!sourcePath) {\n    return {\n      ok: false,","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/app/src/services/artifactDownloadService.ts#L173-L209","documentation":"resolveArtifactForExport trims the artifactId argument; if it is empty or whitespace-only it returns (does NOT throw) a result object { ok: false, code: 'MISSING_ARTIFACT_ID', error: 'artifact id missing' }. Callers of downloadArtifact / the Save-As path must branch on ok, not use try/catch - this is a typed outcome, part of the DownloadArtifactOutcome/ResolveExportResult discriminated union.","triggerScenarios":"Calling downloadArtifact or the Save-As flow with an empty string, whitespace, or an undefined id stringified - e.g. an artifact row whose artifactId field is missing in the message payload that spawned the download button.","commonSituations":"UI rendering a download button for an artifact reference before the id arrives; artifact objects built from partial RPC data where artifact_id was never set; defensive default '' values flowing into the export path.","solutions":["Pass the real artifact id from the artifact record (the same id accepted by openhuman.ai_get_artifact)","Guard at the call site: hide/disable the download control when the id is blank","If the id should exist, trace why the owning payload lacks it - this error is always a caller-side data bug"],"exampleFix":"// before\ndownloadArtifact(artifact.artifactId ?? '', artifact.title, 'md');\n\n// after - do not offer the action without an id\nif (!artifact.artifactId?.trim()) return null; // or hide the button\nreturn <DownloadButton onClick={() => void downloadArtifact(artifact.artifactId!, artifact.title, 'md')} />;","handlingStrategy":"validation","validationCode":"// Guard before calling into the export path\nif (typeof artifactId !== 'string' || !artifactId.trim()) {\n  // do not open the dialog / do not call downloadArtifact\n  return;\n}","typeGuard":"// Narrow the outcome the service returns\nconst isMissingId = (o: DownloadArtifactOutcome): o is { ok: false; code: 'MISSING_ARTIFACT_ID'; error: string } =>\n  !o.ok && o.code === 'MISSING_ARTIFACT_ID';","tryCatchPattern":null,"preventionTips":["Never call downloadArtifact with a defaulted '' id - hide the affordance instead","Branch on the returned ok/code fields; MISSING_ARTIFACT_ID is not thrown, so try/catch will not see it","Treat this code as a caller-side data bug and fix the missing id at its source"],"tags":["artifacts","download","validation","result-object","desktop"],"backgroundTag":"missing-required-argument","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}