{"record":{"id":"943c8e7c945ea5af","repo":"Hmbown/CodeWhale","slug":"persistent-service-id-disappeared-during-commit","errorCode":null,"errorMessage":"Persistent service {id} disappeared during commit","messagePattern":"Persistent service (.+?) disappeared during commit","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/shell.rs","lineNumber":2907,"sourceCode":"            }\n            if shell\n                .child\n                .as_ref()\n                .and_then(ShellChild::process_id)\n                .is_none()\n            {\n                return Err(anyhow!(\n                    \"Persistent service {id} has no releasable process id\"\n                ));\n            }\n        }\n\n        let mut receipts = Vec::with_capacity(ids.len());\n        for id in ids {\n            let mut shell = self\n                .processes\n                .remove(&id)\n                .ok_or_else(|| anyhow!(\"Persistent service {id} disappeared during commit\"))?;\n            let pid = shell\n                .child\n                .as_ref()\n                .and_then(ShellChild::process_id)\n                .ok_or_else(|| anyhow!(\"Persistent service {id} lost its process id\"))?;\n            unregister_pending_persistent_process_group(pid);\n            shell.ownership = ShellOwnership::Released;\n            shell.stdin = None;\n            shell.heavy_permit.take();\n            shell.work_lifecycle = None;\n            receipts.push(PersistentServiceReceipt {\n                task_id: id,\n                pid,\n                process_group_id: pid,\n                ownership: \"external\".to_string(),\n            });\n        }\n        Ok(receipts)","sourceCodeStart":2889,"sourceCodeEnd":2925,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/tools/shell.rs#L2889-L2925","documentation":"The second phase of `commit_persistent_services` removes each validated ID from `processes` to build the receipt; this error fires when the entry vanishes after the validation pass but before its removal. Single-threaded, the loops are back-to-back, so hitting it means the map was mutated inside the commit — an invariant breach, not a usage error.","triggerScenarios":"A re-entrant manager call (kill, kill_all, cleanup, spawn) removing a PersistPending entry between the validation loop and the removal loop of the same commit invocation.","commonSituations":"Multi-threaded test harnesses sharing one ShellManager; new product code that reacts to commit progress by mutating the manager.","solutions":["Serialize all ShellManager access so commit runs to completion without interleaving calls","Treat as a bug: log the task_id and audit what mutated the map mid-commit","Re-derive the pending set and retry commit for the surviving services"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match manager.commit_persistent_services() {\n    Ok(receipts) => receipts,\n    Err(err) if err.to_string().contains(\"disappeared during commit\") => {\n        // map mutated between commit's validation and removal loops: invariant breach, log it\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["Serialize manager access so commit completes without interleaving calls","Audit any code that reacts to commit progress by mutating the manager","Re-derive the pending set and retry commit for surviving services","Treat recurrence as a concurrency bug, not a flake to ignore"],"tags":["persistent-services","invariant","concurrency","unix","rust"],"backgroundTag":"background-job-registry-race","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}