Hmbown/CodeWhale · error
Frame lock failed
Error message
Frame lock failed
What it means
run_world, the pet-render world simulation loop, keeps a bounded ring of the last 16 rendered frames behind a std::sync::Mutex. This error is thrown when locking that mutex returns a poisoning error, meaning another thread panicked while holding the frame-lock and left the shared state inconsistent. The world loop aborts rather than continue rendering from possibly-corrupt frame data.
Solutions
- Find and fix the panic in the thread that poisoned the frames mutex (the original panic message is printed before this error appears).
- Restart the world/serve process to clear the poisoned lock.
- If intentional recovery is desired, use frames.lock().unwrap_or_else(|p| p.into_inner()) instead of failing, accepting the possibly-partial state.
- Guard the frame-producing code against panics (catch_unwind or fix indexing/bounds errors) so the lock never poisons.
Example fix
// before
let mut output = frames
.lock()
.map_err(|_| anyhow::anyhow!("Frame lock failed"))?;
// after — recover from a poisoned lock instead of aborting the world loop
let mut output = frames.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); Defensive patterns
Strategy: try-catch
Type guard
fn frames_healthy(frames: &Mutex<VecDeque<Value>>) -> bool { !frames.is_poisoned() } Try / catch
match frames.lock() {
Ok(guard) => { /* push frame */ }
Err(poisoned) => {
let guard = poisoned.into_inner(); // or log + abort world loop
}
} Prevention
- Never panic while holding a shared mutex; return Result from frame producers instead.
- Run world consumers under catch_unwind so a consumer panic does not poison the render loop's lock.
- Treat any earlier panic in serve logs as the root cause and fix it before adding lock recovery.
When it happens
Trigger: Calling run_world (from serve) while another thread holding the frames Mutex panics; the subsequent frames.lock() call in the tick loop returns Err(PoisonError), which is mapped to this anyhow error.
Common situations: A panic inside a rendering/consumer thread that also touches the frame buffer (e.g. a bug in frame serialization or a JSON build panic); most common during development with panicking frame consumers, or under a genuinely concurrent world server with a buggy frame reader.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Shell tracking hit an internal error — restart Codewhale to…
- xAI OAuth lifecycle lock was poisoned
- catalog cache unavailable
- child wall-time budget exhausted
- {}
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/bdee008ddca16dbe.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/tui/pet_watch/owner.rs:603
}
pose["style"]["alpha"] = json!(
(pose["style"]["alpha"].as_f64().unwrap_or(0.5) * saved.appearance.brightness)
.clamp(0.0, 1.0)
);
}
frame["appearance"] = json!(saved.appearance);
frame["producerConnected"] = json!(producer.is_some());
frame["storageAvailable"] = json!(!storage_error);
frame["audioOwner"] = json!(audio.as_ref().map(|(id, _)| id));
frame["audioUnavailable"] = json!(audio_error);
measurements.push_back(started.elapsed().as_secs_f64() * 1000.0);
if measurements.len() > 300 {
measurements.pop_front();
}
frame["performance"] = json!({"worldHz":30,"frames":ticks,"uptimeSeconds":origin.elapsed().as_secs_f64(),"workMs":measurements.back()});
let mut output = frames
.lock()
.map_err(|_| anyhow::anyhow!("Frame lock failed"))?;
output.push_back(frame);
if output.len() > 16 {
output.pop_front();
}
}
if last_save.elapsed() >= Duration::from_secs(1) {
storage_error = save(&context, &mut saved, &mut store).is_err();
last_save = Instant::now();
}
let work = match rx.recv_timeout(Duration::from_millis(2)) {
Ok(work) => work,
Err(mpsc::RecvTimeoutError::Timeout) => continue,
Err(mpsc::RecvTimeoutError::Disconnected) => {
save(&context, &mut saved, &mut store)?;
return Ok(());
}
};
match work {View on GitHub (pinned to 433685b202)