{"record":{"id":"e4745bf722f8c3ba","repo":"can1357/oh-my-pi","slug":"artifact-id-must-be-numeric-got-id","errorCode":null,"errorMessage":"artifact:// ID must be numeric, got: ${id}","messagePattern":"artifact:// ID must be numeric, got: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/internal-urls/artifact-protocol.ts","lineNumber":33,"sourceCode":"import { 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);\n\t\tif (pinnedIndex >= 0) dirs.splice(pinnedIndex, 1);\n\t\tdirs.unshift(pinnedDir);\n\t}","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/internal-urls/artifact-protocol.ts#L15-L51","documentation":"parseArtifactId (packages/coding-agent/src/internal-urls/artifact-protocol.ts:33) throws when the artifact:// host is present but fails the /^\\d+$/ numeric check. Artifact IDs are monotonically increasing counters, so only digits are accepted; named or alphanumeric hosts belong to other schemes like agent://<name>.","triggerScenarios":"Calling artifact://reviewer_0, artifact://abc, artifact://3a, or artifact://-1 — any non-numeric host passed to resolveArtifactFile via ArtifactProtocolHandler.resolve.","commonSituations":"Confusing schemes: using artifact:// with an agent output ID like reviewer_0 (should be agent://reviewer_0); URL-encoded or percent-containing hosts; an ID copied with a suffix (e.g. '3.txt'); negative or whitespace-padded numbers.","solutions":["Use a purely numeric ID: artifact://<digits>.","If the target is a named agent output (e.g. reviewer_0), switch to the agent:// scheme: agent://reviewer_0.","Strip file extensions/whitespace from the ID before building the URL.","Check completion output (available IDs are numeric strings) to pick a valid ID."],"exampleFix":"// before\nconst url = `artifact://${outputId}`; // outputId = \"reviewer_0\"\n// after\nconst scheme = /^\\d+$/.test(outputId) ? \"artifact\" : \"agent\";\nconst url = `${scheme}://${outputId}`;","handlingStrategy":"validation","validationCode":"if (!/^\\d+$/.test(id)) {\n  throw new Error(`artifact:// requires a numeric ID, got: ${id}`);\n}\nconst url = new URL(`artifact://${id}`);","typeGuard":"function isNumericArtifactId(id: string): boolean {\n  return /^\\d+$/.test(id);\n}","tryCatchPattern":"try {\n  const res = await resolveUrl(new URL(`artifact://${id}`));\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"artifact:// ID must be numeric\")) {\n    // route named IDs to agent:// instead\n    const res2 = await resolveUrl(new URL(`agent://${id}`));\n  } else throw err;\n}","preventionTips":["Use artifact:// only for numeric counter IDs; named outputs go through agent://.","Regex-check the ID against /^\\d+$/ before building the URL.","Strip extensions, whitespace, and sign characters from IDs taken from file names or user input.","Use URL completion (ArtifactProtocolHandler.complete) to source valid IDs."],"tags":["url-validation","internal-urls","artifact-protocol","format-validation"],"backgroundTag":"invalid-url-format","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}