fix(disk): relocate cargo target off 28G / volume + anti-thrash cleanup gate (#4803) - #4834
Conversation
…anup gate (#4803) Fixes #4803. The root filesystem `/` (28G, holds /home and ~/.simard) saturated to 0 bytes free, driving a ~25-min emergency-cleanup crash-loop: each pass deleted target/debug + target/llvm-cov-target only for cargo to instantly rebuild and refill `/` within one cycle. That 0-bytes-free condition cascaded into cognitive-lock refusals, typed `database is locked`, and memory-IPC write failures across goal-session engineers. Root cause: cargo build artifacts defaulted onto `/` (under $HOME) while a 196G /tmp volume sat with free space, and emergency cleanup freed space that instantly rebuilt. Additive, non-breaking fix confined to the cited surface: - tmux.rs: drop the `$HOME/.cargo-targets` default branch so the per-worktree CARGO_TARGET_DIR defaults onto the large-volume `/tmp/simard-cargo-targets` fallback even when HOME is set; `SIMARD_CARGO_TARGETS_ROOT` override still wins. Resolver made pub(crate) as the single source of truth. - lifecycle/spawn.rs: direct-exec path delegates to that unified resolver instead of the divergent hardcoded `/tmp/simard-engineer-target`. - disk_health.rs: emergency_cleanup gains hysteresis (high=95/low=85) plus a persistent time-backoff marker under `<state_root>/disk-health/` (SIMARD_DISK_EMERGENCY_MIN_REFIRE_SECS, default 900, clamped [0,86400]) so it cannot thrash within one build window; symlink + containment guards before every remove_dir_all; fail-open marker I/O. - advance_goal/spawn.rs: build-heavy dispatch preflight probes `/` via the existing disk_pressure gate; Refuse -> benign retry-next-cycle skip with loud warn (no silent fallback); probe error fails open. Structured tracing only (no new print!/println!), no silent fallbacks. Docs added for concept/howto/reference. cargo check + clippy clean; disk_health, tmux-env, and preflight unit tests green. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
preflight Step 9 (refactor/simplify) for #4803. The build-heavy dispatch preflight (BuildDispatchPreflight enum + build_dispatch_preflight fn) was inserted into the MIDDLE of dispatch_spawn_engineer's doc-comment block, so its first paragraph wrongly documented the new enum while dispatch_spawn_engineer kept only the second half. Move the new items ahead of that doc block so each item is documented correctly. Pure reordering, no behavior change; targeted tests (disk_health, preflight, tmux, tests_tmux) green, spawn.rs fmt-clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Automatic checkpoint to preserve review-addressed changes. Saved before running pre-commit hooks and tests.
📊 Coverage Summary
Coverage data from CI run. Test files matching |
rysweet
left a comment
There was a problem hiding this comment.
Step 17b — Comprehensive Code Review ✅ APPROVE
Reviewed the full diff (13 files, +1516/-43): agent_supervisor/{tmux,lifecycle/spawn}.rs, disk_health.rs, disk_reclaim/{executor,mod}.rs, ooda_actions/advance_goal/spawn.rs, operator_commands_ooda/daemon/mod.rs, tests, and docs. This is a well-engineered, additive, non-breaking fix for the /-volume saturation crash-loop (#4803).
Verdict
Merge-ready. CI is fully green (coverage, install-real, e2e-dashboard, pre-commit, cargo-audit/deny/vet, GitGuardian), status is CLEAN / MERGEABLE, no print!/println!/eprintln! added, no "Bridge" naming, no silent fallbacks. Confirmed no src/self_deploy/ or src/safe_update/ files are touched — the P1 deploy-path work can safely rebase onto this.
Review checklist
- ✅ Code quality & standards — pure decision seams (
should_fire_emergency_cleanup,emergency_refire_min_secs_from,build_dispatch_preflight,prune_regenerable_build_artifacts) cleanly separated from I/O; single source of truth for the cargo-target resolver (pub(crate) default_cargo_target_for_worktree) shared by both spawn paths; structuredtracingthroughout. - ✅ Test coverage — strong. Unit tests pin hysteresis/backoff/clamp, off-
/relocation + never-under-HOME guard, and preflight Proceed/Skip/fail-open; plus a real outside-in integration test (tests/disk_reclaim_build_artifact_prune.rs) driving the productionprune_build_tree_artifactsagainst an on-disk tree with the real remover/measurer//procprobe. - ✅ No TODOs / stubs / swallowed exceptions — none. All
unwrap()/expect()are in test code. Fail-open paths (statvfsprobe error, marker I/O) are logged viawarn!, not swallowed. - ✅ No unimplemented functions.
- ✅ Logic correctness — resolution order and gating are sound; the
SIMARD_CARGO_TARGETS_ROOToverride and empty-string guard are preserved. - ✅ Edge-case handling — excellent: symlink refusal via
symlink_metadata(no redirect-follow), containmentstarts_with(repo_root|state_root)guards before everyremove_dir_all, live-PID veto (fail-closed), root-euid refusal downgrades apply→dry-run, refire-secs clamp[0, 86400], explicit0escape hatch, and clock-skew (previn future → suppress).
Non-blocking observations (optional, do not gate merge)
-
EMERGENCY_LOW_WATERMARK_PCT(85) is telemetry/doc-only, not part of the fire decision. It appears only in thewarn!log and inwatermark_band_is_valid_hysteresis;should_fire_emergency_cleanupgates purely on the high watermark + time-backoff. The anti-thrash guarantee is therefore delivered entirely by the backoff, not a two-edge state machine — the code comment honestly says as much. Calling it a "hysteresis band" slightly overstates the mechanism (true hysteresis would track a "dropped below low ⇒ re-arm" state). Consider either wiring the low watermark into the decision or renaming to "time-backoff gate" for precision. Not a defect. -
The backoff marker is persisted unconditionally, even when
freed == 0. Inemergency_cleanup,write_emergency_marker(&marker, now)runs after the prune loop regardless of whether any artifact was actually removed. A fire that frees nothing (e.g., target dirs already absent) still suppresses re-fire formin_refire_secs. This is benign (a no-op refire would also free nothing, and post-relocation/is no longer the fill target), but if you want the marker to strictly track "we actually reclaimed space," gate the write onfreed > 0. Debatable; current behavior is defensible.
Both are minor polish items, not merge gates. Approving.
🔒 Step 17c — Security Review (PR #4834)Scope: the 9 changed Verdict: No Critical/High exploitable vulnerabilities. 3 security-relevant findings (MEDIUM/LOW, all bounded by the local-write trust boundary) worth addressing; several security controls verified secure. Findings are non-blocking for merge but recommended as fast-follow hardening. Findings[MEDIUM · conf 7/10] Predictable, world-writable [MEDIUM · conf 7/10] TOCTOU symlink race — containment re-assert collapses when [LOW · conf 6/10] [Minor · DoS] Marker timestamp overflow panic — ✅ Verified secure (no action needed)
Checklist: ✅ security requirements reviewed · ✅ new-vuln scan (3 local-write hardening findings, none remotely exploitable) · ✅ sensitive-data handling (none introduced) · ⏩ authn/authz (n/a — no auth surface) · ✅ injection review (command/shell/env — none; git/tmux hardened). Automated security review — findings are advisory hardening, not merge blockers. |
🧭 Step 17d — Philosophy Guardian ReviewPR #4834 — fix(disk): relocate cargo target off 28G Verdict: ✅ COMPLIANT — merge-ready on philosophy grounds
Safety invariants worth calling out (all upheld)
Non-blocking observations (not gates)
Philosophy compliance: PASS. No changes required to merge. — Philosophy Guardian (Step 17d, automated) |
Fixes #4803.
Problem
The root filesystem
/(28G, holds/homeand~/.simard) saturated to 0 bytes free, driving a ~25-min emergency-cleanup crash-loop: each pass deletedtarget/debug+target/llvm-cov-targetonly for cargo to instantly rebuild and refill/within one cycle (observed 21:58→00:42). That 0-bytes-free condition cascaded into cognitive-open-lock refusals, typeddatabase is locked, and memory-IPC write failures across goal-session engineers.Root cause: cargo build artifacts defaulted onto the 28G
/volume (under$HOME) while a 196G/tmpvolume sat with free space; emergency cleanup freed space that instantly rebuilt — an ineffective band-aid that thrashed.Fix (additive, non-breaking; confined to the cited surface)
agent_supervisor/tmux.rs— drop the$HOME/.cargo-targetsdefault branch so the per-worktreeCARGO_TARGET_DIRdefaults onto the large-volume/tmp/simard-cargo-targetsfallback even whenHOMEis set.SIMARD_CARGO_TARGETS_ROOToverride still wins; empty-string guard preserved. Resolver madepub(crate)as the single source of truth.agent_supervisor/lifecycle/spawn.rs— direct-exec path delegates to that unified resolver instead of the divergent hardcoded/tmp/simard-engineer-target.disk_health.rs—emergency_cleanupgains hysteresis (high=95 / low=85) plus a persistent time-backoff marker under<state_root>/disk-health/(SIMARD_DISK_EMERGENCY_MIN_REFIRE_SECS, default 900, clamped[0, 86400]) so it cannot thrash within one build window.symlink_metadata+starts_with(repo_root|state_root)containment guards before everyremove_dir_all; fail-open marker I/O with logging.ooda_actions/advance_goal/spawn.rs— build-heavy dispatch preflight probes/via the existingdisk_pressuregate (reused, no new thresholds):Refuse→ benign retry-next-cycle skip with loudwarn!(no silent fallback); probe error fails open (proceed but log) so astatvfshiccup can't wedge all goal advancement.llvm-cov artifacts stay discoverable under the relocated
CARGO_TARGET_DIR.Constraints honored
tracingonly — no newprint!/println!/eprintln!in the diff.Verification
cargo check --lib✓ ·cargo clippy --all-targets --all-features --locked -- -D warnings✓disk_healthhysteresis/backoff/clamp,compute_tmux_envoff-/relocation + never-under-HOME guard, preflight Proceed/Skip/fail-open.Docs
Added concept / how-to / reference pages and wired into
mkdocs.yml.Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com