workflow-fix: sweep /mnt/eps-data/$USER staging in disk janitors
Highlight text on any card (body, plan, or an event) to anchor a comment, or leave a whole-task comment here. Mention @claude to summon a reply.
No comments yet.
Overview / Motivation
Auto-filed by the workflow-fix-on-bug protocol from a chat-mode deep dive (2026-08-05, user-directed: "Do a deep dive and propose workflow fixes" on the fleet-wide disk pressure). Emitting agent: orchestrator + a read-only janitor-coverage audit subagent.
Goal
Add an opt-in /mnt/eps-data/$USER staging-roots leg to the non-canonical cache sweep (clean_experiment_downloads.py), the disk-guard data-disk pass (vm_disk_guard.py), and the watcher data-disk attribution (autonomous_session_watch.py), under the full existing safety contract, so the sanctioned per-issue staging convention finally has a lifecycle owner.
Workflow gap
- Bug observed: All janitor enumeration roots resolve to the boot disk so the sanctioned
/mnt/eps-data/$USER/issue<N>_<slug>staging convention accumulated ~55 dirs / ~710 GB with zero reap, escalation, or attribution./mnt/eps-datais 96% full (47 GB free) and the guard's data-disk pass reports 0.00 GB reclaimable there. - Why it is a workflow gap: CLAUDE.md's compute-character clause routes multi-GB staging to "an existing user-writable per-issue dir
/mnt/eps-data/$USER/issue<N>_<slug>/" (#1393; #1410 explains why: the top level is root-owned), but no janitor arm ever enumerates that root — the convention was sanctioned without giving any sweep coverage of it. The #681 bind cutover never happened (findmnt --mountpoint <repo>/.claude/worktreesis empty), so the boot-disk-rooted globs also never incidentally resolve there. - Confidence (emitter): high
- verified-at-filing (all re-run by the filer 2026-08-05):
grep -n 'discover_roots' scripts/vm_disk_guard.py→ 3 hits (L817–820): tier-(b) roots =repo_root()/"data"+.claude/worktrees/*/dataonly — both physically on/.grep -c 'eps-data' scripts/vm_disk_guard.py→ 7 hits; context-read (audit 2026-08-05): all statvfs/mount plumbing (DATA_DISK_ROOT_DEFAULT, threshold reads, the #915 managed-symlink validation root) — zero enumeration roots. Absence claim probed semantically, not just verbatim.grep -n 'eps-data' scripts/clean_experiment_downloads.py→ 3 hits (L223–229data_disk_root()), consumed only by_managed_symlink_target(L251–276) + the symlink disposition block inclean_issue_downloads.grep -n 'TMP_CACHE_ROOT_DEFAULT' scripts/clean_experiment_downloads.py→ 3 hits (L203, 525, 534): the only non-data/sweep root is/tmp(top-level).grep -n 'repquota' scripts/autonomous_session_watch.py→ primary data-disk attribution_top_issue_caches_by_project_quota(L4539+); live fact:repquotabinary is NOT installed on this VM (quota seeding is owned by the parked #1038/#681 cutover), so the pass always falls to the du fallback whose glob roots are boot-disk (PROJECT_ROOT / "data", L4470).- Live census (read-only subagent, 2026-08-05):
ls /mnt/eps-data/thomasjiralerspong | wc -l→ 61 entries; du total 958.0 GB, of which issue-keyed staging ≈ 710 GB across ~55 dirs. Naming variants all parse via the existingextract_issue_numberP1 regex:issue<N>_<slug>(dominant),issue<N>-<slug>(issue779-grid),issue_<N>_<slug>,i<N>_<slug>(i825_*),tmp_issue<N>_<slug>. Only the ROOT is missing, not the pattern. - Landed-fix history:
git log --oneline --since='14 days ago' -- scripts/vm_disk_guard.py scripts/clean_experiment_downloads.py scripts/autonomous_session_watch.py→ recent commits are the no-progress-respawn lane, proposed_infra_sweep cap key, EBADF probe fixes — none touch data-disk enumeration.
Proposed change (candidate diff sketch — refine in planning)
+ clean_experiment_downloads.py:
+ production_staging_roots() -> [data_disk_root()/<uid-name>] when the mount is live (main()-only opt-in,
+ sibling of production_tmp_root(); env EPM_STAGING_CACHE_ROOTS override)
+ thread staging_roots through clean_issue_downloads -> noncanonical_cache_dirs, treated like the /tmp
+ P1/P2 leg (top-level, uid-owned, non-recursive)
+ vm_disk_guard.py:
+ _discover_staging_issue_numbers mirroring _discover_tmp_issue_numbers (L487-508); wire into
+ clean_terminal_download_caches (L782) and pass staging roots on the DATA-DISK pass in main() (L2246-2258)
+ autonomous_session_watch.py:
+ _data_disk_top_caches (L5049): after the repquota-None fallback, bounded du -s top-N over
+ data_disk_path()/<uid-name>/* so sub-floor sidecar rows attribute the real holders
Downstream contract UNCHANGED and load-bearing: terminal-reap vs active-escalate (TERMINAL_CACHE_REAP_STATUSES — note awaiting_promotion counts as cache-terminal by existing policy), 48h recency keep (consider top-level-only recency for staging roots — a full rglob over a 206 GB tree is a real walk), nested store/+eval_results/ hard block, and the positive re-downloadability-evidence gate (escalate-never-delete).
Safety requirement from the census (MUST carry into the plan): issue1887_lambda_audit (102.1 GB, #1887 = completed) holds PARKED #1345's tensor shards (issue1345/story_slot_ablation_turnstore/*.pt) — cross-issue staging exists in the wild, so a name-keyed terminal reap would destroy a parked task's store. The evidence gate must remain binding, and a dir whose CONTENT paths name a different issue than its dir name should hard-escalate, never reap.
Named residual (planner may descope with a stated reason): /mnt/eps-data/tmp (root-owned sticky, ~2 GB, 10,040 bt-*/epm_isolated_* build-temp entries) matches no issue pattern and stays invisible even after this fix; needs its own age-sweep decision.
Scope / surfaces
- Primary targets:
scripts/clean_experiment_downloads.py,scripts/vm_disk_guard.py,scripts/autonomous_session_watch.py - Cross-references: #1038 (blocked bind-cutover task — owns repquota install + quota seeding; do NOT duplicate), #2015 (open; its sketch recommends MORE /mnt/eps-data staging — coordinate, this fix is its missing lifecycle half), #911/#679/#1450 (the machinery being extended), #1369 (created the relocated HF cache — covered by a SEPARATE sibling filing, not this task).
- Grep the workflow surface for the pattern before editing (
grep -rln 'eps-data' scripts/ .claude/ CLAUDE.md) and update every hit that documents sweep coverage; list them in the plan.
Constraints / invariants
- Workflow-surface only — never experiment code,
configs/, ortasks/. scripts/workflow_lint.py --check-askspasses; ruff on touched files passes; tests pinning janitor invariants updated alongside.- Escalate-only on the data disk stays the default posture for ACTIVE-status dirs; nothing active is ever deleted.
- This session runs under
EPM_WORKFLOW_FIX_SESSION=1and carries aworkflow_fix_target:Provenance line — it MUST NOT auto-route any of its own subagents' workflow-fix candidates (recursion guard).
Provenance
- workflow_fix_target: scripts/clean_experiment_downloads.py, scripts/vm_disk_guard.py, scripts/autonomous_session_watch.py
- fingerprint: 08726cec9cfc
target_file: scripts/clean_experiment_downloads.py, scripts/vm_disk_guard.py, scripts/autonomous_session_watch.py bug_observed: All janitor enumeration roots resolve to the boot disk so the sanctioned /mnt/eps-data/USER/issue_/ as the multi-GB staging home but no janitor arm enumerates that root — the convention has no lifecycle owner proposed_change: Add an opt-in /mnt/eps-data/$USER staging-roots leg to the non-canonical cache sweep, the disk-guard data-disk pass, and the watcher data-disk attribution diff_sketch: |
- production_staging_roots() -> [data_disk_root()/$USER] (main()-only opt-in, sibling of production_tmp_root)
- thread staging_roots -> noncanonical_cache_dirs (treat like the /tmp P1/P2 leg)
- _discover_staging_issue_numbers + wire into clean_terminal_download_caches + the data-disk pass in main()
- watcher _data_disk_top_caches: bounded du -s top-N over data_disk_path()/$USER/* after the repquota-None fallback confidence: high related_task: n/a (chat-mode deep dive, 2026-08-05)