Repository navigation
fix(cleanup): prune date-partitioned state stores and stale JetBrains data (ticket-091) - #119
Conversation
… data (ticket-091) fixos skipped ~/.local/state entirely, so collectors writing YYYY/MM/DD partitions without retention (here ~7 GB/day) filled the disk unseen. cleanup --state-old --days N removes only whole day partitions older than N days by name and by every file's mtime, keeps the current day, files beside the year tree, symlink targets and other filesystems, and reports a hardlink-aware reclaimable size. --jetbrains now handles the flat Toolbox layout: version data directories that no install in Toolbox state.json uses (snap installs included) and that a newer version superseded are candidates; duplicate registered installs are reported for removal through Toolbox instead of being deleted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cket-091) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Validator approval after policy checks for exact head e2d3e12d22a1109efbb56003ef3cd462eb0262c8.
Ticket: ticket-091
Correlation ID: local-semcod-fixos-pr-119-ticket-091
Model: openai/cursor-auto
Reviewed diff chunks: 3
Advisory LLM verdict: REQUEST_CHANGES
Advisory summary: Reviewed all 3 diff chunk(s). Required protected checks pass, and the visible cleanup logic is generally conservative. However, Toolbox metadata parsing does not fully implement its documented fail-closed behavior for malformed or unreadable metadata. | Required checks pass, but the visible retention implementation violates its stated deletion safety contract for symlinks. | The visible chunk adds focused tests for state-retention safety, hardlink accounting, symlink handling, CLI dry-run behavior, and fail-closed JetBrains cleanup discovery. No unresolved code or security defects are evident in this chunk. All protected required checks passed.
Advisory findings: _toolbox_installs can raise instead of returning None when state.json or product-info.json contains invalid UTF-8 or an unexpected JSON shape. UnicodeDecodeError is not covered by the current exception handlers, while non-mapping tools entries or product-info.json values cause uncaught AttributeError at .get(). Validate that the decoded objects and entries are mappings and catch decoding failures so all unreadable/invalid installation metadata reliably yields no deletion candidates.; _measure() permits day partitions containing nested symlinks, and remove_old_partitions() subsequently calls shutil.rmtree() on the whole partition. This unlinks those symlink directory entries, contradicting the module and PR guarantees that symlinks are never touched. Either reject partitions containing any symlink or narrow the documented contract and tests to explicitly permit unlinking symlinks while preserving their targets.
The LLM output above is advisory and was not used as the approval trust root.
Semantic review prerequisite: not_required; policy 676cb4516bbfed2a000e40b9b1b6e4a430ecc761ec546aeb53d721a1905cfdd7.
Actual PR impact radar
Exact range: 48b29790f2d88397712dc91f44a2ecf7109c5ab0...e2d3e12d22a1109efbb56003ef3cd462eb0262c8
Change digest: 7d34a596fbe41253bce57daa67c6a7a18d29f46ff91d88d79a1088c3e34127d8
Score: 64/100 (L), estimated 131 min, split recommended: true
Affected services/components: repository-wide/unclassified
Machine-readable radar JSONL and SVG
{"actual_change":{"additions":814,"base_sha":"48b29790f2d88397712dc91f44a2ecf7109c5ab0","binary_files":0,"categories":{"code":4,"configuration":1,"docs":1,"tests":2},"change_digest":"7d34a596fbe41253bce57daa67c6a7a18d29f46ff91d88d79a1088c3e34127d8","comparison":"48b29790f2d88397712dc91f44a2ecf7109c5ab0...e2d3e12d22a1109efbb56003ef3cd462eb0262c8","deletions":7,"file_count":8,"files":["fixos/cli/_cleanup_space.py","fixos/cli/cleanup_cmd.py","fixos/constants.py","fixos/diagnostics/state_retention.py","project/ticket-091/README.md","project/ticket-091/intent.json","tests/unit/test_jetbrains_toolbox_v2.py","tests/unit/test_state_retention.py"],"head_sha":"e2d3e12d22a1109efbb56003ef3cd462eb0262c8","service_count":0,"services":[]},"assessment_mode":"observed-pr","axes":{"coupling":5,"delivery":2,"scope":5,"uncertainty":3,"validation":1},"complexity":"L","confidence":0.9,"diagnostics":["RADAR-ACCEPTANCE-MISSING","RADAR-BUDGET-EXCEEDED"],"estimate":{"budget_minutes":30,"minutes":131,"within_budget":false},"impact":{"components":["GB","YYYY","apps","claude.com","fixos","local","old-name","project","tests","uncrash"],"files":["GB/day","YYYY/MM/DD","apps/webstorm","apps/webstorm-3","claude.com/claude-code","fixos/cli/_cleanup_space.py","fixos/cli/cleanup_cmd.py","fixos/constants.py","fixos/diagnostics/state_retention.py","local/share","local/state","local/state/subactor/diagnostics/v1","old-name/recent-mtime","project/governance-check.sh","project/ticket-091/README.md","project/ticket-091/intent.json","tests/unit/test_jetbrains_toolbox_v2.py","tests/unit/test_state_retention.py","uncrash/snapshots"],"public_interfaces":[],"runtime_dependencies":0},"schema":"subactor.ticket-radar/v1","score":64,"split":{"parts":[{"estimated_minutes":13,"name":"Implement GB","scope":["GB"]},{"estimated_minutes":13,"name":"Implement YYYY","scope":["YYYY"]},{"estimated_minutes":13,"name":"Implement apps","scope":["apps"]},{"estimated_minutes":13,"name":"Implement claude.com","scope":["claude.com"]},{"estimated_minutes":13,"name":"Implement fixos","scope":["fixos"]},{"estimated_minutes":15,"name":"Validate and project to trackers","scope":["tests","planfile","github/gitlab/jira projections"]}],"reason":"estimated_minutes_exceed_budget","recommended":true},"standards":[{"id":"wellmanifest/dsl","revision":"6c60fc4e0dd1f1bb74f46a7745e28019908d1203","version":"0.1.0-dev"},{"id":"wellmanifest/ticket-lifecycle","revision":"5bf581907a87b46a13a73e6c033d3abe4d9a306f","version":"0.1.0-dev"},{"id":"wellmanifest/git-lifecycle","revision":"7d77d4b7af57e69bc75c3a0290b3a4805c5c4438","version":"0.2.0-dev"},{"id":"wellmanifest/logs","revision":"48c284ef7a069055c0bcb6b900147ce5e65f8b43","version":"0.3.0"}],"ticket_ref":"ticket-091"}<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img"><title>ticket-091: fix(cleanup): prune date-partitioned state stores and stale JetBrains data (ticket-091)</title><rect width="128" height="128" rx="12" fill="#f8fafc"/><g stroke-width="1"><polygon points="64,55 72,61 69,71 59,71 56,61" fill="none" stroke="#d7dde5"/><polygon points="64,47 80,59 74,78 54,78 48,59" fill="none" stroke="#d7dde5"/><polygon points="64,38 89,56 79,85 49,85 39,56" fill="none" stroke="#d7dde5"/><polygon points="64,30 97,53 84,92 44,92 31,53" fill="none" stroke="#d7dde5"/><polygon points="64,21 105,51 89,99 39,99 23,51" fill="none" stroke="#d7dde5"/><line x1="64" y1="64" x2="64" y2="21" stroke="#aab4c0"/><line x1="64" y1="64" x2="105" y2="51" stroke="#aab4c0"/><line x1="64" y1="64" x2="89" y2="99" stroke="#aab4c0"/><line x1="64" y1="64" x2="39" y2="99" stroke="#aab4c0"/><line x1="64" y1="64" x2="23" y2="51" stroke="#aab4c0"/></g><polygon points="64,21 105,51 79,85 59,71 48,59" fill="#fb923c" fill-opacity="0.45" stroke="#c2410c" stroke-width="2"/><circle cx="64" cy="64" r="3" fill="#c2410c"/><g font-family="sans-serif" font-size="7" fill="#334155"><text x="64" y="11" text-anchor="middle">SCO</text><text x="114" y="48" text-anchor="middle">COU</text><text x="95" y="107" text-anchor="middle">UNC</text><text x="33" y="107" text-anchor="middle">VAL</text><text x="14" y="48" text-anchor="middle">DEL</text></g><text x="64" y="124" text-anchor="middle" font-family="sans-serif" font-size="8" fill="#0f172a">L · 131m</text></svg>DECISION D-091-3332
TICKET ticket-091
HEAD_SHA e2d3e12d22a1109efbb56003ef3cd462eb0262c8
CORRELATION_ID local-semcod-fixos-pr-119-ticket-091
ACTOR agent:ifuri-validator-agent[bot]
APPLIED_RULE P-CORE-015
INPUT author_login = "tom-sapletta-com"
INPUT observed_checks = ["test-summary=PASS","test (3.10)=PASS","test-alpine=PASS","test-debian=PASS","test-arch=PASS","test-ubuntu=PASS","governance / enforce=FAIL","test (3.11)=PASS","test-fedora=PASS","governance / remote lifecycle=PASS","test (3.12)=PASS","onedev/local-verify=PASS"]
INPUT required_checks = ["onedev/local-verify","test (3.10)","test (3.11)","test (3.12)","test-summary"]
INPUT required_checks_source = "protected registry (env/request)"
INPUT reviewer_login = "ifuri-validator-agent[bot]"
INPUT semantic_review_assessment = {"schema":"subactor.validator/semantic-review-assessment/v1","subject":{"repository":"semcod/fixos","pull_request":119,"head_sha":"e2d3e12d22a1109efbb56003ef3cd462eb0262c8","base_sha":"48b29790f2d88397712dc91f44a2ecf7109c5ab0","diff_sha256":"9ef76262444c9c3f677991a70366df652220f88c06090c4d80a3b310de1703b1"},"policy":{"policy_schema":"subactor.validator/semantic-review-policy/v1","policy_version":1,"policy_sha256":"676cb4516bbfed2a000e40b9b1b6e4a430ecc761ec546aeb53d721a1905cfdd7","required":false,"critical_paths":[],"observed_paths":["fixos/cli/_cleanup_space.py","fixos/cli/cleanup_cmd.py","fixos/constants.py","fixos/diagnostics/state_retention.py","project/ticket-091/README.md","project/ticket-091/intent.json","tests/unit/test_jetbrains_toolbox_v2.py","tests/unit/test_state_retention.py"]},"grounding":"full-diff-not-per-finding-proof","execution_authority":false,"status":"not_required","reason":null,"review_sha256":null,"unresolved":[]}
INPUT superseded_checks = []
INPUT ticket_radar_receipt = {"schema":"subactor.ticket-radar/v1","base_sha":"48b29790f2d88397712dc91f44a2ecf7109c5ab0","head_sha":"e2d3e12d22a1109efbb56003ef3cd462eb0262c8","change_digest":"7d34a596fbe41253bce57daa67c6a7a18d29f46ff91d88d79a1088c3e34127d8","score":64,"complexity":"L","estimated_minutes":131,"split_recommended":true,"services":[],"authority":"ADVISORY","promotion":"FORBIDDEN"}
VERDICT APPROVE AUTHORITY DETERMINISTIC
REJECTED REQUEST_CHANGES BECAUSE NO_UNSAFE_CHANGE_REASON_FOUND
ADVISORY llm_verdict = "REQUEST_CHANGES" MODEL "openai/cursor-auto"
ASSERT VERDICT_AUTHORITY != "ADVISORY"
Problem
On a host at 100% disk usage,
fixos quick/cleanup --listcould not see most of the space:~/.local/state/subactor/diagnostics/v1held 114 GB ofYYYY/MM/DDtelemetry partitions with no retention, growing about 7 GB/day. fixos deliberately skips~/.local/state, so it neither reported nor could prune this data.--jetbrainsfound 154 MB of 32 GB. It only knows the legacy Toolboxapps/<app>/ch-Nlayout, while current Toolbox installs IDEs flat (apps/webstorm,apps/webstorm-3).Change
fixos/diagnostics/state_retention.py+cleanup --state-old --days N(default 14, also-c state-old). It finds stores containingYYYY/MM/DDtrees under~/.local/stateand~/.local/shareand selects whole day partitions older than N days by name and by every file's mtime. It never touches:--days >= 1);checkpoint.sqlite3);The reclaimable size is hardlink-aware: an inode counts only when all of its links are selected. Content-addressed snapshot trees (e.g.
uncrash/snapshots, link count 289) are not overestimated. Policy matrix entry: riskreview.--jetbrains: version data directories (WebStorm2025.3, …) are candidates only when no install in Toolboxstate.jsonuses thatdataDirectoryNameand a newer version exists. If any install cannot be read, there are no candidates (fail-closed). Duplicate registered installs of the same build are reported as advisories (text and JSON), not deleted, so Toolbox state stays consistent.Validation
tests/unit/test_state_retention.py(11) andtests/unit/test_jetbrains_toolbox_v2.py(4). They cover:./project/governance-check.sh: PASS.--state-old --days 14: 109 stores, 25.4 GiB (2026-10-01..05) in 7.8 s. Kept data was checked: tickets start 2026-10-08, and September process partitions have mtime 2026-10-07.--jetbrains: 2.0 GB (WebStorm2025.3, old Gateway).PyCharm2026.2is protected because the snap PyCharm 2026.2.3 uses it. The duplicate WebStorm install is reported.No data was deleted on the host; removal is covered by fixture tests.
Not addressed: the collector that writes these partitions has no retention of its own. That belongs to its owning project.
🤖 Generated with Claude Code