btrfs subvols: scrub tracker tags from comments; harden subvol chown

- Replace the #-number tracker references in code comments with prose
  (tracker-tag lint; hive convention is prose in source).
- ensure_agent_subvolume now treats a chown failure on the freshly
  created subvolume as fatal: it rolls the subvolume back (deletes it)
  and returns an error, instead of warning and leaving a root-owned
  subvol that hive-c0re can't write into (which would also make the
  c0re-side exists-check skip the retry, wedging the agent).
This commit is contained in:
atlas 2026-06-19 13:13:45 +02:00 committed by mara
commit eb103a5660
3 changed files with 28 additions and 13 deletions

View file

@ -660,7 +660,7 @@ pub async fn destroy(coord: &Arc<Coordinator>, name: &str, purge: bool) -> Resul
let _ = std::fs::remove_dir_all(&runtime);
}
if purge {
// The state root may be a btrfs subvolume (#1762): a subvolume root
// The state root may be a btrfs subvolume: a subvolume root
// can't be removed with rmdir/`remove_dir_all`, so delete it via
// hive-priv (root) first. No-op for plain-dir agents — the loop below
// then handles the plain-dir state root plus the applied dir.

View file

@ -878,7 +878,7 @@ pub fn ensure_state_dir(notes_dir: &Path) -> Result<()> {
/// filesystem supports it — BEFORE the per-agent subdirs (`state/`, `claude/`,
/// `harness/`) are created by `ensure_state_dir` / `ensure_claude_dir`.
///
/// Progressive enhancement (the #1762 model): if the root already exists
/// Progressive enhancement: if the root already exists
/// (any agent provisioned before this landed, plain dir or subvol) it's left
/// exactly as-is — no auto-migration — and the priv round-trip is skipped. On
/// a non-btrfs host the priv op no-ops and the root is later created as a

View file

@ -565,17 +565,32 @@ async fn ensure_agent_subvolume(agent_name: &str) -> Result<(String, String)> {
);
}
// The subvol root is created root-owned; hive-c0re (the `hive-core` user)
// must be able to mkdir state/ claude/ harness/ inside it, exactly as it
// would in a plain dir. Match the owner of AGENT_STATE_ROOT (hive-core).
if let Ok(meta) = std::fs::metadata(&root)
&& let Err(e) = std::os::unix::fs::chown(&agent_root, Some(meta.uid()), Some(meta.gid()))
{
tracing::warn!(
agent = %agent_name,
error = %e,
"ensure_agent_subvolume: chown subvol to agents-root owner failed"
);
// The subvol root is created root-owned, but hive-c0re (the `hive-core`
// user) must be able to mkdir state/ claude/ harness/ inside it — exactly
// as it would in a plain dir. Chown it to AGENT_STATE_ROOT's owner
// (hive-core). This MUST succeed: a root-owned subvol would make the
// downstream dir creation fail with a confusing permission error, and the
// c0re-side exists-check would then skip re-running this op on retry,
// wedging the agent. So on any failure roll the subvol back and bail — the
// create path surfaces a clear error and a retry starts clean.
let chown_result = std::fs::metadata(&root)
.with_context(|| format!("stat agents root {} for ownership", root.display()))
.and_then(|meta| {
std::os::unix::fs::chown(&agent_root, Some(meta.uid()), Some(meta.gid())).with_context(
|| format!("chown subvol {} to agents-root owner", agent_root.display()),
)
});
if let Err(e) = chown_result {
// Best-effort rollback so we never leave a root-owned subvol behind.
let _ = Command::new("btrfs")
.args(["subvolume", "delete"])
.arg(&agent_root)
.output()
.await;
return Err(e.context(format!(
"rolled back subvolume {} after chown failed",
agent_root.display()
)));
}
tracing::info!(agent = %agent_name, path = %agent_root.display(), "created agent state subvolume");
Ok((String::new(), String::new()))