can1357/oh-my-pi · error

Artifact ${id} resolved to a directory, not a file

Error message

Artifact ${id} resolved to a directory, not a file

What it means

After resolving an artifact id to a path, resolveArtifactFile stats the file and rejects directories. Artifact ids are expected to map to individual files; a directory match means the id resolution matched something the protocol cannot serve as content.

Source

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

		for (const f of files) {
			const m = f.match(/^(\d+)\./);
			if (m) availableIds.add(m[1]);
		}
	}

	if (!anyDirExists) {
		throw new Error("No artifacts directory found");
	}

	if (!foundPath) {
		const sorted = [...availableIds].sort((a, b) => Number(a) - Number(b));
		const availableStr = sorted.length > 0 ? sorted.join(", ") : "none";
		throw new Error(`Artifact ${id} not found. Available: ${availableStr}`);
	}

	const stat = await Bun.file(foundPath).stat();
	if (stat.isDirectory()) {
		throw new Error(`Artifact ${id} resolved to a directory, not a file`);
	}
	return { id, path: foundPath, size: stat.size };
}

export class ArtifactProtocolHandler implements ProtocolHandler {
	readonly scheme = "artifact";
	readonly immutable = true;

	async resolve(url: InternalUrl, context?: ResolveContext): Promise<InternalResource> {
		const artifact = await resolveArtifactFile(url, context);

		// Path-only callers (search/grep, bash URL expansion) never touch the
		// artifact bytes. Return the resource shape so those flows keep working
		// on artifacts of any size — only content materialization is gated.
		if (context?.pathOnly) {
			return {
				url: url.href,
				content: "",

View on GitHub (pinned to 9690622007)

Solutions

  1. Point the artifact:// URL at a file id, not the directory
  2. Inspect the artifacts directory and remove or rename the directory that collides with an artifact id
  3. If a tool produced the directory, re-run the tool so it emits a file artifact

Example fix

// before
await resolve('artifact://bundle')      // 'bundle' is a directory
// after
await resolve('artifact://bundle/index.html') // reference a file artifact
Defensive patterns

Strategy: validation

Validate before calling

import { stat } from 'node:fs/promises'
const st = await stat(path)
if (st.isDirectory()) throw new Error(`${path} is a directory; pick a file artifact`)

Try / catch

try {
  return await resolve(`artifact://${id}`)
} catch (err) {
  if (String(err).includes('resolved to a directory')) {
    logger.warn('artifact id maps to a directory, skipping', { id })
    return null
  }
  throw err
}

Prevention

When it happens

Trigger: An artifact id resolves to a directory path inside the artifacts directory — typically when the on-disk naming scheme contains a directory whose name collides with the id pattern, or a tool wrote a directory where a file was expected.

Common situations: Custom tooling wrote nested output (e.g. extracted archives) under the artifacts dir; an id was reused for a directory output; filesystem layout changed between tool versions.

Related errors


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