From bc0087830f01067253ac99e3662bb5c881a5ca18 Mon Sep 17 00:00:00 2001 From: atlas Date: Thu, 4 Jun 2026 11:37:42 +0200 Subject: [PATCH] fix: chmod claude dir 0755 so hive-core can list session files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit claude_has_session() calls read_dir() on the agent's claude/ dir to detect a valid session. The dir was 0700 (create-only path), so hive-core (a different unix user) got EACCES → read_dir returns Err → has_session returns false → every agent shows 'needs login' in the dashboard even when working. Fix: ensure_claude_dir now always sets 0755. The credential files inside (e.g. .credentials.json) are 0600, so the secrets stay private regardless of the containing directory's mode. Existing 0700 dirs are corrected on the next spawn/rebuild cycle that calls ensure_claude_dir. --- hive-c0re/src/lifecycle.rs | 40 +++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/hive-c0re/src/lifecycle.rs b/hive-c0re/src/lifecycle.rs index 77c009d5..dd635bf8 100644 --- a/hive-c0re/src/lifecycle.rs +++ b/hive-c0re/src/lifecycle.rs @@ -242,7 +242,13 @@ pub async fn spawn(name: &str, hive: &HiveEnv, paths: &AgentPaths) -> Result<()> crate::meta::sync_agents(hive, &agents).await?; let container = container_name(name); priv_run("create", name).await?; - set_nspawn_flags(&container, &paths.agent_dir, &paths.claude_dir, &paths.notes_dir).await?; + set_nspawn_flags( + &container, + &paths.agent_dir, + &paths.claude_dir, + &paths.notes_dir, + ) + .await?; set_resource_limits(&container, &hive.agent_cpu_quota, &hive.agent_memory_max).await?; systemd_daemon_reload().await?; priv_run("start", name).await @@ -418,7 +424,13 @@ pub async fn rebuild_no_meta( // Rebuild strategy: stop-before-update + pre-build. // See `docs/coordinator.md::Container lifecycle`. let was_running = is_running(name).await; - set_nspawn_flags(&container, &paths.agent_dir, &paths.claude_dir, &paths.notes_dir).await?; + set_nspawn_flags( + &container, + &paths.agent_dir, + &paths.claude_dir, + &paths.notes_dir, + ) + .await?; set_resource_limits(&container, &hive.agent_cpu_quota, &hive.agent_memory_max).await?; systemd_daemon_reload().await?; if was_running { @@ -490,7 +502,13 @@ pub async fn rebuild_no_meta( // See `docs/coordinator.md::Spawn path`. on_step("nixos-container create"); priv_run("create", name).await?; - set_nspawn_flags(&container, &paths.agent_dir, &paths.claude_dir, &paths.notes_dir).await?; + set_nspawn_flags( + &container, + &paths.agent_dir, + &paths.claude_dir, + &paths.notes_dir, + ) + .await?; set_resource_limits(&container, &hive.agent_cpu_quota, &hive.agent_memory_max).await?; systemd_daemon_reload().await?; on_step("nixos-container start"); @@ -758,12 +776,16 @@ pub fn ensure_claude_dir(claude_dir: &Path) -> Result<()> { if !claude_dir.exists() { std::fs::create_dir_all(claude_dir) .with_context(|| format!("create {}", claude_dir.display()))?; - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - std::fs::set_permissions(claude_dir, std::fs::Permissions::from_mode(0o700)) - .with_context(|| format!("chmod {}", claude_dir.display()))?; - } + } + // 0755: hive-core (different user from the agent) needs read+execute to + // list the directory so `claude_has_session` can detect a valid session. + // The credential files inside (`.credentials.json` etc.) are 0600 so the + // secrets themselves stay private regardless of the directory mode. + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions(claude_dir, std::fs::Permissions::from_mode(0o755)) + .with_context(|| format!("chmod 755 {}", claude_dir.display()))?; } Ok(()) }