{"record":{"id":"f8665033917ec5d0","repo":"astrid-runtime/astrid","slug":"an-astrid-daemon-is-recorded-as-running-pid-file","errorCode":null,"errorMessage":"an Astrid daemon is recorded as running (PID file) but its uplink is unreachable;      run `astrid restart` instead of starting a second kernel onto the singleton lock","messagePattern":"an Astrid daemon is recorded as running \\(PID file\\) but its uplink is unreachable;      run `astrid restart` instead of starting a second kernel onto the singleton lock","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/daemon.rs","lineNumber":219,"sourceCode":"    let socket_path = socket_client::proxy_socket_path();\n    let ready_path = socket_client::readiness_path();\n    let outcome = astrid_core::local_transport::connect_outcome(&socket_path)\n        .await\n        .context(\"failed to probe daemon endpoint\")?;\n    let action = decide_ensure_action(&outcome, recorded_daemon_pid_is_alive());\n    let needs_boot = match action {\n        EnsureAction::UseExisting => {\n            if let astrid_core::local_transport::ConnectOutcome::Connected(stream) = outcome {\n                drop(stream);\n            }\n            ensure_daemon_workspace_matches(workspace_root).await?;\n            if announce {\n                eprintln!(\"[{label}] Connected to existing daemon\");\n            }\n            false\n        },\n        EnsureAction::RefuseSecondBoot => {\n            anyhow::bail!(unreachable_uplink_message());\n        },\n        EnsureAction::CleanStaleAndSpawn => {\n            astrid_core::local_transport::remove_stale_endpoint(&socket_path)\n                .context(\"failed to clean up stale daemon endpoint\")?;\n            let _ = std::fs::remove_file(&ready_path);\n            true\n        },\n        EnsureAction::Spawn => true,\n    };\n    if needs_boot {\n        match spawn_mode {\n            DaemonSpawnMode::Ephemeral => {\n                spawn_daemon_inner(&ready_path, announce, None).await?;\n            },\n            DaemonSpawnMode::Persistent => spawn_persistent_daemon().await?,\n        }\n        ensure_daemon_workspace_matches(workspace_root).await?;\n    }","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/daemon.rs#L201-L237","documentation":"The ensure-daemon logic consults a PID file: if a daemon is recorded as running (the PID is alive) but the CLI cannot connect to its IPC socket (uplink unreachable), starting a second kernel would collide with the singleton lock. Instead of spawning, the CLI refuses with this message and directs the operator to `astrid restart`. It protects the single-kernel invariant when the daemon is alive but not accepting connections (hung, wrong socket path, or socket replaced).","triggerScenarios":"ensure_daemon_inner -> ensure_daemon_inner_locked hitting EnsureAction::RefuseSecondBoot: recorded_daemon_pid_is_alive() is true while connect_outcome() on the proxy socket path fails or finds no listener — e.g. a hung daemon, a stale/removed socket file owned by a live process, or a socket path mismatch (different ASTRID_WORKSPACE_STATE_DIR / workspace).","commonSituations":"Daemon wedged after heavy load; daemon started with a different workspace so the CLI probes the wrong socket; container/namespace where the PID lives but the socket isn't visible; PID reused by an unrelated process after an unclean shutdown.","solutions":["Run `astrid restart` as the message says: it stops the recorded daemon and boots a fresh one cleanly.","Check `astrid status` and confirm the recorded PID actually belongs to an astrid daemon (`ps -p <pid>`); kill it manually if it is a stale/reused PID.","Verify the socket path matches the current workspace/state dir (ASTRID_WORKSPACE_STATE_DIR env) so the CLI probes the right endpoint.","If the daemon is merely hung, inspect its log for a deadlock before restarting to avoid losing in-flight work."],"exampleFix":"# before\n$ astrid list\nError: an Astrid daemon is recorded as running (PID file) but its uplink is unreachable...\n# after\n$ astrid restart\n$ astrid list  # OK","handlingStrategy":"validation","validationCode":"// Before any ensure_daemon call, confirm PID/socket agreement\nfn daemon_state_consistent(socket_path: &Path, pid_path: &Path) -> Result<(), String> {\n    let pid_alive = std::fs::read_to_string(pid_path).ok()\n        .and_then(|s| s.trim().parse::<i32>().ok())\n        .map(|pid| daemon_control::is_process_alive(pid))\n        .unwrap_or(false);\n    let socket_live = socket_path.exists();\n    if pid_alive && !socket_live { Err(\"recorded daemon unreachable; run `astrid restart`\".into()) } else { Ok(()) }\n}","typeGuard":null,"tryCatchPattern":"match ensure_daemon(label).await {\n    Err(e) if e.to_string().contains(\"uplink is unreachable\") => {\n        eprintln!(\"stale/hung daemon detected; running `astrid restart`\");\n        daemon_control::stop_daemon().await?; // or shell out to `astrid restart`\n        ensure_daemon(label).await\n    },\n    other => other,\n}","preventionTips":["Always use `astrid restart` (never start a second daemon) when the PID file says a daemon is running.","Verify the PID belongs to an astrid process before killing it (`ps -p <pid>`).","Keep ASTRID_WORKSPACE_STATE_DIR consistent so CLI and daemon agree on the socket path.","Set up a supervisor/health check so hung daemons are detected and restarted promptly."],"tags":["daemon","singleton-lock","ipc","stale-state"],"backgroundTag":"daemon-uplink-unreachable","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}