fix(teardown): a mission dir with root-owned files is now actually removed
The server runs as uid 65532, so `remove_dir_all` on a mission directory returns PermissionDenied the moment anything root-owned is left in it — and the old code logged that at the same level as "file not found" and moved on. The directory then lived forever. After the seed-copy fix a mission holds 3281 files owned by 65532 and 26 owned by root: `.claude.json` and the session jsonl the per-mission ZeroClaw daemon writes itself, after the copy has been chowned. Twenty-six files is small enough to keep every mission directory alive without anyone noticing why. PermissionDenied now falls back to `root_copy::purge`, which deletes from inside the container as root — the same escape hatch `container_exec` keeps for exactly this, clearing debris a root process created. And if the directory survives even that, it says so, because a cleanup that silently failed is the thing being fixed. Removing the last 26 properly means running the per-mission daemon as 65532, which needs `/mission` pre-created in the image with that ownership — the daemon creates it at boot today and cannot at a lower uid. That is an image change, deliberately not bundled here. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
4967b9b8fd
commit
f68fc019e4
@@ -1023,8 +1023,30 @@ impl MissionRuntimeProvisioner {
|
|||||||
// path is bind-mounted into cm-api, so we can reap it directly.
|
// path is bind-mounted into cm-api, so we can reap it directly.
|
||||||
let mission_dir = format!("{}/{mission_id}", missions_host_root());
|
let mission_dir = format!("{}/{mission_id}", missions_host_root());
|
||||||
if let Err(e) = tokio::fs::remove_dir_all(&mission_dir).await {
|
if let Err(e) = tokio::fs::remove_dir_all(&mission_dir).await {
|
||||||
if e.kind() != std::io::ErrorKind::NotFound {
|
match e.kind() {
|
||||||
eprintln!("mission_runtime: rm workspace dir {mission_dir}: {e}");
|
std::io::ErrorKind::NotFound => {}
|
||||||
|
// The server runs as 65532 and cannot delete root-owned files.
|
||||||
|
// Almost everything under a mission is 65532 now, but the
|
||||||
|
// per-mission ZeroClaw daemon still runs as root and leaves ~26
|
||||||
|
// of its own files (`.claude.json`, session jsonl) behind after
|
||||||
|
// the seed copy has been chowned. Without this fallback those
|
||||||
|
// few files keep the whole directory alive forever — the
|
||||||
|
// cleanup-that-cannot-clean-up shape, at a scale small enough
|
||||||
|
// to go unnoticed for a long time.
|
||||||
|
std::io::ErrorKind::PermissionDenied => {
|
||||||
|
eprintln!(
|
||||||
|
"mission_runtime: {mission_dir} holds root-owned files — removing it from inside the runtime container"
|
||||||
|
);
|
||||||
|
let container = std::env::var("CLAWMATES_RUNTIME_CONTAINER")
|
||||||
|
.unwrap_or_else(|_| "clawmates-runtime".to_string());
|
||||||
|
crate::root_copy::purge(&container, std::path::Path::new(&mission_dir)).await;
|
||||||
|
if tokio::fs::metadata(&mission_dir).await.is_ok() {
|
||||||
|
eprintln!(
|
||||||
|
"mission_runtime: {mission_dir} SURVIVED the root purge — it will keep accumulating"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => eprintln!("mission_runtime: rm workspace dir {mission_dir}: {e}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Reference in New Issue
Block a user