astrid-runtime/astrid · error

an Astrid daemon appears to be running but its uplink is unr

Error message

an Astrid daemon appears to be running but its uplink is unreachable                  (missing or unlinked system.sock while the PID/lock is live).                  run `astrid restart`

What it means

`astrid status` treats a live recorded PID/lock with a missing or unlinked system.sock as a daemon whose uplink is gone. Because the process is still alive but cannot be contacted, status cannot be answered, and `start` refuses to force-recycle (it must not kill a booting daemon or an innocent recycled PID). The CLI bails with an actionable message directing the user to `astrid restart`, which performs an identity-gated SIGTERM→SIGKILL and respawns cleanly.

Source

Thrown at crates/astrid-cli/src/commands/daemon.rs:581

        },
    }
}

/// Handle `astrid status`.
pub(crate) async fn handle_status(output_format: OutputFormat) -> Result<()> {
    validate_runtime_admission()?;
    let socket_path = socket_client::proxy_socket_path();
    let endpoint_present = astrid_core::local_transport::endpoint_is_present(&socket_path)
        .context("failed to inspect daemon endpoint")
        .unwrap_or(false);
    let recorded_alive = recorded_daemon_pid_is_alive();
    match decide_status_action(endpoint_present, recorded_alive) {
        StatusAction::NotRunning => {
            print_status(output_format, None)?;
            return Ok(());
        },
        StatusAction::RunningButUnreachable => {
            anyhow::bail!(
                "an Astrid daemon appears to be running but its uplink is unreachable                  (missing or unlinked system.sock while the PID/lock is live).                  run `astrid restart`"
            );
        },
        StatusAction::QueryLiveSocket => {},
    }

    let connect = tokio::time::timeout(
        STATUS_CONNECT_TIMEOUT,
        socket_client::connect_kernel_for_workspace(None),
    )
    .await;
    let Ok(Ok(mut client)) = connect else {
        if recorded_alive {
            anyhow::bail!(
                "an Astrid daemon appears to be running but its uplink is unreachable; run `astrid restart`"
            );
        }
        print_status(output_format, None)?;

View on GitHub (pinned to affd8760f4)

Solutions

  1. Run `astrid restart` — it identity-gates the recorded PID, SIGTERM/SIGKILLs it, cleans stale markers, and spawns a fresh daemon.
  2. Inspect the run dir (socket_path/pid_path) to confirm system.sock is actually gone and whether the recorded PID belongs to astrid (ps -p <pid>).
  3. If the PID was recycled by an unrelated process, remove the stale PID/lock files manually before starting.
  4. Prevent socket loss by pointing the runtime dir at a location not swept by tmp cleaners.

Example fix

// before (stale/unlinked socket with live PID)
$ astrid status
Error: an Astrid daemon appears to be running but its uplink is unreachable ... run `astrid restart`
// after
$ astrid restart
$ astrid status  # now reports the running daemon
Defensive patterns

Strategy: validation

Validate before calling

// before scripting around status, check reachability
let endpoint_present = astrid_core::local_transport::endpoint_is_present(&socket_client::proxy_socket_path())?;
let pid_alive = /* read pid file and check /proc */;
if pid_alive && !endpoint_present {
    // skip status; go straight to `astrid restart`
    run(vec!["astrid", "restart"])?;
}

Type guard

fn daemon_is_reachable(endpoint_present: bool, recorded_alive: bool) -> bool {
    // only trust status when the socket exists, not merely when a PID is live
    endpoint_present && recorded_alive
}

Try / catch

match astrid_status() {
    Ok(status) => use(status),
    Err(e) if e.to_string().contains("uplink is unreachable") => run(vec!["astrid", "restart"])?,
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Running `astrid status` when decide_status_action returns RunningButUnreachable: the PID file/lock records a live daemon but endpoint_is_present(proxy_socket_path()) is false — the system.sock was deleted, never created, or unlinked (e.g. tmp cleaner removed it, daemon half-crashed, or stale run-dir).

Common situations: Systemd-tmpfiles or periodic /tmp cleanup deleting the socket; daemon process wedged after SIGSTOP or deadlock so it stopped serving the socket; a crashed daemon whose PID file survived; running the CLI in a different container/mount namespace than the daemon so the socket path differs.

Understand the failure class

Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.

Related errors


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