{"record":{"id":"de9c264933f6e490","repo":"ultraworkers/claw-code","slug":"lane-board-should-serialize","errorCode":null,"errorMessage":"lane board should serialize","messagePattern":"lane board should serialize","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"rust/crates/runtime/src/task_registry.rs","lineNumber":246,"sourceCode":"                freshness,\n            };\n\n            match task.status {\n                TaskStatus::Running | TaskStatus::Created => board.active.push(entry),\n                TaskStatus::Blocked => board.blocked.push(entry),\n                TaskStatus::Completed | TaskStatus::Failed | TaskStatus::Stopped => {\n                    board.finished.push(entry);\n                }\n            }\n        }\n\n        board\n    }\n\n    #[must_use]\n    pub fn lane_status_json_at(&self, now: u64, stalled_after_secs: u64) -> serde_json::Value {\n        serde_json::to_value(self.lane_board_at(now, stalled_after_secs))\n            .expect(\"lane board should serialize\")\n    }\n\n    pub fn stop(&self, task_id: &str) -> Result<Task, String> {\n        let mut inner = self.inner.lock().expect(\"registry lock poisoned\");\n        let task = inner\n            .tasks\n            .get_mut(task_id)\n            .ok_or_else(|| format!(\"task not found: {task_id}\"))?;\n\n        match task.status {\n            TaskStatus::Completed | TaskStatus::Failed | TaskStatus::Stopped => {\n                return Err(format!(\n                    \"task {task_id} is already in terminal state: {}\",\n                    task.status\n                ));\n            }\n            _ => {}\n        }","sourceCodeStart":228,"sourceCodeEnd":264,"githubUrl":"https://github.com/ultraworkers/claw-code/blob/08106b0c3771ef5b4a5aa176acccd460e88b7325/rust/crates/runtime/src/task_registry.rs#L228-L264","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nserde_json::to_value(self.lane_board_at(now, stalled_after_secs))\n    .expect(\"lane board should serialize\")\n\n// after\nserde_json::to_value(self.lane_board_at(now, stalled_after_secs))\n    .unwrap_or_else(|err| serde_json::json!({\n        \"generated_at\": now,\n        \"serialization_error\": err.to_string(),\n    }))","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"use std::panic::{catch_unwind, AssertUnwindSafe};\n\nlet json = catch_unwind(AssertUnwindSafe(|| {\n    registry.lane_status_json_at(now, stalled_after_secs)\n}))\n.unwrap_or_else(|_| serde_json::json!({\n    \"generated_at\": now,\n    \"error\": \"lane board unavailable (registry poisoned or serialization failed)\",\n}));","preventionTips":["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."],"tags":["rust","serde","serde-json","panic","serialization","task-registry","lane-board"],"backgroundTag":"serde-serialization-failed","analyzedSha":"08106b0c3771ef5b4a5aa176acccd460e88b7325","analyzedAt":"2026-08-18T00:29:38.590Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}