can1357/oh-my-pi · error · ToolError
write target '${target}' is a semicolon-joined list of ${cou
Error message
write target '${target}' is a semicolon-joined list of ${count} read-tool selectors, not a filesystem path — refusing to create it. write creates a single file; issue one read() per path to read these ranges (e.g. read({ path: "<one path>:<range>" })). What it means
The write target looks like multiple read-tool selectors joined by semicolons (e.g. 'a.ts:1-5;b.ts:6-9'). write only creates a single file, so it refuses to create such a literal path and points you at per-path read() calls.
Source
Thrown at packages/coding-agent/src/tools/write.ts:196
* fires regardless of `content` — the non-empty-content escape hatch exists for
* a lone selector-shaped *filename*, never a `;`-list, and honoring it here
* silently creates a nested directory tree (`a.txt:1-2;b/`) in the workspace.
* The caller still probes the literal target first, so an existing POSIX file
* by that exact name stays writable (same escape as the single-selector guard).
*/
function readSelectorListMisfire(target: string): number | undefined {
if (!target.includes(";")) return undefined;
const segments = target.split(";");
if (segments.length < 2) return undefined;
for (const segment of segments) {
const trimmed = segment.trim();
if (trimmed.length === 0 || splitPathAndSel(trimmed).sel === undefined) return undefined;
}
return segments.length;
}
function throwReadSelectorListMisfire(target: string, count: number): never {
throw new ToolError(
`write target '${target}' is a semicolon-joined list of ${count} read-tool selectors, not a filesystem path — refusing to create it. ` +
`write creates a single file; issue one read() per path to read these ranges (e.g. read({ path: "<one path>:<range>" })).`,
);
}
async function assertNotReadSelectorMisfire(target: string, content: string, cwd: string): Promise<void> {
const listCount = readSelectorListMisfire(target);
if (listCount !== undefined && (await probeLiteralPathExists(target, cwd)) === "missing") {
throwReadSelectorListMisfire(target, listCount);
}
const sel = readSelectorForEmptyWrite(target, content);
if (sel === undefined) return;
if ((await probeLiteralPathExists(target, cwd)) !== "missing") return;
throwReadSelectorMisfire(target, sel);
}
const BULK_DIRECTIVE_RE = /^#?(\d+)\s*[:=]\s*(@ours|@theirs|@base|@both)$/;
/**View on GitHub (pinned to 9690622007)
Solutions
- Issue one read() per path/range instead of a single write
- Split into separate write() calls, one plain file path each
Example fix
// before
write({ path: "a.ts:1-5;b.ts:6-9", content: "" })
// after
read({ path: "a.ts:1-5" }); read({ path: "b.ts:6-9" }) Defensive patterns
Strategy: validation
Validate before calling
function looksLikeSelectorList(target: string): boolean {
const segs = target.trim().split(";").map(s => s.trim()).filter(Boolean);
if (segs.length < 2) return false;
return segs.every(s => splitPathAndSel(s).sel !== undefined);
}
if (looksLikeSelectorList(p)) {
for (const s of p.split(";")) await read({ path: s.trim() });
} Type guard
function isSinglePath(t: string): boolean {
return t.trim().split(";").length === 1;
} Try / catch
try {
await write({ path: target, content });
} catch (e) {
if (e instanceof ToolError && e.message.includes("semicolon-joined list")) {
for (const seg of target.split(";")) await read({ path: seg.trim() });
return;
}
throw e;
} Prevention
- One write() call per file — never join multiple paths with semicolons
- Use one read() per path/range for batch reads
- Keep write targets free of ';' characters
When it happens
Trigger: write({ path: "a.ts:1-5; b.ts:6-9", content: "" }) — target parses into ≥2 selector segments.
Common situations: Batch-style attempts to read or touch several file ranges in one call, or models concatenating read paths with semicolons.
Related errors
- write target '${target}' ends with a read-tool selector ':${
- invalid template, {}, contains directory separator
- invalid suffix {}, contains directory separator
- git-subdir path "${source.path}" does not exist in cloned re
- Absolute paths are not allowed in omp:// URLs
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/7df918ab25d8148e.
Report an issue: GitHub.