can1357/oh-my-pi · error

artifact:// ID must be numeric, got: ${id}

Error message

artifact:// ID must be numeric, got: ${id}

What it means

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>.

Source

Thrown at packages/coding-agent/src/internal-urls/artifact-protocol.ts:33

import { artifactsDirsFromRegistry } from "./registry-helpers";
import type { InternalResource, InternalUrl, ProtocolHandler, ResolveContext, UrlCompletion } from "./types";

const MAX_INLINE_ARTIFACT_BYTES = 8 * 1024 * 1024;

/** Filesystem location for a session artifact, resolved without materializing its content. */
export interface ResolvedArtifactFile {
	id: string;
	path: string;
	size: number;
}

function parseArtifactId(url: InternalUrl): string {
	const id = url.rawHost || url.hostname;
	if (!id) {
		throw new Error("artifact:// URL requires a numeric ID: artifact://0");
	}
	if (!/^\d+$/.test(id)) {
		throw new Error(`artifact:// ID must be numeric, got: ${id}`);
	}
	return id;
}

/** Resolve an `artifact://` URL to its backing file without reading artifact bytes. */
export async function resolveArtifactFile(url: InternalUrl, context?: ResolveContext): Promise<ResolvedArtifactFile> {
	const id = parseArtifactId(url);

	// Artifact ids are per-session counters; in multi-session hosts the same
	// id exists in several dirs. Pin resolution to the calling session's
	// artifacts dir first so `artifact://3` means *this* session's #3.
	const dirs = artifactsDirsFromRegistry();
	const pinnedDir = context?.localProtocolOptions?.getArtifactsDir?.() ?? null;
	if (pinnedDir) {
		const pinnedIndex = dirs.indexOf(pinnedDir);
		if (pinnedIndex >= 0) dirs.splice(pinnedIndex, 1);
		dirs.unshift(pinnedDir);
	}

View on GitHub (pinned to 9690622007)

Solutions

  1. Use a purely numeric ID: artifact://<digits>.
  2. If the target is a named agent output (e.g. reviewer_0), switch to the agent:// scheme: agent://reviewer_0.
  3. Strip file extensions/whitespace from the ID before building the URL.
  4. Check completion output (available IDs are numeric strings) to pick a valid ID.

Example fix

// before
const url = `artifact://${outputId}`; // outputId = "reviewer_0"
// after
const scheme = /^\d+$/.test(outputId) ? "artifact" : "agent";
const url = `${scheme}://${outputId}`;
Defensive patterns

Strategy: validation

Validate before calling

if (!/^\d+$/.test(id)) {
  throw new Error(`artifact:// requires a numeric ID, got: ${id}`);
}
const url = new URL(`artifact://${id}`);

Type guard

function isNumericArtifactId(id: string): boolean {
  return /^\d+$/.test(id);
}

Try / catch

try {
  const res = await resolveUrl(new URL(`artifact://${id}`));
} catch (err) {
  if (err instanceof Error && err.message.startsWith("artifact:// ID must be numeric")) {
    // route named IDs to agent:// instead
    const res2 = await resolveUrl(new URL(`agent://${id}`));
  } else throw err;
}

Prevention

When it happens

Trigger: Calling artifact://reviewer_0, artifact://abc, artifact://3a, or artifact://-1 — any non-numeric host passed to resolveArtifactFile via ArtifactProtocolHandler.resolve.

Common situations: 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.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/e4745bf722f8c3ba. Report an issue: GitHub.