astrid-runtime/astrid · error

shutdown stage daemon.singleton_lock: failed to acquire {}:

Error message

shutdown stage daemon.singleton_lock: failed to acquire {}: {error}

What it means

The sibling case of the singleton-lock shutdown stage: `try_lock()` failed with `TryLockError::Error(error)`, an actual OS error (as opposed to the lock simply being held). `cleanup_daemon_runtime_for_home` maps that error into this shutdown-stage message including the lock path and the underlying {error}. The runtime cleanup aborts because the singleton state cannot be verified as free.

Source

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

    let mut options = std::fs::OpenOptions::new();
    options.read(true).write(true).create(true);
    #[cfg(unix)]
    {
        use std::os::unix::fs::OpenOptionsExt as _;
        options.mode(0o600);
    }
    let lock = options.open(&lock_path).with_context(|| {
        format!(
            "shutdown stage daemon.singleton_lock: open {}",
            lock_path.display()
        )
    })?;
    lock.try_lock().map_err(|error| match error {
        std::fs::TryLockError::WouldBlock => anyhow::anyhow!(
            "shutdown stage daemon.singleton_lock: lock remains held at {}",
            lock_path.display()
        ),
        std::fs::TryLockError::Error(error) => anyhow::anyhow!(
            "shutdown stage daemon.singleton_lock: failed to acquire {}: {error}",
            lock_path.display()
        ),
    })?;

    match astrid_core::local_transport::connect_outcome(socket_path)
        .await
        .context("shutdown stage daemon.listener_probe")?
    {
        astrid_core::local_transport::ConnectOutcome::Connected(_) => {
            anyhow::bail!(
                "shutdown stage daemon.listener_absence: endpoint remains live at {}",
                socket_path.display()
            );
        },
        astrid_core::local_transport::ConnectOutcome::Stale => {
            astrid_core::local_transport::remove_stale_endpoint(socket_path).with_context(
                || {

View on GitHub (pinned to affd8760f4)

Solutions

  1. Read the wrapped {error} for the concrete errno (ENOENT, EACCES, EROFS) and fix that condition.
  2. Restore permissions on the home's runtime directory: `chown -R` / `chmod u+rw` the lock path.
  3. Recreate the home runtime layout if the lock file was deleted, then retry stop/cleanup.
  4. If the filesystem went read-only, remount it read-write or move ASTRID_HOME to a healthy local disk.
Defensive patterns

Strategy: try-catch

Validate before calling

let lock_path = home.runtime_dir().join("daemon.singleton.lock");
let meta = std::fs::metadata(&lock_path)
    .map_err(|e| anyhow!("singleton lock {} unreadable before cleanup: {e}", lock_path.display()))?;
if meta.permissions().readonly() {
    return Err(anyhow!("singleton lock {} is read-only", lock_path.display()));
}

Try / catch

match cleanup_daemon_runtime() {
    Err(e) if e.to_string().contains("failed to acquire") => {
        let cause = /* extract wrapped error */;
        eprintln!("singleton lock acquire failed: {cause}; check permissions/mount state");
        // do NOT force-remove markers; surface to the operator
    }
    other => other,
}

Prevention

When it happens

Trigger: Cleanup runs but acquiring the singleton lock fails due to I/O problems: lock file deleted mid-flight, permission denied on the lock file, filesystem error, or an OS-level flock failure.

Common situations: Runtime directory permissions changed after daemon start; lock file removed by an external cleaner while the daemon runs; disk/full or read-only remount of the home filesystem.

Understand the failure class

Background: "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it — this error's family across 42 libraries.

Related errors


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