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

  1. 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).
  2. Grep the LaneBoard/LaneBoardEntry/LaneHeartbeat/TaskStatus types for custom Serialize impls or non-string-keyed maps and replace them with plainly serializable types.
  3. Replace the expect with graceful degradation so a serialization failure cannot abort the CLI: return serde_json::json!({"error": ...}) on Err.
  4. 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

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


AI-assisted analysis of ultraworkers/claw-code@08106b0c37 (2026-08-18). Data as JSON: /api/errors/de9c264933f6e490. Report an issue: GitHub.