can1357/oh-my-pi · error · ToolError
write does not accept the trailing selector ":${sel}" — it w
Error message
write does not accept the trailing selector ":${sel}" — it writes a whole file. Remove ":${sel}", or if the filename truly ends with it, percent-encode the ":" as %3A. What it means
The write tool rejects a path that carries a trailing `:selector` suffix (e.g. `notes.md:raw`). Read supports such selectors, but write always replaces the entire file, so a selector is either a copy-paste mistake or a filename that literally contains a colon. The library throws a ToolError rather than silently writing to a mangled filename.
Source
Thrown at packages/coding-agent/src/tools/path-utils.ts:481
return { path, sel: chunks.join(":") };
}
/**
* Peel a read-tool selector off an internal-URL write target so `write` resolves
* the same file `read` does (e.g. `ssh://h/f:raw` -> `ssh://h/f`). Only the
* whole-file display modes `raw`/`conflicts` are accepted (they do not change
* which bytes are written); any other selector-shaped tail `splitInternalUrlSel`
* peels — a line range, a compound like `raw:1-20`, or a malformed `:-N` — throws,
* because `write` addresses a whole file, not a partial range, and silently
* stripping it would write to a path the caller never named. Non-URL paths and
* URLs without a selector pass through unchanged.
*/
export function peelWriteUrlSelector(rawPath: string): string {
const { path, sel } = splitInternalUrlSel(rawPath);
if (sel === undefined) return rawPath;
// Case-insensitive to match read's selector grammar (parseSel + the /i regexes above).
if (/^(?:raw|conflicts)$/i.test(sel)) return path;
throw new ToolError(
`write does not accept the trailing selector ":${sel}" — it writes a whole file. ` +
`Remove ":${sel}", or if the filename truly ends with it, percent-encode the ":" as %3A.`,
);
}
function assertNotInternalUrl(expanded: string, original: string): void {
for (const prefix of TOP_LEVEL_INTERNAL_URL_PREFIXES) {
if (expanded.startsWith(prefix)) {
throw new Error(
`Path "${original}" uses internal scheme "${prefix}" and must be resolved through the proper protocol handler, not as a filesystem path.`,
);
}
}
}
export function normalizeLocalScheme(filePath: string): string {
return filePath.replace(/^(local:)\/(?!\/)/, "$1//");
}View on GitHub (pinned to 9690622007)
Solutions
- Remove the trailing `:<selector>` from the path and pass only the plain file path to write.
- If the filename legitimately ends with `:<sel>`, percent-encode the colon in the path as `%3A` so the selector parser does not fire.
- If you intended to edit part of a file, use the edit tool or read with a selector, then write the whole file.
Example fix
// before
write({ path: "docs/guide.md:raw", content: "..." })
// after
write({ path: "docs/guide.md", content: "..." }) Defensive patterns
Strategy: validation
Validate before calling
function isWritablePath(p) {
const m = p.match(/:([^/:]+)$/);
return !(m && /^(raw|conflicts)$/i.test(m[1]) === false && p.includes(":"));
}
if (!isWritablePath(p)) p = p.replace(/:([^/:]+)$/, ""); Type guard
function hasSelectorSuffix(p) {
const m = p.match(/:([^/:]+)$/);
return m !== null;
} Prevention
- Never reuse read-tool path strings (with selectors) as write paths.
- Percent-encode literal colons in filenames as %3A.
- Strip selectors centrally before any write call.
When it happens
Trigger: Calling the write tool (via resolveToCwd/peelWriteUrlSelector) with a path argument that splitInternalUrlSel parses as path+selector where the selector is not `raw` or `conflicts` — e.g. `write path="file.ts:summary"` after reusing a read-style path.
Common situations: Copy-pasting a read-tool path (with its `:raw`/`:lines` selector) into a write call; agent-generated paths that append `:something`; filenames genuinely containing `:` (Windows drive-like names, timestamped names like `log:2026-01-01.txt`).
Related errors
- Snippet file not found: ${resolved}
- ACP cwd must be absolute: ${cwd}
- Security knowledge base is not a file: ${input}
- Security output directory must be outside the scanned reposi
- Security output path exists and is not a directory
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/bc42e66a862b52b8.
Report an issue: GitHub.