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

  1. Drop the scope and write to `conflict://<id>` with the chosen full content in `content`.
  2. Use the `@<scope>` shorthand in content (e.g. `@theirs`) to select one side wholesale.
  3. 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

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


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