fix(#2018): surface permission error in hivectl agent lookup instead of misleading 'no such agent'
This commit is contained in:
parent
b0c89af817
commit
49067ad83e
1 changed files with 24 additions and 6 deletions
|
|
@ -933,8 +933,26 @@ fn human_bytes(n: u64) -> String {
|
|||
/// state dir (not the live container list) so kept-state tombstones
|
||||
/// still resolve as agents — re-provisioning a destroyed-but-kept agent
|
||||
/// should still drop its token in the existing state tree.
|
||||
fn is_agent(name: &str) -> bool {
|
||||
Coordinator::agent_state_root(name).exists()
|
||||
///
|
||||
/// Uses `try_exists()` rather than `Path::exists()` so a permission
|
||||
/// error reaching the agents root is surfaced, not collapsed into
|
||||
/// `false`. The agents root is `0700 hive-core`, so running hivectl
|
||||
/// without root yields EACCES on traversal — `Path::exists()` would
|
||||
/// silently report `false`, which callers turn into a misleading "no
|
||||
/// such agent" (or, for the create-user paths, a silent misclassify of
|
||||
/// a real agent as a non-agent account). Mapping EACCES to an explicit
|
||||
/// "needs root" error fixes that first-run footgun (#2018).
|
||||
fn agent_exists(name: &str) -> Result<bool> {
|
||||
let root = Coordinator::agent_state_root(name);
|
||||
match root.try_exists() {
|
||||
Ok(found) => Ok(found),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => bail!(
|
||||
"cannot read the agents root {} (permission denied) - this command needs root; \
|
||||
re-run with sudo",
|
||||
root.parent().unwrap_or(&root).display()
|
||||
),
|
||||
Err(e) => Err(e).with_context(|| format!("check agent state dir {}", root.display())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Drop into an interactive Claude session in the agent container.
|
||||
|
|
@ -973,7 +991,7 @@ fn is_agent(name: &str) -> bool {
|
|||
/// `machinectl shell` inherits the caller's PTY, so the session is
|
||||
/// fully interactive. Requires root and a running container.
|
||||
fn choom(name: &str, fresh: bool) -> Result<()> {
|
||||
if !is_agent(name) {
|
||||
if !agent_exists(name)? {
|
||||
bail!("no such agent: '{name}' (no state dir under /var/lib/hyperhive/agents/)");
|
||||
}
|
||||
let container = hive_c0re::lifecycle::container_name(name);
|
||||
|
|
@ -1023,7 +1041,7 @@ async fn forge_create_user(name: &str, password: Option<&str>, password_stdin: b
|
|||
);
|
||||
}
|
||||
let user_password = resolve_password(password, password_stdin)?;
|
||||
if is_agent(name) {
|
||||
if agent_exists(name)? {
|
||||
if user_password.is_some() {
|
||||
bail!(
|
||||
"forge create-user: --password / --password-stdin is for non-agent (operator) accounts only; '{name}' is an agent which authenticates via API token"
|
||||
|
|
@ -1095,7 +1113,7 @@ async fn matrix_create_user(
|
|||
.build()
|
||||
.context("build reqwest client")?;
|
||||
let user_password = resolve_password(password, password_stdin)?;
|
||||
if is_agent(name) {
|
||||
if agent_exists(name)? {
|
||||
if user_password.is_some() {
|
||||
// The boot-sweep / approval-time agent provisioning path
|
||||
// doesn't accept a password — agents auth by access_token,
|
||||
|
|
@ -1421,7 +1439,7 @@ fn single_agent_scope(name: &str) -> hive_sh4re::LifecycleScope {
|
|||
/// regardless of the migration outcome so a failed migration never leaves
|
||||
/// the agent down; the migration error (if any) is surfaced afterwards.
|
||||
async fn subvol_upgrade(socket: &Path, name: &str, yes: bool) -> Result<()> {
|
||||
if !is_agent(name) {
|
||||
if !agent_exists(name)? {
|
||||
bail!("no agent named {name:?} (no state dir under the agents root)");
|
||||
}
|
||||
if !yes {
|
||||
|
|
|
|||
Loading…
Reference in a new issue