{"record":{"id":"124c613d05b3fd9e","repo":"can1357/oh-my-pi","slug":"artifact-url-requires-a-numeric-id-artifact","errorCode":null,"errorMessage":"artifact:// URL requires a numeric ID: artifact://0","messagePattern":"artifact:// URL requires a numeric ID: artifact://0","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/internal-urls/artifact-protocol.ts","lineNumber":30,"sourceCode":"import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { isEnoent } from \"@oh-my-pi/pi-utils\";\nimport { artifactsDirsFromRegistry } from \"./registry-helpers\";\nimport type { InternalResource, InternalUrl, ProtocolHandler, ResolveContext, UrlCompletion } from \"./types\";\n\nconst MAX_INLINE_ARTIFACT_BYTES = 8 * 1024 * 1024;\n\n/** Filesystem location for a session artifact, resolved without materializing its content. */\nexport interface ResolvedArtifactFile {\n\tid: string;\n\tpath: string;\n\tsize: number;\n}\n\nfunction parseArtifactId(url: InternalUrl): string {\n\tconst id = url.rawHost || url.hostname;\n\tif (!id) {\n\t\tthrow new Error(\"artifact:// URL requires a numeric ID: artifact://0\");\n\t}\n\tif (!/^\\d+$/.test(id)) {\n\t\tthrow new Error(`artifact:// ID must be numeric, got: ${id}`);\n\t}\n\treturn id;\n}\n\n/** Resolve an `artifact://` URL to its backing file without reading artifact bytes. */\nexport async function resolveArtifactFile(url: InternalUrl, context?: ResolveContext): Promise<ResolvedArtifactFile> {\n\tconst id = parseArtifactId(url);\n\n\t// Artifact ids are per-session counters; in multi-session hosts the same\n\t// id exists in several dirs. Pin resolution to the calling session's\n\t// artifacts dir first so `artifact://3` means *this* session's #3.\n\tconst dirs = artifactsDirsFromRegistry();\n\tconst pinnedDir = context?.localProtocolOptions?.getArtifactsDir?.() ?? null;\n\tif (pinnedDir) {\n\t\tconst pinnedIndex = dirs.indexOf(pinnedDir);","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/internal-urls/artifact-protocol.ts#L12-L48","documentation":"parseArtifactId (packages/coding-agent/src/internal-urls/artifact-protocol.ts:30) throws when an artifact:// URL has an empty host, meaning no artifact ID was supplied. Artifact IDs are per-session numeric counters (e.g. artifact://0, artifact://3), so an ID is mandatory. The message embeds the offending URL form for clarity.","triggerScenarios":"Resolving the bare URL artifact:// (no host), e.g. new URL(\"artifact://\") or a tool expanding an empty/undefined ID into the scheme; also artifact:/// (empty host with a leading slash path).","commonSituations":"Template interpolation with an undefined/null variable: `artifact://${id}` with id undefined; a script stripping the ID when slicing a longer URL string; an LLM emitting artifact:// without an ID in a tool call.","solutions":["Supply the numeric artifact ID in the URL host: artifact://0, artifact://3, etc.","List available IDs first (ArtifactProtocolHandler.complete, or directory listing of the session artifacts dir with files named <id>.<ext>) and pick a valid one.","Fix the interpolation site so the ID variable is defined before building the URL.","If you meant file content rather than an artifact, use a different scheme (e.g. file:// or agent://)."],"exampleFix":"// before\nconst url = `artifact://${id}`; // id === undefined -> artifact://\n// after\nif (id === undefined || id === null) throw new Error(\"artifact ID required\");\nconst url = `artifact://${id}`;","handlingStrategy":"validation","validationCode":"if (!id || typeof id !== \"string\" || id.trim() === \"\") {\n  throw new Error(\"artifact URL requires a numeric ID\");\n}\nconst url = new URL(`artifact://${id}`);","typeGuard":"function hasArtifactId(id: string | undefined | null): id is string {\n  return typeof id === \"string\" && id.length > 0;\n}","tryCatchPattern":"try {\n  const res = await resolveUrl(new URL(`artifact://${id}`));\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"requires a numeric ID\")) {\n    // id was empty; prompt user or list available IDs via completion\n  } else throw err;\n}","preventionTips":["Never interpolate an ID variable into artifact:// without a defined check first.","Validate ID presence and numeric shape at the string level before URL construction.","List available artifact IDs before referencing one.","Keep the scheme/ID pairing explicit in tool-call templates for agents."],"tags":["url-validation","internal-urls","artifact-protocol","missing-argument"],"backgroundTag":"missing-url-parameter","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}