Compare commits

..
Author SHA1 Message Date
atlas
7e229889a6 fix: address argus review on ensure_claude_dir — narrow to EPERM, fix doc comment 2026-06-04 15:03:12 +02:00
atlas
e553e40577 fix: soft-fail chmod in ensure_claude_dir when dir is agent-owned
hive-agent-user-migrate chowns the bind-mounted claude dir to the agent
user on every container boot. After that, hive-core (a different user)
cannot chmod it — set_permissions fails with EPERM, which was propagated
as an error and caused the rebuild to fail entirely.

Fix: make the chmod best-effort. Newly created dirs (owned by hive-core)
get the 0755 mode set immediately; after the agent-migration chown the
mode is preserved so claude_has_session works correctly. Subsequent calls
that hit the EPERM path just log at DEBUG and continue.
2026-06-04 14:48:08 +02:00

View file

@ -767,12 +767,15 @@ pub async fn setup_applied(
Ok(()) Ok(())
} }
/// Create the per-agent Claude credentials dir if missing. Mode 0700 — only /// Create the per-agent Claude credentials dir if missing. Mode 0755 — hive-core
/// root inside the container reads/writes it. Idempotent: existing dirs are /// needs read+execute to list the directory so `claude_has_session` can detect a
/// left untouched (an agent's OAuth tokens survive `destroy`/recreate). /// valid session; credential files inside (`.credentials.json` etc.) are 0600 so
/// secrets stay private regardless of the directory mode. Idempotent: existing
/// dirs are left untouched (an agent's OAuth tokens survive `destroy`/recreate).
/// Public for the `InitConfig` approval path in `actions.rs` which seeds /// Public for the `InitConfig` approval path in `actions.rs` which seeds
/// dirs without calling the full `spawn`. /// dirs without calling the full `spawn`.
pub fn ensure_claude_dir(claude_dir: &Path) -> Result<()> { pub fn ensure_claude_dir(claude_dir: &Path) -> Result<()> {
use std::io;
if !claude_dir.exists() { if !claude_dir.exists() {
std::fs::create_dir_all(claude_dir) std::fs::create_dir_all(claude_dir)
.with_context(|| format!("create {}", claude_dir.display()))?; .with_context(|| format!("create {}", claude_dir.display()))?;
@ -781,11 +784,27 @@ pub fn ensure_claude_dir(claude_dir: &Path) -> Result<()> {
// list the directory so `claude_has_session` can detect a valid session. // list the directory so `claude_has_session` can detect a valid session.
// The credential files inside (`.credentials.json` etc.) are 0600 so the // The credential files inside (`.credentials.json` etc.) are 0600 so the
// secrets themselves stay private regardless of the directory mode. // secrets themselves stay private regardless of the directory mode.
//
// Best-effort: on the first container boot, `hive-agent-user-migrate`
// chowns this dir to the agent user. After that, hive-core (a different
// user) cannot chmod it (EPERM) — that's fine because the mode set during
// initial creation (0755) is preserved through the chown. Any other error
// (ENOENT, I/O error) is unexpected and propagated.
#[cfg(unix)] #[cfg(unix)]
{ {
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(claude_dir, std::fs::Permissions::from_mode(0o755)) match std::fs::set_permissions(claude_dir, std::fs::Permissions::from_mode(0o755)) {
.with_context(|| format!("chmod 755 {}", claude_dir.display()))?; Ok(()) => {}
Err(e) if e.kind() == io::ErrorKind::PermissionDenied => {
tracing::debug!(
path = %claude_dir.display(),
"ensure_claude_dir: chmod 755 skipped (dir likely owned by agent user after migration)"
);
}
Err(e) => {
return Err(e).with_context(|| format!("chmod 755 {}", claude_dir.display()));
}
}
} }
Ok(()) Ok(())
} }