feat: explicit local run lifecycle with list and prune - #32
Conversation
Run bundles accumulated without bound. `aas runs` lists persisted runs newest-first with stage status, and `aas prune --keep <n>` removes oldest runs beyond the window, with `--dry-run` preview. The run the latest pointer identifies is always kept so exports and downloads never dangle, and nothing is deleted without an explicit keep count. Missing stores report empty instead of failing.
There was a problem hiding this comment.
Devin Review found 3 potential issues.
2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)
| for (const run of runs) { | ||
| if (keepSet.has(run.run_id)) continue; | ||
| removed.push(run.run_id); | ||
| if (!dryRun) rmSync(join(runsDirectory(outputRoot), run.run_id), { recursive: true, force: true }); |
There was a problem hiding this comment.
🔴 Concurrent pruning can erase completed runs
When overlapping demos finish out of order, pruneRuns can delete a published run before its latest pointer is written. persistRunBundle then writes that pointer after deletion. The run is lost, and latest exports fail.
Prompt for agents
Prevent pruneRuns in bin/aas.mjs from racing with persistRunBundle across processes. persistRunBundle publishes the final run directory before updating latest.json, so an older-ID run that completes after a newer-ID run can be deleted during that interval and then become the latest pointer target. Serialize publication and pruning with a shared lock, or introduce a lifecycle marker that makes newly published runs ineligible for deletion until pointer publication completes. Preserve atomic latest-pointer updates and ensure interrupted operations remain recoverable.
Was this helpful? React with 👍 or 👎 to provide feedback.
| } catch { | ||
| return null; |
There was a problem hiding this comment.
🟡 Unreadable latest pointers disable retention
When latest.json is unreadable, malformed, or invalid, readLatestRunId silently treats it as absent. pruneRuns can then delete its target when that run falls outside the keep window.
Prompt for agents
Make pruneRuns fail closed when latest.json exists but cannot be read, parsed, or validated. Only treat ENOENT as an absent pointer. Validate the pointer schema and run_id before any deletion, and surface other failures so pruning cannot silently drop latest-target protection.
Was this helpful? React with 👍 or 👎 to provide feedback.
| } catch { | ||
| continue; | ||
| } | ||
| if (!manifest || typeof manifest !== "object") continue; |
There was a problem hiding this comment.
🟡 Unrelated run directories can be deleted
A valid-named directory with any JSON object in manifest.json passes listRuns without a matching run ID or schema. pruneRuns can recursively delete that unrelated directory outside retention.
| if (!manifest || typeof manifest !== "object") continue; | |
| if ( | |
| !manifest | |
| || typeof manifest !== "object" | |
| || Array.isArray(manifest) | |
| || manifest.schema_version !== "agent-action-stack.run/v1" | |
| || manifest.run_id !== entry.name | |
| ) continue; |
Was this helpful? React with 👍 or 👎 to provide feedback.
Capability
aas runs(newest-first with stage status,--jsonsupported) andaas prune --keep <n>(--dry-runpreview,--jsonsupported).Retention is explicit and non-destructive by default: no automatic
deletion exists,
--keepis required, the latest-pointer target isalways kept, and dry runs change nothing.
Verification
npm test: 5 new tests (listing order and shape, keep-window pruningplus export-after-prune, latest-pointer protection, input validation
and empty stores, CLI argument handling).
dry-run, and validation paths exercised.