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

  1. Run `astrid restart` as the message says: it stops the recorded daemon and boots a fresh one cleanly.
  2. 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.
  3. Verify the socket path matches the current workspace/state dir (ASTRID_WORKSPACE_STATE_DIR env) so the CLI probes the right endpoint.
  4. 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

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


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/f8665033917ec5d0. Report an issue: GitHub.