Hmbown/CodeWhale · error · anyhow::Error
lane exit receipt {} belongs to {}, expected {}
Error message
lane exit receipt {} belongs to {}, expected {} What it means
After parsing, read_lane_exit_receipt checks receipt.lane_id against the lane id it was asked about and bails on mismatch. The receipt is only meaningful for the lane that wrote it; a mismatch means the file at lane_exit_receipt_path(log_path) is stale — left by a previous lane that used the same log path. Acting on it would attribute another lane's exit code to this lane.
Source
Thrown at crates/lane/src/runtime.rs:534
fn read_lane_exit_receipt(log_path: &Path, lane_id: &str) -> Result<Option<LaneExitReceipt>> {
let path = lane_exit_receipt_path(log_path);
let metadata = match std::fs::metadata(&path) {
Ok(metadata) => metadata,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(err) => return Err(err).with_context(|| format!("stat {}", path.display())),
};
if metadata.len() > MAX_EXIT_RECEIPT_BYTES {
bail!(
"lane exit receipt {} exceeds {} bytes",
path.display(),
MAX_EXIT_RECEIPT_BYTES
);
}
let bytes = std::fs::read(&path).with_context(|| format!("read {}", path.display()))?;
let receipt: LaneExitReceipt =
serde_json::from_slice(&bytes).with_context(|| format!("parse {}", path.display()))?;
if receipt.lane_id != lane_id {
bail!(
"lane exit receipt {} belongs to {}, expected {}",
path.display(),
receipt.lane_id,
lane_id
);
}
Ok(Some(receipt))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TmuxSessionState {
Present,
Absent,
}
fn tmux_command(socket: &Path) -> Command {
let mut command = Command::new("tmux");
command.arg("-S").arg(socket);View on GitHub (pinned to 0c42157ee5)
Solutions
- Remove the stale receipt file named in the message, then reconcile again
- Give each lane generation a unique log path (embed the lane id or a run id)
- If you manage lifecycle code, clear receipt files when a lane's log directory is recycled
Example fix
// before
let log_path = logs_dir.join("lane-current.jsonl"); // reused across lanes
// after
let log_path = logs_dir.join(format!("{lane_id}.jsonl")); // unique per lane Defensive patterns
Strategy: fallback
Validate before calling
// Use unique log paths per lane so receipts can never be cross-read.
let log_path = logs_dir.join(format!("{lane_id}.jsonl")); Try / catch
match read_lane_exit_receipt(&log_path, &lane_id) {
Ok(Some(receipt)) => { /* exit code for this lane */ }
Err(err) if err.to_string().contains("belongs to") => {
// Stale receipt from another lane: discard and fall back to tmux state.
let _ = std::fs::remove_file(lane_exit_receipt_path(&log_path));
}
other => other?,
} Prevention
- Embed the lane id (or a unique run id) in every lane's log path
- Clear receipt files when recycling a lane's log directory
- Never copy lane state directories between lanes
When it happens
Trigger: Calling reconciliation for lane B while the receipt file under B's log_path still contains {"lane_id": "A", ...}. Happens when log paths are reused across lanes or generations and the old receipt was not removed.
Common situations: Regenerating lanes in a fixed directory (e.g. logs/lane-0) without cleanup; copying/cloning a workspace with leftover lane state; a lane id scheme that collides after truncation.
Related errors
- serialized lane exit receipt exceeds size bound
- project workspace path cannot be empty
- terminal lane transition requires a terminal status
- unknown runtime backend `{other}` (use tmux|inline|vm|ci)
- invalid lane environment key {key:?}
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/ddeca9ee332c9961.
Report an issue: GitHub.