Skip to content

Released-resource garbage collection

praetorctl gc defaults to a read-only dry-run. Every resource is protected until its owner explicitly releases it. Age limits control retention after release; they do not establish whether an agent session has ended.

praetorctl gc --path . --json
praetorctl gc --path . --released-path .standards/worktrees/task-123 --json

Only add --dry-run=false after reviewing the plan and ensuring the owner has stopped writing. Repeat --released-path for each resource. Paths must be exact direct children of a configured collection pool, relative to --path or absolute within that root. Missing releases, overlapping pools, outside paths, symlink components, incomplete scans and inspection failures stop collection with a nonzero exit status. The complete scope is preflighted before any removal.

The pools are .standards/worktrees, .standards/ephemeral, and .standards/tmp. --worktrees-dir can select another nonoverlapping directory inside the repository root. The Go API also accepts EphemeralDir. Root-level and bin/ files are not swept by filename. Git metadata and nested repositories cannot be collected as artifacts.

A pool you configure with --worktrees-dir (or EphemeralDir) must exist: a missing one, such as a mistyped path, stops collection with a nonzero exit and a configured collection pool ... does not exist error. A default pool the repository never created is not an error; the report lists it under missing_pools and the text output prints it as a missing default pool, so an empty plan is never mistaken for an examined, empty pool (internal/gc/gc_test.go, TestCollect_Negative_MistypedWorktreesDirIsAnError, TestCollect_Boundary_AbsentDefaultPoolsAreListed).

Both --max-age (worktrees) and --artifact-max-age default to 24 hours. The newest modification time in the complete resource determines retention. Scans are bounded to 2,000 direct entries per pool and 50,000 entries / 10,000 directories per released resource, with at most 50,000 planned entries in total. Exceeding a bound is an error, never partial successful discovery. Symlinks and special files in a released resource are protected; this intentionally also protects otherwise clean symlink-containing worktrees.

Released worktrees must be registered linked worktrees belonging to the root repository. Primary, bare, detached, locked, prunable, dirty, ignored-only and submodule-containing worktrees are refused, and so is one where a tracked path selects a clean or process filter driver: the cleanliness probe and the git status that git worktree remove runs itself would each run that driver. The filter check is the shared util.RefuseGitStatusFilters, once in the repository-only view the sealed probe reads and once with util.WithEffectiveGitConfig, so drivers and attributes from the global and system Git files count, as they do for the removal itself. Both views are needed: a global macro that unsets filter or a global attr.tree can hide from the effective view a driver the probe still runs. A driver no tracked path selects, such as the filter.lfs block Git for Windows and git lfs install define, and a smudge-only driver never run, so they do not protect the worktree (#679). Global attributes, excludes and filesystem-monitor settings do not protect it either: the cleanliness probe reads none of them and lists every untracked file, and the removal runs with core.fsmonitor=false (internal/worktree/worktree.go, checkRemovalFilters; fixtures TestWorktree_Positive_ReleasedRemovalIgnoresUnusedOperatorConfig, TestWorktree_Negative_ReleasedRemovalRefusesSelectedOperatorFilter, TestWorktree_Negative_ReleasedRemovalRefusesRepositoryFilterOperatorFilesHide, TestWorktree_Boundary_ReleasedRemovalOperatorEdges). Removal uses Git without force and preserves the branch, including unpublished commits. praetorctl worktree remove has the same preservation behavior unless explicitly forced. --force passes --force to Git twice, which also removes a locked worktree, and deletes the wt/<task-id> branch even when the worktree itself is already gone. Administrative praetorctl worktree prune remains a separate operation: after git worktree prune it deletes wt/ branches that no worktree has checked out and whose commits HEAD already contains, with git branch -d; a branch carrying unpublished commits is kept. Fixtures: internal/worktree/worktree_test.go (TestWorktree_Positive_PruneSweepsMergedOrphanBranches, TestWorktree_Negative_ForceRemovalOfVanishedWorktreeStillDeletesBranch).

--clean-go-test-cache explicitly requests clearing the shared Go test cache; it is off by default and only runs on an applying invocation. It does not widen the artifact pools or authorize deletion of binaries. SkipTestCache still suppresses it for API callers. GC never automatically runs Git administrative pruning; SkipGitPrune remains accepted for source compatibility.

Resource paths in the report are relative to the collection root. The JSON report separates planned_* fields from completed removal fields and reclaimed_bytes. Dry-run completed-removal fields stay empty and actual bytes stay zero. Byte counts describe file lengths, not allocated disk savings. A report with complete: false and an error is incomplete; an apply failure can leave earlier resources removed or the failing directory partially removed. The error exit status must be checked even when a partial report is available.

Release is currently a caller assertion of quiescence, not a verified session lease. Collection compares per-entry filesystem identities before removal, checks cancellation between artifact removals, and confines filesystem access with an open root, but it does not fence another process that resumes writing. Do not derive automatic releases from a missing PID, old mtime or expired heartbeat. Producer registration, interrupted-state reconciliation, referenced evidence retention and quarantine are specified in the shared lifecycle plan.