{"record":{"id":"0c2738b0331b09f8","repo":"astrid-runtime/astrid","slug":"shutdown-stage-daemon-singleton-lock-lock-remains","errorCode":null,"errorMessage":"shutdown stage daemon.singleton_lock: lock remains held at {}","messagePattern":"shutdown stage daemon\\.singleton_lock: lock remains held at (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/daemon.rs","lineNumber":894,"sourceCode":"    if !runtime_present {\n        return Ok(());\n    }\n    let lock_path = home.run_dir().join(\"system.lock\");\n    let mut options = std::fs::OpenOptions::new();\n    options.read(true).write(true).create(true);\n    #[cfg(unix)]\n    {\n        use std::os::unix::fs::OpenOptionsExt as _;\n        options.mode(0o600);\n    }\n    let lock = options.open(&lock_path).with_context(|| {\n        format!(\n            \"shutdown stage daemon.singleton_lock: open {}\",\n            lock_path.display()\n        )\n    })?;\n    lock.try_lock().map_err(|error| match error {\n        std::fs::TryLockError::WouldBlock => anyhow::anyhow!(\n            \"shutdown stage daemon.singleton_lock: lock remains held at {}\",\n            lock_path.display()\n        ),\n        std::fs::TryLockError::Error(error) => anyhow::anyhow!(\n            \"shutdown stage daemon.singleton_lock: failed to acquire {}: {error}\",\n            lock_path.display()\n        ),\n    })?;\n\n    match astrid_core::local_transport::connect_outcome(socket_path)\n        .await\n        .context(\"shutdown stage daemon.listener_probe\")?\n    {\n        astrid_core::local_transport::ConnectOutcome::Connected(_) => {\n            anyhow::bail!(\n                \"shutdown stage daemon.listener_absence: endpoint remains live at {}\",\n                socket_path.display()\n            );","sourceCodeStart":876,"sourceCodeEnd":912,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/daemon.rs#L876-L912","documentation":"A shutdown-stage error emitted by `cleanup_daemon_runtime_for_home` when `try_lock()` on the daemon singleton lock file returns `TryLockError::WouldBlock`, meaning another process still holds the lock. Cleanup refuses to proceed so it cannot delete runtime state (markers, socket) out from under a live daemon. The message includes the lock path for diagnosis.","triggerScenarios":"Running `astrid daemon stop`/cleanup for a home whose singleton lock is currently held by a running daemon or another CLI process; calling cleanup while a live listener is still serving on the socket.","commonSituations":"Attempted cleanup while the daemon is genuinely still running (stop didn't complete); an orphaned process holds the lock after a crash; tests concurrently starting/stopping daemons for the same home.","solutions":["Let the running daemon finish: wait for the lock holder to exit, then re-run cleanup/stop.","Confirm no live listener remains (check `astrid daemon status` / the socket) before forcing cleanup.","Find the holder with `lsof <lock_path>` and kill the stale process if it's truly orphaned.","Re-run the stop command; cleanup is idempotent once the lock is free."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// preflight: is a daemon still alive for this home?\nif astrid_cli::local_transport::connect_outcome(&socket_path).is_ok() {\n    return Err(anyhow!(\"daemon still live at {socket_path}; stop it before cleanup\"));\n}","typeGuard":null,"tryCatchPattern":"match cleanup_daemon_runtime() {\n    Err(e) if e.to_string().contains(\"lock remains held\") => {\n        eprintln!(\"another daemon still holds the singleton lock; waiting...\");\n        wait_for_lock_release(lock_path, timeout)?;\n        cleanup_daemon_runtime()\n    }\n    other => other,\n}","preventionTips":["Always stop the daemon through `astrid daemon stop` before any manual runtime cleanup.","Use `astrid daemon status` (or probe the socket) as a preflight to cleanup.","Monitor for orphaned daemon processes after crashes and reap them.","Serialize daemon lifecycle operations per home in scripts/tests (don't run stop/start concurrently)."],"tags":["daemon","shutdown","file-lock","concurrency"],"backgroundTag":"file-lock-contention","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}