can1357/oh-my-pi · error · ToolError
Conflict URI scope '/${conflictUri.scope}' is read-only — re
Error message
Conflict URI scope '/${conflictUri.scope}' is read-only — read `conflict://${conflictUri.id}/${conflictUri.scope}` to inspect that side. To write, drop the scope (`conflict://${conflictUri.id}`). What it means
When writing to a `conflict://` URI with a scope segment (e.g. `conflict://<id>/ours`), WriteTool rejects the write because scope segments are read-only views of one side of a conflict — they exist only for reading/inspecting. To resolve a conflict the caller must write to the bare `conflict://<id>` URI and supply the chosen content (or a shorthand like `@ours`) in the `content` field.
Source
Thrown at packages/coding-agent/src/tools/write.ts:1221
},
},
});
if (xdResult) return xdResult;
let resultText = `Successfully wrote ${cleanContent.length} bytes to ${path}`;
if (stripped) {
resultText += `\nNote: auto-stripped hashline display prefixes from content before writing.`;
}
return { content: [{ type: "text", text: resultText }], details: {} };
}
if (scheme !== "local") await internalRouter.write(path, cleanContent);
// local:// is backed by the session-local artifact sandbox and is
// resolved by resolvePlanPath below so write/read share the same root.
}
const conflictUri = parseConflictUri(path);
if (conflictUri) {
if (conflictUri.scope) {
throw new ToolError(
`Conflict URI scope '/${conflictUri.scope}' is read-only — read \`conflict://${conflictUri.id}/${conflictUri.scope}\` to inspect that side. To write, drop the scope (\`conflict://${conflictUri.id}\`) and put the chosen content (or shorthand like \`@${conflictUri.scope}\`) in \`content\`.`,
);
}
emitWriteProgress(onUpdate, cleanContent, path);
const result =
conflictUri.id === "*"
? await this.#resolveAllConflicts(cleanContent, stripped, signal, content)
: await this.#resolveSingleConflictById(conflictUri.id, cleanContent, stripped, signal);
if (conflictUri.recoveredPrefix !== undefined) {
appendNoteToResult(
result,
`Note: stripped erroneous '${conflictUri.recoveredPrefix}:' prefix from path; conflict URIs are global (use \`conflict://${conflictUri.id}\`, not \`<file>:conflict://${conflictUri.id}\`).`,
);
}
return result;
}
const resolvedArchivePath = await this.#resolveArchiveWritePath(path);
if (resolvedArchivePath) {View on GitHub (pinned to 9690622007)
Solutions
- Drop the scope and write to `conflict://<id>` with the chosen full content in `content`.
- Use the `@<scope>` shorthand in content (e.g. `@theirs`) to select one side wholesale.
- Read the scoped URI (`conflict://<id>/<scope>`) if you only meant to inspect, not write.
Example fix
// before
write({ path: "conflict://abc123/theirs", content: "..." })
// after
write({ path: "conflict://abc123", content: "@theirs" }) Defensive patterns
Strategy: validation
Validate before calling
const m = path.match(/^conflict:\/\/([^/]+)(\/.*)?$/); if (m && m[2]) throw new Error("conflict:// scope is read-only; write to conflict://" + m[1]); Type guard
function isWritableConflictUri(path: string): boolean { const m = path.match(/^conflict:\/\/[^/]+$/); return !!m; } Try / catch
try { await write({ path, content }); } catch (e) { if (String(e.message).includes("is read-only")) { const id = path.split("/")[2]; await write({ path: `conflict://${id}`, content: `@${path.split("/")[3]}` }); } else throw e; } Prevention
- Treat scoped conflict:// URIs (conflict://<id>/<scope>) as read-only.
- Resolve conflicts by writing to the bare conflict://<id> URI.
- Use the @<scope> content shorthand to pick a side.
- Never attempt in-place edits of a scoped conflict view.
When it happens
Trigger: Calling Write with a path like `conflict://abc123/theirs` — i.e. parseConflictUri returns a URI whose `scope` is non-empty.
Common situations: An agent that has just read one side of a conflict (`conflict://<id>/ours`) tries to overwrite that same scoped URL to resolve the conflict, instead of writing to the un-scoped conflict URI.
Related errors
- ${error.message} (rethrown as ToolError from StructuredSubag
- ${scheme}:// URLs are read-only for write; use the protocol-
- LSP action ${action} is disabled in this read-only session
- Plan mode is not active.
- Unsupported internal URL in bash command: ${url}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/2ffaf11555f36127.
Report an issue: GitHub.