Skip to content

fix(cleanup): prune date-partitioned state stores and stale JetBrains data (ticket-091) - #119

Merged
ifuri-validator-agent[bot] merged 2 commits into
mainfrom
ticket/091-state-retention
Oct 20, 2026
Merged

ifuri-validator-agent[bot] merged 2 commits into
mainfrom
ticket/091-state-retention

Conversation

@tom-sapletta-com

Copy link
Copy Markdown
Contributor

Problem

On a host at 100% disk usage, fixos quick / cleanup --list could not see most of the space:

  • ~/.local/state/subactor/diagnostics/v1 held 114 GB of YYYY/MM/DD telemetry partitions with no retention, growing about 7 GB/day. fixos deliberately skips ~/.local/state, so it neither reported nor could prune this data.
  • --jetbrains found 154 MB of 32 GB. It only knows the legacy Toolbox apps/<app>/ch-N layout, 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 containing YYYY/MM/DD trees under ~/.local/state and ~/.local/share and selects whole day partitions older than N days by name and by every file's mtime. It never touches:

    • the current day (--days >= 1);
    • files beside the year tree (e.g. checkpoint.sqlite3);
    • symlinks or their targets, symlinked roots;
    • other filesystems.

    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: risk review.

  • --jetbrains: version data directories (WebStorm2025.3, …) are candidates only when no install in Toolbox state.json uses that dataDirectoryName and 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

  • New tests: tests/unit/test_state_retention.py (11) and tests/unit/test_jetbrains_toolbox_v2.py (4). They cover:
    • old-name/recent-mtime partitions;
    • hardlinks shared with kept data;
    • links within the selection;
    • symlink targets, checkpoint and current day preserved;
    • a CLI dry-run that deletes nothing;
    • a snap install keeping an older-looking data dir;
    • fail-closed on an unreadable install.
  • Full unit suite: 736 passed. ./project/governance-check.sh: PASS.
  • Read-only run on the affected host:
    • --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.2 is 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

tom-sapletta-com and others added 2 commits October 20, 2026 21:47
… 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>

@ifuri-validator-agent ifuri-validator-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Merge will be attempted after this approval when explicitly authorized. ## Decision record (recomputable)
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"

@ifuri-validator-agent
ifuri-validator-agent Bot merged commit 7543432 into main Oct 20, 2026
11 of 12 checks passed
@ifuri-validator-agent
ifuri-validator-agent Bot deleted the ticket/091-state-retention branch October 20, 2026 20:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant