Skip to content

fix: remove capture plans no caller will seal - #20

Merged
santoshkumarradha merged 2 commits into
mainfrom
fix/prune-capture-plans
Oct 26, 2026
Merged

santoshkumarradha merged 2 commits into
mainfrom
fix/prune-capture-plans

Conversation

@santoshkumarradha

Copy link
Copy Markdown
Member

Summary

Capture plans a caller never sealed stayed in capture-plans/ forever. One machine held 43 of them, 98 GB in total. A plan more than ten minutes old is now treated as abandoned and removed.

 capture_plan
+  prune_plans(workspace/capture-plans)   # one read_dir, never the store
   ...plan...
   write <token>.json

 daemon writer, idle tick (0.3.3)
   check_index
+  prune_abandoned_plans(store)           # workspaces/*/capture-plans, for timelines that never plan again
prune_plans(dir)
  for <64-hex>.json or .tmp* (a write that never got renamed)
    if mtime is more than 10 min old: remove (errors ignored)
  future mtime counts as fresh; other names are left alone

Rule: age only, 10 minutes. codeaf seals within SealWithin (30 s), and a person running capture-plan then hook turn-end by hand still has plenty of time. A count cap would remove young plans whenever several callers plan on one timeline at once, which breaks the promise that a plan in use is never deleted. With age alone, a directory holds at most the plans made in the last ten minutes, and a plan that is sealed removes itself, as before.

A seal that lost its plan to pruning gets the existing capture plan is absent; re-plan error. If a seal has already read its plan, deleting the file does not affect it.

Evidence

  • Before (0a705ca, tests only): all four new tests fail. Old plans survive a new plan, the seal of a stalled plan succeeds, and the quiet daemon writer never removes anything.
  • After (e8e0f30): they pass.
40 plans + a .tmp leftover, all aged 1 day; fresh plan F; new plan N
  -> only F.json, N.json and the unrelated notes.txt remain; F still seals
stalled plan aged 1 day, then a new plan  -> seal(stalled) = "capture plan is absent; re-plan"
handle A: 30 rounds of plan+seal  ||  handle B: 30 rounds of 20 aged junk plans + capture_plan
  -> every seal by A succeeds, and B's junk is gone after each round
daemon (idle 100 ms): aged plans on a timeline with no further plans are removed; a fresh one stays

Spark fleet job 20261026-184751-002483 ran on e8e0f30. cargo fmt --check, cargo clippy --workspace --all-targets --locked -D warnings and cargo test --workspace --locked all passed, exit 0. The prune tests also passed 25 out of 25 repeated runs.

Merge Danger

Door: two-way

Plans are disposable. The worst case is that a caller stalled for more than ten minutes has to re-plan.

Blast Radius: small

Only capture-plans/ changes. Release step: a separate release: 0.3.4 PR that bumps the workspace version, as was done for 0.3.3, then tag v0.3.4. After that, codeaf's engine pin can move.

🤖 Generated with Claude Code

Plans a caller never seals stay in capture-plans/ forever today. A new
plan, and a quiet daemon writer for a timeline that never plans again,
must remove plans older than any caller takes to seal, keep a fresh
plan sealable, and leave a stalled caller a clean re-plan error.
A plan is deleted only by its own seal, so every re-plan, crash, timeout
or engine exit left its file behind; one machine held 98 GB of them.
A plan older than ten minutes, twenty times the time a caller takes to
seal, is now abandoned: each new plan removes its timeline's abandoned
plans and partial writes, and an idle daemon writer does the same for
timelines that never plan again. Both read one directory of plans,
never the store. Sealing a removed plan still asks for a re-plan.
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