can1357/oh-my-pi · critical · anyhow::Error
Overlapping replacements detected; refine pattern to avoid a
Error message
Overlapping replacements detected; refine pattern to avoid ambiguous edits
What it means
_write_json checks process.stdin before writing a command frame. If the subprocess's stdin pipe is None (process exited and pipes closed, or spawned without stdin=PIPE), it raises RpcProcessExitError, signalling the RPC server side is gone or misconfigured.
Source
Thrown at crates/pi-ast/src/ops.rs:316
let mut sorted: Vec<&Edit<String>> = edits.iter().collect();
sorted.sort_by(|a, b| {
a.position
.cmp(&b.position)
.then(a.deleted_length.cmp(&b.deleted_length))
.then(a.inserted_text.cmp(&b.inserted_text))
});
// Byte-identical edits (same span, same replacement) are one deterministic
// edit: multiple patterns matching the same node collapse instead of
// tripping the overlap check. Only divergent overlaps are ambiguous.
sorted.dedup_by(|a, b| {
a.position == b.position
&& a.deleted_length == b.deleted_length
&& a.inserted_text == b.inserted_text
});
let mut prev_end = 0usize;
for edit in &sorted {
if edit.position < prev_end {
return Err(anyhow!(
"Overlapping replacements detected; refine pattern to avoid ambiguous edits"
));
}
prev_end = edit.position.saturating_add(edit.deleted_length);
}
let mut output = content.to_string();
for edit in sorted.into_iter().rev() {
let start = edit.position;
let end = edit.position.saturating_add(edit.deleted_length);
if end > output.len() || start > end {
return Err(anyhow!("Computed edit range is out of bounds"));
}
let replacement = std::str::from_utf8(&edit.inserted_text)
.map_err(|err| anyhow!("Replacement text is not valid UTF-8: {err}"))?;
output.replace_range(start..end, replacement);
}
Ok(output)View on GitHub (pinned to 9690622007)
Solutions
- Restart the client: stop() the current instance, create a new RpcClient, start() again, and retry the command.
- Check server stderr/logs to find why the child process exited before the write.
- Verify the spawn command and that the client constructs the process with stdin=PIPE.
- Catch RpcProcessExitError around requests and implement a restart-and-retry wrapper.
Example fix
// before
result = await client.request("prompt", {...}) # raises after server died
// after
try:
result = await client.request("prompt", {...})
except RpcProcessExitError:
await client.stop()
client = RpcClient(cmd)
await client.start()
result = await client.request("prompt", {...}) Defensive patterns
Strategy: try-catch
Validate before calling
if client._process is None or client._process.poll() is not None:
await client.stop(); await client.start() # restart before sending Type guard
def process_alive(client: RpcClient) -> bool:
p = client._process
return p is not None and p.poll() is None Try / catch
try:
result = await client.request("prompt", payload)
except RpcProcessExitError:
await client.stop()
client = RpcClient(cmd)
await client.start()
result = await client.request("prompt", payload) Prevention
- Monitor the child process (poll()) and restart proactively when it dies.
- Capture server stderr and exit code to diagnose recurring crashes.
- Always spawn through the library's start() so stdin=PIPE is set up correctly.
When it happens
Trigger: The child RPC process has crashed/exited and its stdin pipe was closed; the subprocess was created without stdin redirection; a concurrent stop() closed stdin while a write was in flight.
Common situations: Server binary crashed earlier (or never started correctly), OS killed the child, or long-running session where the server died mid-session and the next command is attempted.
Related errors
- Computed edit range is out of bounds
- ${this.#options.languageName} kernel stdin is not open
- Unsupported language '{value}'. Supported: {}
- Unable to infer language from file extension: {}. Specify `l
- Invalid pattern: {err}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/09623e6db9419122.
Report an issue: GitHub.