{"record":{"id":"61284ce5a5c19927","repo":"can1357/oh-my-pi","slug":"write-target-target-ends-with-a-read-tool-sel","errorCode":null,"errorMessage":"write target '${target}' ends with a read-tool selector ':${sel}' and no such file exists — refusing to create a literal file by that name. If you meant to read it, use read({ path: \"${target}\" }). If you truly intend to create this file, pass its contents in `content` (a non-empty write is never blocked).","messagePattern":"write target '(.+?)' ends with a read-tool selector ':(.+?)' and no such file exists — refusing to create a literal file by that name\\. If you meant to read it, use read\\((.+?)\\)\\. If you truly intend to create this file, pass its contents in `content` \\(a non-empty write is never blocked\\)\\.","errorType":"validation","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/write.ts","lineNumber":164,"sourceCode":" * filename is legal on POSIX (issue #4618), that request otherwise resolves to\n * filesystem creation and reports success, leaving a stray zero-byte file the\n * model cannot recover from — the local analogue of the `xd://` near-miss guard\n * ({@link assertWriteTargetAddressable}, issue #6123).\n *\n * Fires only on the high-confidence combination the report identifies: the tail\n * parses as a read-tool selector, the literal target is missing, and no content\n * was supplied. Non-empty content is the escape hatch — it is never blocked, so\n * a deliberate write to a selector-shaped filename still succeeds. An existing\n * literal path or an ambiguous stat (`\"unknown\"`: EACCES, transient I/O) also\n * passes through so a real file is never shadowed by the guard.\n */\nfunction readSelectorForEmptyWrite(target: string, content: string): string | undefined {\n\tif (content.length > 0) return undefined;\n\treturn splitPathAndSel(target).sel;\n}\n\nfunction throwReadSelectorMisfire(target: string, sel: string): never {\n\tthrow new ToolError(\n\t\t`write target '${target}' ends with a read-tool selector ':${sel}' and no such file exists — refusing to create a literal file by that name. ` +\n\t\t\t`If you meant to read it, use read({ path: \"${target}\" }). ` +\n\t\t\t`If you truly intend to create this file, pass its contents in \\`content\\` (a non-empty write is never blocked).`,\n\t);\n}\n\n/**\n * Recognize a semicolon-joined list of read-tool selectors mis-dispatched as a\n * single write target — the multi-file read expression the scout emitted in\n * issue #6809 (`a.txt:1-2;b/c.txt:3-4`). Every `;`-segment must be non-empty and\n * carry its own read selector ({@link splitPathAndSel} peels a `:N-M`, `:raw`,\n * or `:conflicts` tail). No real call targets such a list: `read` accepts one\n * path, `write` writes one file. Unlike {@link readSelectorForEmptyWrite} this\n * fires regardless of `content` — the non-empty-content escape hatch exists for\n * a lone selector-shaped *filename*, never a `;`-list, and honoring it here\n * silently creates a nested directory tree (`a.txt:1-2;b/`) in the workspace.\n * The caller still probes the literal target first, so an existing POSIX file\n * by that exact name stays writable (same escape as the single-selector guard).","sourceCodeStart":146,"sourceCodeEnd":182,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/write.ts#L146-L182","documentation":"The write target ends in ':<selector>' (a read-tool range/line selector) and no file by that literal name exists. Writing would create a nonsense file like 'foo.ts:10-20', so the tool refuses for empty writes and suggests using read() instead.","triggerScenarios":"write({ path: \"src/foo.ts:50-80\", content: \"\" }) — an empty-content write whose target parses with a read selector and no such literal file exists.","commonSituations":"A model confusing write with read syntax, attempting to 'write a range' of an existing file, or echoing read paths back into write.","solutions":["Use read({ path: \"src/foo.ts:50-80\" }) if you meant to read that range","Provide non-empty content: a non-empty write to that literal name is never blocked","Remove the ':selector' suffix and write to the plain file path"],"exampleFix":"// before\nwrite({ path: \"src/foo.ts:50-80\", content: \"\" })\n// after\nread({ path: \"src/foo.ts:50-80\" })","handlingStrategy":"validation","validationCode":"function hasReadSelector(target: string): boolean {\n  return /:[0-9]+(-[0-9]+)?$/.test(target.trim());\n}\nif (hasReadSelector(p) && content.length === 0) {\n  return read({ path: p });\n}","typeGuard":"function isPlainFilePath(t: string): boolean {\n  return splitPathAndSel(t.trim()).sel === undefined;\n}","tryCatchPattern":"try {\n  await write({ path: target, content });\n} catch (e) {\n  if (e instanceof ToolError && e.message.includes(\"read-tool selector\")) {\n    return read({ path: target });\n  }\n  throw e;\n}","preventionTips":["Never append :line or :start-end ranges to write targets","Use read() for range access; use write() only with plain file paths","Strip any selector suffix before composing a write path"],"tags":["path-validation","read-selector","misfire"],"backgroundTag":"read-selector-misfire","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}