ultraworkers/claw-code · error
lane board should serialize
Error message
lane board should serialize
What it means
This panic fires in TaskRegistry::lane_status_json_at (task_registry.rs:246): serde_json::to_value(self.lane_board_at(...)) is .expect()-ed with 'lane board should serialize'. LaneBoard and its entries are plain #[derive(Serialize)] structs of Strings, u64s, Options, Vecs, and snake_case enums, so serialization is infallible in practice; the expect exists to satisfy the infallible-JSON API shape. A panic here means a Serialize impl in the LaneBoard graph returned Err — e.g. a newly added field type whose serialization can fail (non-string map keys, custom Serialize with error paths) — or the earlier lane_board_at lock panicked ('registry lock poisoned') before serialization even ran.
Source
Thrown at rust/crates/runtime/src/task_registry.rs:246
freshness,
};
match task.status {
TaskStatus::Running | TaskStatus::Created => board.active.push(entry),
TaskStatus::Blocked => board.blocked.push(entry),
TaskStatus::Completed | TaskStatus::Failed | TaskStatus::Stopped => {
board.finished.push(entry);
}
}
}
board
}
#[must_use]
pub fn lane_status_json_at(&self, now: u64, stalled_after_secs: u64) -> serde_json::Value {
serde_json::to_value(self.lane_board_at(now, stalled_after_secs))
.expect("lane board should serialize")
}
pub fn stop(&self, task_id: &str) -> Result<Task, String> {
let mut inner = self.inner.lock().expect("registry lock poisoned");
let task = inner
.tasks
.get_mut(task_id)
.ok_or_else(|| format!("task not found: {task_id}"))?;
match task.status {
TaskStatus::Completed | TaskStatus::Failed | TaskStatus::Stopped => {
return Err(format!(
"task {task_id} is already in terminal state: {}",
task.status
));
}
_ => {}
}View on GitHub (pinned to 08106b0c37)
Solutions
- Check whether the real panic is 'registry lock poisoned' from lane_board_at — if so, fix the primary lock-poisoning panic first (see that error).
- Grep the LaneBoard/LaneBoardEntry/LaneHeartbeat/TaskStatus types for custom Serialize impls or non-string-keyed maps and replace them with plainly serializable types.
- Replace the expect with graceful degradation so a serialization failure cannot abort the CLI: return serde_json::json!({"error": ...}) on Err.
- Add a unit test that round-trips a fully populated LaneBoard through serde_json::to_value to catch fallible fields at CI time.
Example fix
// before
serde_json::to_value(self.lane_board_at(now, stalled_after_secs))
.expect("lane board should serialize")
// after
serde_json::to_value(self.lane_board_at(now, stalled_after_secs))
.unwrap_or_else(|err| serde_json::json!({
"generated_at": now,
"serialization_error": err.to_string(),
})) Defensive patterns
Strategy: try-catch
Try / catch
use std::panic::{catch_unwind, AssertUnwindSafe};
let json = catch_unwind(AssertUnwindSafe(|| {
registry.lane_status_json_at(now, stalled_after_secs)
}))
.unwrap_or_else(|_| serde_json::json!({
"generated_at": now,
"error": "lane board unavailable (registry poisoned or serialization failed)",
})); Prevention
- Keep LaneBoard/LaneBoardEntry/LaneHeartbeat fields to plainly serializable types (String, u64, bool, Option, Vec, unit enums).
- Never add a custom Serialize impl or non-string-keyed map to the board graph without a to_value round-trip test.
- Add a CI unit test that serializes a fully populated LaneBoard via serde_json::to_value.
- Remember this expect can also be reached secondarily by lock poisoning in lane_board_at — fix the primary panic.
When it happens
Trigger: Calling lane_status_json_at(now, stalled_after_secs) when (a) the registry mutex is poisoned, making lane_board_at panic first, or (b) a LaneBoard field type was changed to one with fallible serialization (HashMap with non-string keys, f64 NaN is fine for JSON but custom serializes are not, manually implemented Serialize returning Err).
Common situations: Extending LaneBoard/LaneBoardEntry/LaneHeartbeat with a new field whose type does not reliably serialize to JSON; the compiler cannot warn because expect asserts success at runtime. Also triggered secondarily whenever the TaskRegistry lock is poisoned by a worker panic.
Related errors
- registry lock poisoned
- time should be after epoch
- lsp registry lock poisoned
- mcp registry lock poisoned
- team registry lock poisoned
AI-assisted analysis of ultraworkers/claw-code@08106b0c37 (2026-08-18).
Data as JSON: /api/errors/de9c264933f6e490.
Report an issue: GitHub.