astrid-runtime/astrid · error
an Astrid daemon is recorded as running (PID file) but its u
Error message
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
What it means
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).
Source
Thrown at crates/astrid-cli/src/commands/daemon.rs:219
let socket_path = socket_client::proxy_socket_path();
let ready_path = socket_client::readiness_path();
let outcome = astrid_core::local_transport::connect_outcome(&socket_path)
.await
.context("failed to probe daemon endpoint")?;
let action = decide_ensure_action(&outcome, recorded_daemon_pid_is_alive());
let needs_boot = match action {
EnsureAction::UseExisting => {
if let astrid_core::local_transport::ConnectOutcome::Connected(stream) = outcome {
drop(stream);
}
ensure_daemon_workspace_matches(workspace_root).await?;
if announce {
eprintln!("[{label}] Connected to existing daemon");
}
false
},
EnsureAction::RefuseSecondBoot => {
anyhow::bail!(unreachable_uplink_message());
},
EnsureAction::CleanStaleAndSpawn => {
astrid_core::local_transport::remove_stale_endpoint(&socket_path)
.context("failed to clean up stale daemon endpoint")?;
let _ = std::fs::remove_file(&ready_path);
true
},
EnsureAction::Spawn => true,
};
if needs_boot {
match spawn_mode {
DaemonSpawnMode::Ephemeral => {
spawn_daemon_inner(&ready_path, announce, None).await?;
},
DaemonSpawnMode::Persistent => spawn_persistent_daemon().await?,
}
ensure_daemon_workspace_matches(workspace_root).await?;
}View on GitHub (pinned to affd8760f4)
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.
Example fix
# before $ astrid list Error: an Astrid daemon is recorded as running (PID file) but its uplink is unreachable... # after $ astrid restart $ astrid list # OK
Defensive patterns
Strategy: validation
Validate before calling
// Before any ensure_daemon call, confirm PID/socket agreement
fn daemon_state_consistent(socket_path: &Path, pid_path: &Path) -> Result<(), String> {
let pid_alive = std::fs::read_to_string(pid_path).ok()
.and_then(|s| s.trim().parse::<i32>().ok())
.map(|pid| daemon_control::is_process_alive(pid))
.unwrap_or(false);
let socket_live = socket_path.exists();
if pid_alive && !socket_live { Err("recorded daemon unreachable; run `astrid restart`".into()) } else { Ok(()) }
} Try / catch
match ensure_daemon(label).await {
Err(e) if e.to_string().contains("uplink is unreachable") => {
eprintln!("stale/hung daemon detected; running `astrid restart`");
daemon_control::stop_daemon().await?; // or shell out to `astrid restart`
ensure_daemon(label).await
},
other => other,
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- daemon metadata lookup failed: {error}
- unexpected daemon metadata response: {other:?}
- daemon rejected capsule metadata request: {message}
- unexpected daemon response: {other:?}
- daemon rejected capsule metadata request: {message}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/f8665033917ec5d0.
Report an issue: GitHub.