{"record":{"id":"10bda2dd6373cf99","repo":"astrid-runtime/astrid","slug":"channel-state-path-has-no-parent","errorCode":null,"errorMessage":"channel state path has no parent","messagePattern":"channel state path has no parent","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/update_channel.rs","lineNumber":714,"sourceCode":"    Ok((\n        dir.join(format!(\"{}.toml\", channel.as_str())),\n        dir.join(format!(\"{}.toml.sigstore.json\", channel.as_str())),\n    ))\n}\n\nstruct ChannelLock(std::fs::File);\n\nimpl Drop for ChannelLock {\n    fn drop(&mut self) {\n        let _ = FileExt::unlock(&self.0);\n    }\n}\n\nfn acquire_channel_lock(channel: UpdateChannel) -> anyhow::Result<ChannelLock> {\n    let (pointer_path, _) = state_paths(channel)?;\n    let dir = pointer_path\n        .parent()\n        .ok_or_else(|| anyhow::anyhow!(\"channel state path has no parent\"))?;\n    std::fs::create_dir_all(dir).context(\"could not create channel state directory\")?;\n    let lock_path = dir.join(format!(\".{}.lock\", channel.as_str()));\n    let lock = std::fs::OpenOptions::new()\n        .create(true)\n        .read(true)\n        .write(true)\n        .truncate(false)\n        .open(&lock_path)\n        .context(\"could not open channel update lock\")?;\n    lock.try_lock_exclusive()\n        .context(\"another Astrid process is already resolving this channel\")?;\n    Ok(ChannelLock(lock))\n}\n\npub(super) fn enforce_continuity(\n    channel: UpdateChannel,\n    candidate: &ChannelPointer,\n    candidate_bytes: &[u8],","sourceCodeStart":696,"sourceCodeEnd":732,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/update_channel.rs#L696-L732","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Fix the state-directory override so it points to a real subdirectory (e.g. ~/.astrid/channels)","Unset the offending env var to fall back to the default state path","Ensure the configured path is not the filesystem root"],"exampleFix":"// before\nASTRID_STATE_DIR=/ astrid update\n// after\nASTRID_STATE_DIR=$HOME/.astrid astrid update","handlingStrategy":"validation","validationCode":"if state_dir == \"/\" || state_dir.is_empty() {\n    return Err(\"state dir must be a real subdirectory\".into());\n}","typeGuard":null,"tryCatchPattern":"match acquire_channel_lock(channel) {\n    Ok(lock) => /* proceed */,\n    Err(e) if e.to_string().contains(\"no parent\") => eprintln!(\"fix your state-dir configuration: {e}\"),\n    Err(e) => return Err(e),\n}","preventionTips":["Never point state dirs at \"/\" or empty paths","Use the library's default state location unless you have a reason","Validate env overrides at startup"],"tags":["filesystem","locking","state-path"],"backgroundTag":"invalid-config-value","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}