{"record":{"id":"21280cdb7a666845","repo":"tinyhumansai/openhuman","slug":"capturingobserver-steps-mutex-poisoned","errorCode":null,"errorMessage":"CapturingObserver steps mutex poisoned","messagePattern":"CapturingObserver steps mutex poisoned","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/openhuman/flows/builder_tools.rs","lineNumber":3463,"sourceCode":"/// node's [`ExecutionStep`](tinyflows::observability::ExecutionStep) — in\n/// particular its `diagnostics` (null-resolved `=`-expressions the engine\n/// traced during that node's config resolution) — so [`DryRunWorkflowTool`]\n/// can inspect them once the sandbox run settles. See the struct's \"Null-\n/// resolution check\" doc for why this exists.\n/// `pub(crate)` (not private) so [`crate::openhuman::flows::ops::validate_required_arg_resolvability`]\n/// (issue B18 — escalating a null-resolved REQUIRED outbound arg to a hard\n/// authoring-time reject) can run the identical sandbox-capture shape without\n/// duplicating this struct.\n#[derive(Default)]\npub(crate) struct CapturingObserver {\n    steps: std::sync::Mutex<Vec<tinyflows::observability::ExecutionStep>>,\n}\n\nimpl tinyflows::observability::RunObserver for CapturingObserver {\n    fn on_step_finish(&self, step: &tinyflows::observability::ExecutionStep) {\n        self.steps\n            .lock()\n            .expect(\"CapturingObserver steps mutex poisoned\")\n            .push(step.clone());\n    }\n}\n\nimpl CapturingObserver {\n    /// A snapshot of every step recorded so far (steps are pushed\n    /// synchronously from `on_step_finish`, so once the run's future resolves\n    /// every step it will ever record is already present).\n    pub(crate) fn steps(&self) -> Vec<tinyflows::observability::ExecutionStep> {\n        self.steps\n            .lock()\n            .expect(\"CapturingObserver steps mutex poisoned\")\n            .clone()\n    }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// save_workflow — persist a built graph onto an EXISTING saved flow","sourceCodeStart":3445,"sourceCodeEnd":3481,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/flows/builder_tools.rs#L3445-L3481","documentation":"Mutex poisoning on CapturingObserver's steps mutex in the flows dry-run tooling: the observer collects ExecutionStep records (including null-resolution diagnostics) during the sandbox run; if a panic unwound while that mutex was held, later lock() calls in DryRunWorkflowTool's inspection path hit PoisonError and this expect fires. The dry-run result is lost; the originating panic is the real fault.","triggerScenarios":"Thrown at src/openhuman/flows/builder_tools.rs:3463 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Fix the panic that occurred mid-capture (visible earlier in the run log)","Scope the steps lock to the shortest possible region so engine panics cannot hold it","Recover with into_inner() if partial captured steps are still useful for diagnostics"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}