can1357/oh-my-pi · error
modified change requires peer root
Error message
modified change requires peer root
What it means
The pi-iso differ builds a unified diff for a Modified entry by reading both the changed file (primary side) and its unchanged counterpart in the peer root (lower layer). plain_change documents that `op == Modified` requires `peer_root = Some(lower)`; when a caller classifies an entry as Modified but passes `peer_root: None`, this expect panics. It is an internal invariant violation: the diff of a modification cannot be computed without the original bytes.
Source
Thrown at crates/pi-iso/src/diff.rs:366
/// `op == Modified` requires `peer_root = Some(lower)` so we can read the
/// counterpart; `Added`/`Removed` only need the side we already know about.
fn plain_change(
side: &Path,
rel: &Path,
op: ChangeKind,
peer_root: Option<&Path>,
) -> IsoResult<FileChange> {
let full = side.join(rel);
let primary = std::fs::read(&full)
.map_err(|err| IsoError::other(format!("read {}: {err}", full.display())))?;
if looks_binary(&primary) {
return Ok(FileChange { path: rel.to_path_buf(), op, diff: None });
}
let (old_bytes, new_bytes) = match op {
ChangeKind::Added => (Vec::new(), primary),
ChangeKind::Removed => (primary, Vec::new()),
ChangeKind::Modified => {
let peer = peer_root.expect("modified change requires peer root");
let peer_full = peer.join(rel);
let peer_bytes = std::fs::read(&peer_full)
.map_err(|err| IsoError::other(format!("read {}: {err}", peer_full.display())))?;
if looks_binary(&peer_bytes) {
return Ok(FileChange { path: rel.to_path_buf(), op, diff: None });
}
(peer_bytes, primary)
},
};
let (Ok(old_text), Ok(new_text)) =
(std::str::from_utf8(&old_bytes), std::str::from_utf8(&new_bytes))
else {
return Ok(FileChange { path: rel.to_path_buf(), op, diff: None });
};
Ok(FileChange {
path: rel.to_path_buf(),
op,
diff: Some(render_unified(rel, op, old_text, new_text)),View on GitHub (pinned to 9690622007)
Solutions
- Ensure the diff is always invoked with both roots configured — pass the lower/base directory as peer_root whenever a Modified change is possible.
- Verify the peer root exists and is readable before diffing (index_tree skips missing roots silently, so a missing peer surfaces later as this panic).
- If diffing a standalone tree is intended, treat every entry as Added rather than Modified so no peer bytes are needed.
- Check the pi-iso version/upgrade notes: if walk_diff_blocking's signature changed, update call sites to supply the peer root parameter.
Example fix
// before: diffing only the upper layer
walk_diff_blocking(&upper, None /* peer_root */, |change| { ... });
// panic: modified change requires peer root
// after: supply the base layer
walk_diff_blocking(&upper, Some(&lower), |change| { ... }); Defensive patterns
Strategy: validation
Validate before calling
import * as fs from 'node:fs';
function assertPeerRoot(lowerPath) {
if (!lowerPath || !fs.existsSync(lowerPath)) {
throw new Error(`peer (lower) root must exist before diffing Modified entries: ${lowerPath}`);
}
} Type guard
function isConfiguredPeerRoot(peerRoot) {
return typeof peerRoot === 'string' && peerRoot.length > 0;
} Try / catch
try {
await iso.diff({ upper, lower });
} catch (err) {
if (err instanceof Error && err.message.includes('modified change requires peer root')) {
console.error('Diff was run without a base layer; configure the lower root');
} else {
throw err;
}
} Prevention
- Always pass both upper and lower roots when calling the iso diff API; never diff an overlay without its base.
- Validate that the lower root exists and is readable before diffing.
- Keep the base layer stable for the lifetime of the overlay — don't delete or move it between writes and diffs.
When it happens
Trigger: Calling walk_diff_blocking / the diff API such that an entry is classified ChangeKind::Modified while the peer (lower) root is None — typically diffing against a non-existent or unset lower layer, or a custom caller invoking plain_change directly with Modified and peer_root=None.
Common situations: Diffing an overlay/upper directory without configuring the lower (base) directory it was layered on; the peer root was deleted or moved between snapshot and diff; a version change where the diff API started accepting a single-root mode but Modified classification still assumes a peer.
Related errors
- unknown filetype: {ft_debug}
- Is a directory
- Too many levels of symbolic links
- {}: {error}
- failed to create a unique fc temporary file
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/e95586f5549dc5da.
Report an issue: GitHub.