can1357/oh-my-pi · critical · anyhow::Error

Computed edit range is out of bounds

Error message

Computed edit range is out of bounds

What it means

Writing the JSON command frame to the subprocess stdin raised BrokenPipeError or OSError. The client converts it to RpcProcessExitError with the underlying OS message, since a failed stdin write almost always means the child process has exited.

Source

Thrown at crates/pi-ast/src/ops.rs:328

			&& 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)
}

pub fn collect_matched_files(
	cwd: &Path,
	patterns: &[String],
) -> Result<Vec<MatchedFile>, std::io::Error> {
	let globset = build_globset(patterns)?;
	let mut builder = WalkBuilder::new(cwd);
	builder
		.hidden(false)
		.git_ignore(true)
		.git_global(true)

View on GitHub (pinned to 9690622007)

Solutions

  1. Restart the RPC client (stop, new instance, start) and retry; the old process cannot be written to.
  2. Inspect server stderr and exit code to diagnose the original crash.
  3. Serialize stop() and request calls (locks/async ordering) so stop() doesn't close stdin mid-write.
  4. Wrap all requests in try/except RpcProcessExitError with automatic restart logic.

Example fix

// before
await client.stop()
await client.request("prompt", {...})  # broken pipe: stdin already closed
// after
await client.stop()
await client.start()
await client.request("prompt", {...})
Defensive patterns

Strategy: try-catch

Validate before calling

proc = client._process
if proc is None or proc.poll() is not None:
    raise RuntimeError("RPC server not running; restart before sending commands")

Type guard

def can_write(client: RpcClient) -> bool:
    p = client._process
    return p is not None and p.stdin is not None and p.poll() is None

Try / catch

try:
    result = await client.request("prompt", payload)
except RpcProcessExitError as exc:
    logger.error("RPC write failed, restarting client", exc_info=exc)
    await client.stop()
    await client.start()
    result = await client.request("prompt", payload)

Prevention

When it happens

Trigger: Child RPC process died before/during the write; pipe buffer closed concurrently by stop(); EPIPE/EIO from the OS when the read end of the pipe is closed.

Common situations: Server crash from an unhandled exception, out-of-memory kill, or the server exiting after an earlier protocol error — the next request write then fails with a broken pipe.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/722602b11e6e918d. Report an issue: GitHub.