diff --git a/hive-c0re/src/lifecycle.rs b/hive-c0re/src/lifecycle.rs index ba9a57dc..b3dbe8bd 100644 --- a/hive-c0re/src/lifecycle.rs +++ b/hive-c0re/src/lifecycle.rs @@ -1137,6 +1137,22 @@ async fn set_nspawn_flags( // Ensure /shared directory exists before binding. systemd-nspawn requires the bind source to exist. std::fs::create_dir_all(HOST_SHARED_ROOT) .with_context(|| format!("create {HOST_SHARED_ROOT}"))?; + // Make /shared writable by every agent. Containers share host uids (no + // PrivateUsers), but each agent is a distinct unix user, so a root-owned + // 0755 dir leaves them unable to write — the documented "read/write for + // all agents" contract was broken (#1374). A setgid group would need a + // pinned GID declared in every container plus all agent users joined to + // it (cross-container coordination + a rebuild cascade); instead we use + // the /tmp model — sticky world-writable (1777). The sticky bit lets any + // agent create files while protecting each agent's entries from deletion + // by the others, and matches /shared's documented "free-for-all, may be + // deleted/lost" semantics without touching any per-agent config. + { + use std::os::unix::fs::PermissionsExt as _; + let perms = std::fs::Permissions::from_mode(0o1777); + std::fs::set_permissions(HOST_SHARED_ROOT, perms) + .with_context(|| format!("chmod 1777 {HOST_SHARED_ROOT}"))?; + } // Ensure /knowledge dir exists. It may be empty until forge seeds it; // nspawn refuses to start if the bind source is missing entirely. std::fs::create_dir_all(crate::knowledge::LOCAL_DIR)