can1357/oh-my-pi · error · ToolError
Could not resolve image attachment '${readPath}'. Available
Error message
Could not resolve image attachment '${readPath}'. Available attachment URIs: ${availableUris}. Use one of the listed attachment URIs, or attach an image first when none are available. What it means
Reads of image attachment URIs (matching IMAGE_ATTACHMENT_URI_REGEX) are resolved against the session's registered image attachments. If no attachment's uri equals the requested readPath, the tool throws a ToolError listing every available attachment URI (or 'none') and instructs the caller to use one of them or attach an image first.
Source
Thrown at packages/coding-agent/src/tools/read.ts:1097
async #executeInner(
_toolCallId: string,
params: ReadParams,
signal?: AbortSignal,
_onUpdate?: AgentToolUpdateCallback<ReadToolDetails>,
_toolContext?: AgentToolContext,
): Promise<AgentToolResult<ReadToolDetails>> {
let { path: readPath } = params;
if (readPath.startsWith("file://")) {
readPath = expandPath(readPath);
}
if (IMAGE_ATTACHMENT_URI_REGEX.test(readPath)) {
const attachments = this.session.getImageAttachments?.() ?? [];
const attachment = attachments.find(entry => entry.uri === readPath);
if (!attachment) {
const availableUris = attachments.map(entry => entry.uri).join(", ") || "none";
throw new ToolError(
`Could not resolve image attachment '${readPath}'. Available attachment URIs: ${availableUris}. Use one of the listed attachment URIs, or attach an image first when none are available.`,
);
}
readPath = attachment.sourcePath;
}
const conflictUri = parseConflictUri(readPath);
if (conflictUri) {
if (conflictUri.id === "*") {
throw new ToolError(
"Reading `conflict://*` is not supported — wildcards are write-only. Use the `<path>:conflicts` read selector for the full list of conflicts in a file, or read `conflict://<N>` to inspect a single block.",
);
}
return this.#readConflictRegion(conflictUri.id, conflictUri.scope);
}
const displayMode = resolveFileDisplayMode(this.session);
const parsedUrlTarget = parseReadUrlTarget(readPath);View on GitHub (pinned to 9690622007)
Solutions
- Copy the URI exactly from the 'Available attachment URIs' list in the message.
- If the list is 'none', attach the image to the session first (drag-and-drop/paste into the client) before reading.
- Re-attach the image if the session was restarted and use the new URI.
- Fall back to reading the original file from disk by path if it exists locally.
Example fix
// before
read('attachment://img-42') // stale id
// after
read('attachment://img-7f3a') // URI copied from the available list Defensive patterns
Strategy: validation
Validate before calling
const attachments = session.getImageAttachments?.() ?? [];
const uris = new Set(attachments.map(a => a.uri));
if (!uris.has(attachmentUri)) {
// fall back to the original file on disk, or attach first
throw new Error(`Unknown attachment ${attachmentUri}; known: ${[...uris].join(', ') || 'none'}`);
} Type guard
function isKnownAttachment(uri: string, session: Session): boolean {
return (session.getImageAttachments?.() ?? []).some(a => a.uri === uri);
} Try / catch
try {
return await readTool.execute({ path: uri });
} catch (e) {
if (e instanceof ToolError && e.message.startsWith('Could not resolve image attachment')) {
const available = e.message.match(/Available attachment URIs: ([^.]*)/)?.[1] ?? 'none';
const first = available.split(', ')[0];
return first && first !== 'none' ? readTool.execute({ path: first }) : null;
}
throw e;
} Prevention
- Copy attachment URIs verbatim from the tool/client output — never reconstruct them.
- Re-resolve URIs after session restarts; attachments don't persist across sessions.
- Check the available-URIs list embedded in the error before retrying.
- If the source file exists on disk, read it by path instead of via attachment URI.
When it happens
Trigger: Reading 'attachment://<id>' (or similar) with a stale/typo'd URI; the attachment was dropped from session history; a new session with no attachments while the model replays an old URI.
Common situations: Model hallucinating or misremembering an attachment URI; referencing an image attached in a previous session/turn that was garbage-collected; whitespace or case mismatch in the URI.
Related errors
- Discord response did not include an attachment
- Discord attachment did not include a URL
- Cannot resolve relative source "${source}": marketplaceClone
- Reading `conflict://*` is not supported — wildcards are writ
- Unknown URI-like write target '${trimmed}'. Did you mean 'xd
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/b87b896d1d2b1455.
Report an issue: GitHub.