astrid-runtime/astrid · error · anyhow::Error

channel state path has no parent

Error message

channel state path has no parent

What it means

When acquiring the channel lock, the state pointer path returned by state_paths() has no parent directory (a root-level or degenerate path). The lock code requires a directory to create the .{channel}.lock file in.

Solutions

  1. Fix the state-directory override so it points to a real subdirectory (e.g. ~/.astrid/channels)
  2. Unset the offending env var to fall back to the default state path
  3. Ensure the configured path is not the filesystem root

Example fix

// before
ASTRID_STATE_DIR=/ astrid update
// after
ASTRID_STATE_DIR=$HOME/.astrid astrid update
Defensive patterns

Strategy: validation

Validate before calling

if state_dir == "/" || state_dir.is_empty() {
    return Err("state dir must be a real subdirectory".into());
}

Try / catch

match acquire_channel_lock(channel) {
    Ok(lock) => /* proceed */,
    Err(e) if e.to_string().contains("no parent") => eprintln!("fix your state-dir configuration: {e}"),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: acquire_channel_lock computes state_paths(channel) and pointer_path.parent() returns None — only possible if the configured state path is a bare root or empty filename, typically from a bad ASTRID state-dir override.

Common situations: An environment variable or config overriding the channel state directory to "/" or an empty/relative bare path; an embedding launcher passing a malformed state root.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at crates/astrid-cli/src/commands/update_channel.rs:714

    Ok((
        dir.join(format!("{}.toml", channel.as_str())),
        dir.join(format!("{}.toml.sigstore.json", channel.as_str())),
    ))
}

struct ChannelLock(std::fs::File);

impl Drop for ChannelLock {
    fn drop(&mut self) {
        let _ = FileExt::unlock(&self.0);
    }
}

fn acquire_channel_lock(channel: UpdateChannel) -> anyhow::Result<ChannelLock> {
    let (pointer_path, _) = state_paths(channel)?;
    let dir = pointer_path
        .parent()
        .ok_or_else(|| anyhow::anyhow!("channel state path has no parent"))?;
    std::fs::create_dir_all(dir).context("could not create channel state directory")?;
    let lock_path = dir.join(format!(".{}.lock", channel.as_str()));
    let lock = std::fs::OpenOptions::new()
        .create(true)
        .read(true)
        .write(true)
        .truncate(false)
        .open(&lock_path)
        .context("could not open channel update lock")?;
    lock.try_lock_exclusive()
        .context("another Astrid process is already resolving this channel")?;
    Ok(ChannelLock(lock))
}

pub(super) fn enforce_continuity(
    channel: UpdateChannel,
    candidate: &ChannelPointer,
    candidate_bytes: &[u8],

View on GitHub (pinned to affd8760f4)