{"record":{"id":"2ffaf11555f36127","repo":"can1357/oh-my-pi","slug":"conflict-uri-scope-conflicturi-scope-is-read","errorCode":null,"errorMessage":"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}`).","messagePattern":"Conflict URI scope '/(.+?)' is read-only — read `conflict://(.+?)/(.+?)` to inspect that side\\. To write, drop the scope \\(`conflict://(.+?)`\\)\\.","errorType":"validation","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/write.ts","lineNumber":1221,"sourceCode":"\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t\tif (xdResult) return xdResult;\n\t\t\t\t\tlet resultText = `Successfully wrote ${cleanContent.length} bytes to ${path}`;\n\t\t\t\t\tif (stripped) {\n\t\t\t\t\t\tresultText += `\\nNote: auto-stripped hashline display prefixes from content before writing.`;\n\t\t\t\t\t}\n\t\t\t\t\treturn { content: [{ type: \"text\", text: resultText }], details: {} };\n\t\t\t\t}\n\t\t\t\tif (scheme !== \"local\") await internalRouter.write(path, cleanContent);\n\t\t\t\t// local:// is backed by the session-local artifact sandbox and is\n\t\t\t\t// resolved by resolvePlanPath below so write/read share the same root.\n\t\t\t}\n\n\t\t\tconst conflictUri = parseConflictUri(path);\n\t\t\tif (conflictUri) {\n\t\t\t\tif (conflictUri.scope) {\n\t\t\t\t\tthrow new ToolError(\n\t\t\t\t\t\t`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\\`.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\temitWriteProgress(onUpdate, cleanContent, path);\n\t\t\t\tconst result =\n\t\t\t\t\tconflictUri.id === \"*\"\n\t\t\t\t\t\t? await this.#resolveAllConflicts(cleanContent, stripped, signal, content)\n\t\t\t\t\t\t: await this.#resolveSingleConflictById(conflictUri.id, cleanContent, stripped, signal);\n\t\t\t\tif (conflictUri.recoveredPrefix !== undefined) {\n\t\t\t\t\tappendNoteToResult(\n\t\t\t\t\t\tresult,\n\t\t\t\t\t\t`Note: stripped erroneous '${conflictUri.recoveredPrefix}:' prefix from path; conflict URIs are global (use \\`conflict://${conflictUri.id}\\`, not \\`<file>:conflict://${conflictUri.id}\\`).`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t}\n\t\t\tconst resolvedArchivePath = await this.#resolveArchiveWritePath(path);\n\t\t\tif (resolvedArchivePath) {","sourceCodeStart":1203,"sourceCodeEnd":1239,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/write.ts#L1203-L1239","documentation":"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.","triggerScenarios":"Calling Write with a path like `conflict://abc123/theirs` — i.e. parseConflictUri returns a URI whose `scope` is non-empty.","commonSituations":"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.","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."],"exampleFix":"// before\nwrite({ path: \"conflict://abc123/theirs\", content: \"...\" })\n// after\nwrite({ path: \"conflict://abc123\", content: \"@theirs\" })","handlingStrategy":"validation","validationCode":"const m = path.match(/^conflict:\\/\\/([^/]+)(\\/.*)?$/); if (m && m[2]) throw new Error(\"conflict:// scope is read-only; write to conflict://\" + m[1]);","typeGuard":"function isWritableConflictUri(path: string): boolean { const m = path.match(/^conflict:\\/\\/[^/]+$/); return !!m; }","tryCatchPattern":"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; }","preventionTips":["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."],"tags":["tool-error","conflict-resolution","read-only"],"backgroundTag":"read-only-uri-write","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}