Skip to content

Completed task cleanup command

Use scripts/cleanup-completed-task.ps1 from the primary checkout for eligible completed ordinary tasks. It requires Python 3.14, Git, authenticated GitHub CLI, and a live cleanup controller with supported Codex task tools. The PowerShell entry point uses isolated Python startup with site initialization disabled and binds the scripts package directly to its checked-in script directory, ignoring inherited Python paths and startup hooks. Git's worktree inventory and common directory identify the primary checkout before worktree-root configuration is read. The inventory uses NUL-delimited porcelain so Unicode and other supported path characters are preserved verbatim. A primary-checkout target is directed to its restoration workflow even when that configuration is unavailable or unsafe. For non-primary targets, a desktop value that is not a TOML table produces a structured refusal directing configuration repair; a later configuration rewrite preserves already recorded cleanup gates in the refusal result. The active configuration uses the pinned regular-file reader with a 1 MiB limit before TOML parsing. Child commands stream stdout and stderr through a combined 4 MiB cap with a 90-second execution timeout. Excess output terminates the child and produces a sanitized refusal; inspect the tool directly before retrying. Windows children enter a non-breakaway job before the command is authorized to start; POSIX children enter a new process group. Completion and refusal paths terminate remaining descendants and verify quiescence. Failure to establish or verify containment refuses the operation and requires helper reconciliation before retry. Child environments discard inherited GIT_* overrides except the credential prompt helper GIT_ASKPASS, then explicitly disable optional Git locks. Repository, index, object-store, namespace, and injected configuration overrides therefore cannot redirect inspection or deletion away from the checked worktree. Normal on-disk Git configuration still applies. Every Git child explicitly sets core.fsmonitor=false, including cleanliness checks and worktree removal, so a stale monitor response cannot hide tracked edits from the cleanup gates. Git children also force core.trustctime=true and core.checkStat=default so on-disk configuration cannot suppress ctime and other normal stat comparisons for tracked files, including same-size edits with a preserved mtime. Because filesystem timestamps can still collide, cleanup independently hashes every tracked regular file against its index blob before trusting cleanliness. Built-in Git checkout normalization applies, while custom content filters and unsupported tracked entries require independent reconciliation. Hash mismatches preserve the worktree. Specialized resources receive a new eligibility and scope inspection after the complete scope scan, immediately before their prepared-release gate and owning-tool invocation. POSIX Git children also force core.fileMode=true so executable-bit-only edits remain visible even when repository configuration disables mode tracking. Windows retains its native file-mode behavior. Closing issue references must name this same repository through their canonical GitHub issue URLs. A matching issue number in another repository cannot satisfy the local closure gate. Before worktree removal, filesystem enumeration admits only tracked source files and the root worktree .git file. Nested .git metadata and other Git-invisible files require inventory reconciliation before cleanup can continue. Execution holds an exclusive per-task operating-system lock from before eligibility through the final result, and reloads durable gates after acquiring it. A concurrent controller refuses immediately and may retry after the owner exits. The lock hashes a canonical GitHub repository, exact task ID, and PR identity, independently of handoff whitespace, key ordering, or evidence location. Evidence journals retain their original raw-byte handoff digest binding. On POSIX, recovery flushes the evidence directory before trusting visible journals: a previous rename may have succeeded while its directory flush failed. A repeated flush failure refuses recovery without accepting those gates. Windows uses a named mutex; POSIX keeps a single-link .atlaso-cleanup-<digest>.lock coordination file beneath the configured root and releases its advisory lock on exit. Preserve that inode to prevent split ownership. Preview remains read-only. Generated-directory release additionally requires Windows. The controller supplies fresh task evidence through stdin; the command independently checks GitHub, Git, configuration, and filesystem state. No private Codex database, rollout editing, guessed server endpoint, administrative bypass, or execution-policy override is used.

Prepare durable ownership evidence

The originating task records resources as they are created. Its cleanup-ready handoff must survive outside its worktree and every resource being removed, beneath the configured desktop.git-worktree-root or another explicitly permitted durable controller surface. This command currently accepts its handoff and evidence directory beneath that configured root only. Read the exact supported Codex config.toml; never infer a worktree root from an existing path. The supplied configuration must be the active CODEX_HOME/config.toml (the standard user Codex directory when CODEX_HOME is unset), not a replacement supplied by the cleanup handoff.

Provide a UTF-8 JSON handoff with these schema-1 fields:

Field Required evidence
schema Integer 1.
task_id, description Exact Codex task identity and short string description without traceability segments; title formatting is validated before resource release.
task_title Exact current title recorded by the originating task in the cleanup-ready handoff.
repository Exact public same-repository GitHub owner/name.
primary_checkout, worktree Absolute paths independently verified against Git registration.
worktree_identity [st_dev, st_ino] recorded with Python Path.stat() when the task worktree was created.
branch, head, merge, pr Exact owned branch, full PR head SHA, squash SHA, and positive PR number.
issues Every closing issue number returned by GitHub for this PR.
resources Complete bounded inventory, or explicit [] with independently verified empty-inventory evidence.

A resource requires id, kind, task_id, source_commit, matching repository and pr, and at least one exact path or provider_id. Its required ownership_manifest contains an absolute path and the original file's sha256; preserve that manifest outside the worktree and every removal root. The command verifies the manifest bytes, while the controller independently verifies its provenance and binding to the resource. Specialized resources also require the exact supported cleanup_tool. Every resource identity is checked before any resource is released. Supported kinds are generated_tree, vm, lifecycle, artifact, reservation, credential_bridge, process, and configuration. Specialized kinds require an existing supported owning tool; a kind name does not prove that capability. Preserve unsupported, retained, shared, permanent, user-created, active, or ambiguously owned resources and report the missing evidence or owning cleanup capability. The recorded creation source_commit must be the PR head or one of its ancestors; retain original resource identity when validation ran before later review commits.

For generated_tree, include an absolute path strictly beneath the task worktree and root_identity, the identity array from WindowsFiles().snapshot(path)["."]["identity"] in scripts.completed_task_files. Record this at creation, with task ownership; a cleanup-time snapshot alone cannot establish ownership. The command captures and reports every ordinary entry at preview/execution time. It rejects tracked files, reparse points, hard-linked files, nested Git repositories, and Git/credential-recovery roots. It pins ancestors and deletes matching objects through Windows handles; changed objects or new children stop removal and preserve remaining entries. Partial releases require a fresh inspection on retry. Never retrofit ownership merely because a directory is called node_modules or site.

Pytest validation output may use kind: "generated_tree" with the exact cleanup_tool: "PytestGeneratedTree" to select the bounded internal-link policy. Generic generated-tree resources and every ordinary cleanup call continue to refuse links. The opt-in accepts only Win32 symbolic-link leaves whose raw targets stay lexically inside the originally receipted root; it records the link object identity and raw reparse-data digest, permits internal chains and dangling links, and never traverses a link target. Junctions, external targets, root or ancestor links, and unknown reparse types remain refusals. Cleanup pins every snapshotted object and link before deleting any entry, then checks identity, stamp, and link digest; a changed link, new child, or replacement preserves the tree for fresh inspection.

The opt-in may select the top-level worktree .atlaso-local root only as an originally receipted pytest validation resource. Its immediate children are limited to pytest, markdown, mypy, ruff, native-policy, node-compile-cache, npm-cache, packer-plugins, names matching pytest-<suffix>, or packer-plugin-<number>.zip. Other content, including credentials and recovery state, remains ineligible; nested .atlaso-local paths stay rejected except for the explicitly selected fixed regression probes below. If a maintainer authorizes reselecting an existing validation root, bind it to its original creation receipt and keep the original ownership manifest and hash. A later link scan does not create per-link provenance or replace the root's original receipt; missing original evidence means preserve the root.

For the directory-gate cleanup regression only, PytestGeneratedTree may receive the explicit synthetic_directory_probes resource field. It must list each selected nested root using the exact relative layout pytest[-suffix]/test_real_directory_gate_prune<digits>/worktree/.atlaso-local; the bounded profile accepts at most 16 unique selections. Every selected root must contain exactly an empty nested empty directory and an ordinary result with spaces.txt file whose bytes are generated. The complete originally receipted pytest output root is still inventoried, and the profile rejects unselected recovery roots, extra entries, changed types, and links. It verifies the fixed file contents through the snapshot handle and checks them again on fresh deletion handles after all entries are pinned and before deleting any entry. A changed payload preserves the entire output root. This field does not broaden ordinary generated-tree cleanup or make .atlaso-local a general credential/recovery exemption.

Primary-checkout restoration, private advisory cleanup, non-task-owned branch/checkout exceptions, and issue-less Dependabot tasks retain their existing policy workflows. A single Codex task spanning multiple pull requests follows the schema-2 batch workflow below. This entry point refuses unsupported eligibility instead of relaxing its gates. Do not use its directory removal primitive for those exceptions.

Preview and execute

From the verified primary checkout, supply absolute paths in these PowerShell variables:

pwsh -NoProfile -File scripts/cleanup-completed-task.ps1 -Handoff $handoff -Evidence $evidence -Config $config

Omitting -Execute is a read-only preview. It does not fetch, create evidence files, release resources, mutate refs, remove worktrees, or rename tasks. Read the proposed exact entries/refs and any refusal. Execute with the same inputs:

pwsh -NoProfile -File scripts/cleanup-completed-task.ps1 -Handoff $handoff -Evidence $evidence -Config $config -Execute

Execution revalidates every transition; a saved preview is never authorization. The terminal order is validation_resources_released, remote_branch_absent, worktree_removed, then task_title_done. Remote deletion uses an atomic expected-SHA lease and verifies absence. Worktree removal uses non-forced git worktree remove. The exact unreferenced local ref is separately removed with an old-SHA compare-and-swap and verified absent. Remaining ignored files block resource completion before branch removal.

Live controller protocol

Run the command with bidirectional stdin/stdout, such as an agent-controlled terminal session. Each stdout JSON line with kind: "controller_request" contains a unique id, operation, and payload. The controller performs fresh supported-tool reads for that request, then writes exactly one JSON line to stdin:

{"id":"the-current-request-id","result":{"handoff_sha256":"the-requested-digest"}}

That minimal example illustrates framing only; each operation requires the complete result below. Responses are bounded to 256 KiB and 120 seconds. A non-object JSON response, mismatched nonce, missing field, error, EOF, or timeout refuses the transition with a structured result that retains the completed gates. Never pipe a saved response sequence into execution. The bridge is a trusted controller boundary, not authentication against a malicious operator who can write stdin or edit the program. Keep the originating task idle and unpinned for the entire reconciliation; a maintainer restarting it must stop cleanup first. Fresh snapshots do not provide a cross-application atomic activity lock.

Task inspection

For task.inspect, use supported list_threads and read_thread controls, paging as necessary. Independently bind the handoff to the originating task's recorded creation/cleanup evidence; the handoff's claims alone are insufficient. Inspect other owners, current pin/activity state, every linked PR/issue, review disposition, maintainer holds, resource inventory and ownership manifests, completed chained post-merge workflows, and downstream needs. Do not equate idle with cleanup-ready, an absent resource with ownership, or an empty workflow response with completion.

Return handoff_sha256, nonempty durable sanitized evidence_refs, and each of these fields as literal true only after verification: identity_verified, exclusive_ownership_verified, idle, unpinned, holds_clear, downstream_clear, inventory_verified, post_merge_complete, reviews_complete, and supported_tools_used. Return a nonempty post_merge_runs list for the complete applicable post-merge chain. Each entry contains exactly id, run_attempt, workflow_id, head_sha, and event, bound through the source CI and downstream handoffs. The command independently reads each run and requires matching identity and successful completion. Include applicable manual recovery runs, but exclude unrelated manual dispatches merely sharing the merge SHA. Preserve the chain's applicability evidence in evidence_refs; run selection must not omit failed or pending required work. For an empty inventory, also return inventory_empty_verified: true after explicitly verifying validation_resource_inventory_empty. Also return the exact supported-tool observed_title. It must match the handoff's task_title; a title-only retry may also observe the canonical completed title only after the worktree and both refs are independently absent. Missing capabilities must return a refusal. This is the controller's independent evidence attestation; the script does not infer semantic ownership or maintainer intent from task titles.

Resource inspection and owning tools

The entire inventory passes schema, unique-ID, task/source ownership, and source-ancestry preflight before any resource can be released. This includes entries whose release gates were recovered; duplicate IDs cannot reuse an earlier gate. Ownership manifests must also remain beneath the configured durable root. A removal scope cannot contain another inventoried resource's path; reconcile overlapping ownership through the existing owning tools before using this command. All resources' complete removal scopes are inspected before the first release, including provider-only resources and auxiliary paths. Intersecting scopes or later scope changes block release and require ownership reconciliation.

For resource.inspect, independently check the exact resource/provider, manifest, process/boot identity where applicable, and surviving reservations/claims/recovery state. Return literal booleans ownership_verified, inactive, retained, supported_cleanup, evidence_preserved, and absent. Preserve durable sanitized evidence outside every removal root. For no resources, task inspection must explicitly verify validation_resource_inventory_empty. For specialized resources, also return removal_scopes: every absolute filesystem path the owning tool will remove, including derived parent directories, disks, and auxiliary roots. An explicit empty list is valid only for a provider operation with no filesystem removal. Scopes must lie beneath the configured root and cannot contain either checkout, the handoff, configuration, evidence directory, or any ownership manifest. The command repeats this inspection before release and passes the validated scopes to resource.release; the controller must bind the owning-tool invocation to those exact scopes and refuse if its operation would remove anything beyond them.

For resource.release, call only the resource's existing supported owning tool with exact identity arguments: remove-atlaso-vm.ps1, remove-lifecycle-vms.ps1/-CleanupVmsOnly, or the documented Remove-AtlasoWorkstationArtifactRoot path with independent strict-descendant checks, as appropriate. Reservations, credential bridges, processes, and configuration require their own documented recovery/cleanup entry points. Do not substitute broad deletion or stop unrelated processes. Return success: true only after that tool succeeds. The script requests another independent resource.inspect; release acknowledgement alone cannot complete the resource gate. Generated trees are removed by this command and still receive independent absence readback.

Treat a task-created VMware LAN segment as a separate configuration resource with its exact provider_id, original creation commit, and preserved receipt path/hash in ownership_manifest. Use cleanup_tool: "Remove-AtlasoWorkstationLanSegment"; never inventory the entire shared preferences file as a removable resource. Its provider registration has no filesystem removal scope (removal_scopes: []). Complete the receipt-bound LAN segment release after VM removal and before releasing lifecycle result roots. Independently verify the receipt's task/PR/source binding, complete configured VM search roots, absent adapter references, and absent exact provider registration on each readback. The owning operation retains creation evidence and changes only the selected provider records. Open Workstation UI, active VMware processes, references, ambiguous ownership, or recovery residue keep validation_resources_released blocked. Preserve legacy segments without receipts and report the exact missing ownership evidence; VM absence alone never authorizes their deletion.

Before recording the aggregate resource-release gate, the command reinspects every resource, including whether its durable evidence survived release. Later eligibility checks repeat that aggregate readback and require completed remote/local ref gates to remain absent, even if a recreated ref points to the original SHA. Reappearance blocks completion and preserves the recreated resource/ref.

Disposable documentation caches

scripts.zensical_cache.ZensicalCache owns creation-bound Windows documentation caches. Inventory the exact checkout-local .cache as kind: "artifact" with cleanup_tool: "ZensicalCache". Generic generated-tree cleanup continues to require its original directory identity and must never adopt a cache recreated by Zensical.

Before a task build, choose a new durable receipt filename beneath the configured worktree root, outside the task checkout and every removal scope. Use a separate evidence directory that is not an ancestor of the checkout; placing receipts directly in a pinned ancestor would prevent Windows write-through publication. With the cache absent, run the canonical wrapper using the documentation interpreter:

.\.venv-docs\Scripts\python.exe scripts/build_docs.py `
  --cache-receipt $receipt --task-id $taskId --resource-id $resourceId --source-commit $sourceCommit

Use the independently verified originating task identity and creation commit. The wrapper reads the active Codex configuration, creates the exact .cache exclusively, and durably records its original identity and checkout binding before use. The wrapper holds an exclusive lifecycle claim from bootstrap through build, sealing, and redirects. It refuses unreceipted cache state and pins that directory throughout build --strict and sealing; the cache starts empty, so Zensical's native --clean replacement is omitted. Unexpected replacement or receipt publication failure stops the operation. Ordinary hosted CI retains the portable --clean --strict path.

After the child exits or process creation fails, sealing verifies the creation identity and ownership marker, and preserves a bounded inventory of exact identities and content hashes. The owned wrapper prints a JSON ownership_manifest record with the authoritative path and bounded-file SHA-256 after each successful seal, including a failed builder launch. Record that identity in the resource inventory. Preserve all creation, attempt, sealed, prepared, and absence receipts outside removal roots. A failed strict build may still seal its owned cache for diagnosis and eventual release; passing or failing validation does not authorize teardown.

To repeat validation before release, use the same receipt and original task/resource/source binding. The owner first verifies the current sealed identities and content hashes, then records a numbered attempt bound to the prior manifest before clearing its verified contents in place. The original directory and creation receipts remain unchanged, and the cache starts empty for the next strict build. Each attempt publishes new immutable sealed inventory and manifest files; refresh the inventory's ownership_manifest to the latest returned path and hash. A prior manifest cannot approve the new revision. Prepared or completed release blocks another build.

An interrupted bootstrap may resume only from a durable original receipt, matching checkout/cache identities, and an empty or exact-marker-only cache. It completes missing generation/marker steps before any builder use. An unfinished attempt cannot start another build or authorize release; preserve it for owning-tool sealing and independent recovery. Missing original provenance, pending receipt publication, changed entries, or replaced identities remain refusals. A caught failure before any receipt publication may roll back only the still-empty exact root created by that invocation. A process stop before the original receipt is durable has no independent identity proof and remains cleanup-blocked.

For each fresh resource.inspect or resource.release, construct the tool using the same active configuration, receipt, and independently verified binding (id, task_id, repository, source_commit, and absolute path). Call controller_call(operation, payload, approved_resource) only after independently verifying source ancestry, positive PR identity, manifest hash, exclusive task ownership, inactivity, retention, and downstream needs. The adapter's only removal scope is the exact .cache. Shared or retained state, changed contents or identities, reparse points, hard links, Git metadata, and credential/recovery trees require refusal and preservation.

Release durably records its sealed inventory before deleting checked objects through Windows handles. An interrupted retry accepts only already removed entries and directory timestamps changed by removal; new or replaced entries and changed file bytes block retry. Pending receipt publication requires reconciliation. Repeat fresh resource.inspect after release and require exact absence; acknowledgment alone cannot complete the resource gate. A later build needs a new creation receipt and resource identity after completed release; reappearance must be preserved. If deletion finishes before the final absence receipt is published, verified creation/sealed/prepared receipts and fresh namespace absence still establish preserved evidence. The enclosing controller can finalize its resource gate from those independent reads; inspection never writes a missing receipt, and unprepared absence remains a refusal. Receipt-backed absence readback also works after checkout removal; any recreated checkout must match its original identity.

Task-owned 1Password configuration

scripts.onepassword_task_configuration.OnePasswordTaskConfiguration owns only a task-created copy of the two supported Windows 1Password configuration files. Use kind: "configuration" and cleanup_tool: "OnePasswordTaskConfiguration". Generic generated-tree cleanup continues to exclude credential and recovery roots. This operation never decrypts configuration or retrieves credentials.

Prepare this copy before other task tooling creates .atlaso-local. First independently establish task ownership, the exact registered worktree beneath the active Codex root, its creation commit, and its primary checkout. Load the credential reuse procedure and validate the source configuration. Then run the following from the task checkout, using verified variables:

from pathlib import Path
from scripts.onepassword_task_configuration import OnePasswordTaskConfiguration

binding = {
    "id": "onepassword-configuration",
    "task_id": task_id,
    "repository": "mdaneri/Atlaso",
    "source_commit": source_commit,
    "path": str(worktree / ".atlaso-local"),
}
owner = OnePasswordTaskConfiguration(
    config=Path(active_codex_config),
    receipt=durable_evidence / "onepassword-copy.json",
    binding=binding,
)
ownership_manifest = owner.copy_from_primary()

The owner independently verifies the Git common directory, primary/task registrations, canonical HTTPS repository remote, source ancestry, and Git-ignore/tracking state. It requires a new destination directory and receipt; an existing directory or configuration is preserved without adoption, permission repair, or replacement. The primary directory and both source files must already have protected ACLs allowing only the current user and SYSTEM. Source and destination paths must be ordinary and single-link, with no reparse ancestors. The selector must identify the pinned Atlaso Environment. The owner copies bytes without decryption, records original identities through creation handles, and publishes durable creation and sealed receipts outside the worktree before the copy is available for use.

Keep both receipts and their returned ownership_manifest path/hash in the resource inventory, adding the verified PR number once available. Reconstruct the owner with the same original binding for each fresh resource.inspect or resource.release; invoke owner.controller_call(operation, payload, approved_resource) from the live cleanup controller. As with other specialized owners, the controller must first independently verify task/PR/source and manifest provenance, exclusive ownership, inactivity of every credential consumer, retention/downstream needs, and absence of credential recovery obligations. Owner filesystem checks and open-handle refusals do not replace those live process and task checks. Do not call release while a consumer may retain credentials in memory.

The receipt directory must already exist separately from both checkouts and their ancestors. A receipt directly under the configured worktree root is refused before configuration creation: ancestor handles held during copying would prevent write-through evidence publication there. Use a dedicated sibling evidence directory as in the example.

The sole removal scope is the exact task .atlaso-local directory. Release other task outputs through their own tools first: any child other than onepassword-environment-id and onepassword-service-account-token.dpapi, including caches, locks, and cleanup/recovery markers, blocks this operation. The owner rechecks protected current-user/SYSTEM ACLs, original directory/file identities, single hard links, bounded sizes, and sealed byte hashes. It pins all remaining objects before deletion and uses ordinary Windows handle disposition; active handles or newly added children refuse release. Primary configuration is never a removal scope.

Keep separately inventoried validation roots outside this directory so their removal scopes stay disjoint. An existing nested resource requires controller inventory reconciliation; this owner never waives the overlapping-scope refusal.

A prepared-release receipt precedes deletion. Retry an interrupted release with the same original receipts and binding; only already removed entries and the unchanged original survivors are accepted. After success, independently inspect again and require absence; a recreated directory is preserved. Pending receipt publication, an incomplete copy without a sealed receipt, changed identity/content/ACL, missing original provenance, or ambiguous ownership keeps cleanup blocked for explicit reconciliation. Existing copies made without this original creation evidence remain unsupported; retain them and report the missing provenance rather than generating a new receipt at cleanup time.

Disposable pytest Git fixtures

scripts.pytest_git_fixtures.PytestGitFixtures owns Windows disposable pytest Git artifact roots. Use kind: "artifact" and cleanup_tool: "PytestGitFixtures"; generic generated-tree removal continues to reject nested and bare repositories. This tool removes the complete owned fixture graph, including its internal Git registrations, through checked Windows handles. It never prunes or removes registrations in an external repository.

Before validation, construct the tool with the active Codex configuration, a new durable receipt filename outside the task worktree and every removal root, and verified id, task_id, repository, source_commit, and absolute path. The root and receipts must be beneath the configured worktree root. Call create() before using the new empty root; call register(repository_path) immediately after creating each ordinary repository, bare repository, or linked worktree. Keep every common directory and registered worktree inside that same artifact root. Original root and repository identities are recorded durably, not inferred from their names or a later cleanup snapshot. Every non-bare repository's effective Git working-tree root must equal its recorded creation root; external or redirected effective worktrees block sealing even when common metadata and registrations remain internal.

After validation stops, call seal() to preserve the complete bounded identity/content inventory and Git topology. The returned small .manifest receipt is the resource's ownership_manifest; record its SHA-256 in the task inventory. It binds the larger .sealed inventory, which binds the original creation receipts by hash. Do not change fixtures after sealing. If sealing stops between inventory and manifest publication, seal() validates the existing inventory against fresh identities, contents, topology, and original receipts before publishing the missing manifest. It never replaces existing evidence; a matching completed seal is idempotent, and changed state or pending publication blocks retry. Unrecorded repositories, external/shared common directories, foreign or locked registrations, object alternates, Git locks, reparse points, hard links, credential/recovery trees, changed identities, or changed file contents block release. Validation must finish and the controller must independently prove exclusive ownership and inactivity; the receipt alone does not authorize cleanup.

For each fresh resource.inspect or resource.release request, the live controller may call tool.controller_call(operation, payload, approved_resource) after independently approving that exact resource, its positive PR identity, source ancestry, originating task, retention state, and manifest hash. This adapter requires the request's handoff digest and exact removal scopes. Its single filesystem scope is the fixture root. Inspect again after release; the acknowledgment alone does not satisfy the enclosing cleanup protocol.

Release durably prepares the sealed inventory before deleting any entry and retains all receipts outside the root. It pins every expected entry and verifies file hashes through those handles before the first deletion, retaining the handles until each child is disposed and closed. Changes after inspection therefore cannot become a new baseline. An interrupted retry checks every surviving entry against that inventory, allows only missing entries from a prepared release and directory timestamps changed by child removal, and refuses new, replaced, or changed data. A surviving pending publication requires reconciliation. Successful release records durable absence evidence and independently reads back root absence. An absent root without prepared evidence does not establish ownership or completed release. If deletion finishes before absence publication, inspection reports evidence_preserved: false; the controller must stop. Resume the owning tool's exact-scope release() to finalize the durable absence receipt, then repeat controller inspection. Missing, pending, or invalid absence evidence never authorizes task cleanup. Legacy artifacts without original creation receipts remain preserved; never retrofit them or use broad directory deletion. Read-only Git objects use the checked-handle Windows disposition flag without changing file permissions or enabling deletion of active shared handles. Ordinary generated-tree behavior is unchanged.

Completion title

For task.title, use supported set_thread_title with the exact supplied title, then read_thread to retrieve the persisted title. Return persisted_readback: true and observed_title. The script uses completed_task_title.py to format and verify all linked identifiers and the single Done suffix. Do not archive the task. If mutable title controls are actually unavailable, return capability: "unavailable" and concrete capability_evidence; record the documented not-applicable exception without claiming a visible Done suffix. A denied or failed available control is a failure, not an unavailable capability.

Failure and interrupted recovery

All GitHub CLI evidence reads explicitly select github.com, independently of GH_HOST. The handoff must be an ordinary file beneath the configured worktree root and outside the target worktree; an arbitrary external or temporary path is not an approved durable handoff surface.

Exit zero means a successful preview or completed execution; inspect the JSON status to distinguish them. Exit one and status: "refused" give an actionable retry_condition and completed gates. Durable JSON evidence records bind each transition and prepared generated-tree inventory to the original handoff digest. Preserve the original handoff and controller evidence references alongside these records. Never use a journal's claimed gate to skip live checks. Each fresh invocation recovers the matching monotonic journal history and rechecks the continued absence of completed resources and refs. Reappearance refuses cleanup rather than authorizing a second deletion. Gates enter the reported completed list only after the prospective record has been flushed, fsynced, and published. An interrupted .pending record or conflicting history requires independent evidence reconciliation before another invocation can proceed. Fetch and push URLs must use the exact GitHub HTTPS repository URL. SSH origins are refused because local SSH commands and host configuration can redirect transport independently of Git's reported URL; configure HTTPS before retrying. Every eligibility check also verifies all effective Git push URLs; a separate fork push URL or extra destination blocks cleanup even when the fetch URL names the correct repository. Symbolic task refs, including dangling refs, block cleanup. Local deletion never dereferences the task ref. Index assume-unchanged and skip-worktree flags also block cleanup because they can hide edits from Git's cleanliness checks. Large controller evidence and snapshots are stored as bounded, hashed .evidence files; cumulative journals contain their references and remain within the same 64 MiB recovery limit. Preserve both file types. A retry verifies referenced payloads before continuing, and prepared transition records are persisted before destructive owning-tool or Git actions. Journal and payload publication uses Windows MoveFileExW with MOVEFILE_WRITE_THROUGH, or a rename followed by a parent-directory fsync on POSIX. A gate is reported only after that publication succeeds; file-content fsync alone is insufficient. See the Windows move contract. Each missing evidence-directory component is first created under a pending sibling name and durably published through the same parent before journals are written. A surviving .atlaso-cleanup-directory.pending sibling blocks retry until independently reconciled. POSIX retries also fsync the parent of an existing evidence directory. Windows inputs reject trailing-dot/space, reserved-device, and short-name aliases before containment checks. An absent remote main produces a structured refusal, including any already recorded gates.

An absent worktree with absent registration may resume exact matching local-ref cleanup. A fully removed branch and worktree may retry title-only completion without repeating destructive operations. A path absent while registration remains, changed ref, failed resource release, or stale title keeps the task actionable. Reconcile that exact condition through supported tools, then rerun. Successful earlier deletion cannot be rolled back by the command. The title-capability exception preserves its capability evidence and still records the literal task_title_done gate. After the title response, full task/GitHub eligibility and resource/Git/worktree absence are checked again before that terminal gate is recorded, including holds, task/downstream activity, issue closure, and post-merge workflow state. Reappearance leaves cleanup refused and the recreated object preserved, even if the title readback itself succeeded. Resource ancestry verification is durably recorded against the exact handoff before release. A recovered released-resource gate uses that evidence, so title-only retries do not require squash-merged task objects that Git maintenance has pruned. Handoff inputs must be regular single-link files no larger than 256 KiB; validation precedes the bounded payload read. Ownership manifests use the same bounded reader. Windows pins no-follow file and ancestor handles during the read; POSIX uses a nonblocking no-follow open. Both verify regular single-link identity and recheck size after reading. Generated-tree deletion repeats resource-specific ownership, inactivity, retention, capability, and evidence checks after task eligibility reconciliation, immediately before recording its prepared gate and removing the checked tree. Generated snapshots reject .git entries and bare-repository structures (HEAD, objects, and refs or packed-refs) at any depth. These repositories require separate inventory and their owning cleanup workflow. Nested .atlaso-local credential/recovery directories are also preserved for their owning cleanup workflow; only the explicit fixed synthetic regression-probe profile described above admits its known harmless fixture directories. Recovered journals and blobs use the same regular-file reader with the 64 MiB evidence limit. Before final ref/worktree removal, every remaining directory must be a parent of tracked source; empty ignored or untracked directories require inventory reconciliation and owning-tool release because Git's cleanliness checks do not report them.

Execution guards still apply. A tool refusal such as “blocked by policy” is evidence of an execution refusal, not proof of a specific Atlaso policy breach. Record the exact rejected operation on the controller's durable evidence surface, preserve remaining resources, and report the stated reason without inventing one. Do not retry through a different shell, bypass flag, or ad hoc deletion. A refusal that prevents the command from starting cannot be caught by its code; the controller must record it. Never mark Done after a failed or ambiguous gate.

Completed Task Cleanup

Agents and the primary-checkout cleanup controller must use scripts/cleanup-completed-task.ps1 for eligible completed ordinary task worktrees. Run pwsh -NoProfile -File scripts/cleanup-completed-task.ps1 with absolute -Handoff, -Evidence, and -Config inputs: omit -Execute for read-only preview, then add it for execution with fresh live controller evidence. Follow the command and bridge contract. Never replace a failed or refused command with ad hoc deletion; preserve remaining resources and report its exact retry condition. Unsupported policy exceptions retain their existing safeguards and require the documented controller workflow; this command does not waive ownership, resource-release, terminal-order, or title-readback gates.

An ordinary implementation task becomes cleanup-ready only after its pull request is merged, the merge commit is reachable from current origin/main, the linked issue is closed, applicable post-merge workflows are complete, and no review, deployment, release, or maintainer activity remains. Private remediation uses advisory_cleanup_ready instead of the nonexistent public-issue gate: an explicitly authorized advisory administrator must have merged the private pull request through its draft advisory, the resulting commit must be reachable from current origin/main, required advisory-side review and recorded local validation must be complete, coordinated release and disclosure activity must be finished, and the advisory must require no further task activity. Before becoming idle, a worktree-backed originating task must send a cleanup-ready handoff to a cleanup controller running from the repository's primary checkout. The handoff must name the repository, task identifier and current title, pull-request number, task-owned branch, absolute worktree path, pull-request head SHA, and merge commit SHA. A handoff is evidence to revalidate, never authority to skip a gate.

The primary-checkout controller must wait until the originating task is idle and unpinned, then independently re-fetch task, GitHub, and Git worktree state. First identify and verify whether the task uses the repository's primary checkout. Only for a non-primary target, independently read the supported Codex git-worktree-root setting at cleanup time and fail closed if it cannot resolve one exact safe root; a guessed or fallback path is never ownership evidence. It must verify the exact merged pull request and completed post-merge activity, then determine remote-branch ownership and local checkout/worktree ownership independently. Require exclusive task ownership before a destructive step or establish the external ownership required by the corresponding non-destructive exception below. Require a closed linked issue for ordinary work or privately revalidate every advisory_cleanup_ready criterion against the corresponding advisory record. Only a non-primary target must be a registered, clean, unlocked, non-reparse-point worktree beneath the resolved Codex worktree root. Never remove the primary checkout, a user-created or permanent worktree, or a worktree whose ownership or state is ambiguous. A squash-merged pull-request head need not be an ancestor of main only when the worktree HEAD equals the recorded pull-request head SHA and the recorded merge commit is reachable from current origin/main.

If cleanup discovers a task worktree outside the configured root, preserve its state and obtain maintainer direction; never move or delete it automatically.

Passing tests does not permit indefinite retention of disposable validation infrastructure. Inventory resources as they are created in a bounded validation_resource_inventory, binding each resource to its task, repository, source commit, PR when present, exact path or provider identity, and existing ownership manifest. Names alone never prove ownership. Include PR-numbered test VMs, lifecycle VMs, local and PR builders, disposable clones, address reservations, output claims, task-created disks and temporary networks, artifact/test roots, helper processes, credential-bridge recovery state, locks, and external test resources.

The originating task releases these resources through existing supported cleanup paths once required validation evidence is preserved, the PR is terminal, and no review, deployment, release, diagnosis, retry, or maintainer activity needs the environment. Failed or interrupted validation may retain resources only while diagnosis or retry needs them. This resource gate does not waive the existing merge, issue, post-merge, or private-remediation prerequisites for branch/worktree cleanup. Earlier per-operation sensitive-material cleanup and recovery remain mandatory.

Use remove-atlaso-vm.ps1 with the exact VMX and expected name after independently verifying ownership; use remove-lifecycle-vms.ps1 or the lifecycle wrapper's -CleanupVmsOnly for the exact PR-owned lab. These VM-only paths retain the result root; after preserving evidence and verifying ownership and quiescence, release that exact root with Remove-AtlasoWorkstationArtifactRoot using its exact configured-root binding as documented in the lifecycle guide. First use Assert-AtlasoStrictDescendantPath against independently configured permitted and canonical lifecycle roots; derive the expected lab path separately from validated task/PR identity, never from the candidate manifest path. Preserve existing identity, filesystem, shared-disk, provider-state, process-termination, and recovery safeguards. Release associated resources through their owning tools; VM removal alone does not prove reservations, claims, or recovery state released. Never delete shared, reusable, permanent, user-created, differently owned, or ambiguous resources.

The cleanup-ready handoff includes the inventory and durable, sanitized validation_resource_release_evidence: the cleanup entry point and result, exact resource identities, and verified absence of each disposable resource, VM registration and path, reservation, claim, process, lock, and temporary root. Preserve required validation and ownership evidence outside every root scheduled for deletion, on a permitted durable task/controller evidence surface. The primary-checkout controller independently reads back the exact resource states before recording validation_resources_released and before branch/worktree cleanup. For no resources, require an explicitly verified validation_resource_inventory_empty statement; missing evidence is never proof of absence. If another owner must perform teardown, hand off this bounded inventory and require the same ownership checks and independent readback.

Record explicit maintainer retention or a proven active downstream need as validation_resource_retention, with the exact resource, owner, reason, and retry condition. Any unresolved retention, failure, unsupported cleanup path, or ambiguous ownership records validation_resource_cleanup_blocked, preserves uncertain resources and recovery evidence, and keeps the task actionable. Do not delete the branch/worktree or mark Done to hide a blocked resource gate. Track missing cleanup capabilities separately; never substitute ad hoc deletion or broad VMware inventory cleanup.

For ordinary public tasks, enforce task_title_done with scripts/completed_task_title.py after all prior gates pass. Supply every linked issue and PR from verified task/GitHub evidence, not from a potentially truncated current title; use repeated --issue and --pr arguments and a short --description without traceability or completion segments. For an issue-less GitHub-managed Dependabot PR, independently verify the documented dependency-update exception and pass --dependabot; preserve every PR number and any linked issue that does exist. Ordinary tasks still require issues. The formatter puts all identifiers first, trims only the description to a conservative 60 UTF-16-unit budget, and retains exactly one " · Done" suffix. If the identifiers alone do not fit, keep completion blocked for maintainer direction; never drop an issue or PR. Use supported task-title controls to set the exact generated title, then read the persisted title through a supported task read tool and rerun the same formatter inputs with --observed-title. Record task_title_readback_verified only when verification succeeds. A rename acknowledgement alone is insufficient. A stale, truncated, missing, or duplicated completion marker blocks Done and requires an idempotent title-only retry after revalidating earlier gates. Do not repeat destructive cleanup on a title retry. If title controls are unavailable, retain the existing capability-evidence exception. Private remediation keeps its sanitized title and private evidence; never pass advisory identifiers to this public formatter, and still require exact supported-tool title readback.

Batch cleanup for tasks with multiple pull requests

When one Codex task owns multiple pull requests, use the batch cleanup entry point with a schema-2 manifest containing exactly schema, task_id, task_title, description, and handoffs. schema is integer 2; handoffs contains 2–16 entries, each exactly an absolute child path and its raw-file sha256. Each child remains a schema-1 cleanup handoff. The batch and every child must bind the same task ID, title, description, repository, and primary checkout; PR numbers, branches, and worktrees must be distinct. Every removal scope must be disjoint, and no protected batch, child, or ownership evidence may be inside any removal root.

The batch controller independently inventories the full task and verifies all members before execution. For every task.inspect, return batch_identity_verified: true, the exact batch_sha256, and batch_membership_complete: true only when the complete task-to-PR inventory and every child's identity, merge, issue, review, resource, and lifecycle gates have been verified. All children are preflighted before the first mutation. Execution holds the batch lock plus every member lock, revalidates the complete batch before each member transition, and records child progress as member_complete; child journals never record task_title_done. Per-member journals live under <evidence>/<batch_sha256>/<child_handoff_sha256> and stay bound to each child's original handoff digest. Every preflight checks ignored files and empty untracked directories outside the validated resource removal scopes. It also rechecks absence for resources already released, so later residue or reappearing resources block further cleanup.

After every member has passed its resource, remote-ref, worktree, and local-ref gates, refresh the complete inventory and all child eligibility evidence. Then issue one task.title request with the aggregate title and batch binding; the controller must read back the persisted title. Only the anchor member journal records batch_task_title_done. A retry reloads the original journals and revalidates all members before attempting title completion, so a completed child is not cleaned up again. Manifest or child digest changes, duplicate or overlapping members, missing PRs, pending peers, or ambiguous ownership refuse the batch before mutation or block the aggregate title. A single-PR task continues through the ordinary schema-1 flow and does not need a batch manifest.

Terminal order:

  1. validation_resources_released
  2. remote_branch_absent
  3. worktree_removed
  4. task_title_done

For ordinary remote_branch_absent, delete only the exact task-owned branch from its same-repository GitHub remote. If the ref exists, require it to equal the pull-request head SHA and delete it with an atomic expected-SHA lease such as --force-with-lease=refs/heads/BRANCH:HEAD_SHA; a lease rejection or unsupported atomic guard blocks cleanup. Then verify the remote ref is absent. For private remediation, satisfy advisory_remote_branch_absent only on private surfaces: bind the exact temporary private fork, private pull request, branch, task, and recorded head SHA through the advisory; if the ref exists, require it to equal that head and use the same atomic expected-SHA lease before deleting only the ref and privately verifying absence. An already absent ref satisfies the gate only after the same private identity and merge evidence are verified. Never delete the temporary fork, change advisory state, or enable repository-wide automatic branch deletion. For worktree_removed, first verify that the exact local task branch is absent or still equals the recorded pull-request head and is referenced only by the target worktree. Use git worktree remove, prune only stale worktree metadata for the affected repository, and verify both the path and registration are absent. Then require the local branch to be unreferenced by every registered worktree, delete only that exact ref when present, and verify local_task_branch_absent before recording worktree_removed. A retry interrupted after the path and registration disappeared but before local-ref deletion may enter worktree_removal_resume only when the path and registration remain absent. The worktree removal remote branch gate is either verified absent or recorded not applicable through non_task_owned_remote_branch_preserved, and the same task ownership, pull-request head, and merge evidence prove that the exact unreferenced local branch is safely deletable or already absent. For a task running in the primary checkout, the initial path requires a clean checkout still at the recorded task head; fetch current origin/main, switch to local main without force, fast-forward it exactly to origin/main, and verify the resulting HEAD. A retry interrupted after that switch may enter primary_checkout_resume only when the checkout is clean on local main and a fresh fetch and non-forced fast-forward makes it equal current origin/main. The primary checkout remote branch gate is either verified absent or recorded not applicable through non_task_owned_remote_branch_preserved; the local task branch must still equal the recorded pull-request head while checked out nowhere or already be absent under the same task ownership and merge evidence. Delete the exact local task branch when it remains, record primary_checkout_restored, then record worktree removal as not applicable and never remove the checkout. For task_title_done, use supported task-title controls to append the exact suffix " · Done" once, preserving the description and issue/pull-request traceability. Keep the completed task unarchived unless a maintainer separately requests archival. Only when the runtime exposes no supported mutable task-title control, record task_title_done as verified not applicable with the capability evidence; do not append or claim a visible Done suffix, and do not block otherwise-complete cleanup on the unavailable control.

For an existing ordinary pull request, evaluate remote and local ownership separately after independently verifying the exact merge, reachable merge commit, closed linked issue, and completed post-merge activity. When the remote branch is non-task-owned, preserve it, record non_task_owned_remote_branch_preserved, and record remote_branch_absent as verified not applicable; this does not exempt a task-owned local worktree from normal removal. When the local checkout or worktree is non-task-owned, preserve it and its local refs and metadata, record non_task_owned_checkout_preserved, and record worktree_removed as verified not applicable; this does not exempt a task-owned remote branch from normal deletion. Apply these decisions in terminal order. Ambiguous ownership blocks the affected transition and the Done suffix.

Any failed or ambiguous gate blocks task_title_done; leave the task actionable and report the exact retry condition. The daily Codex cleanup automation is the reconciliation backstop for missed handoffs and partially completed terminal transitions, but it must apply the same checks and ordering. Private vulnerability remediation additionally follows SECURITY.md: keep titles, handoffs, controller output, advisory identity, and temporary-fork remote operations sanitized and private; block rather than expose or guess when private state cannot be verified; and do not treat an advisory merge as lifecycle completion while coordinated release, disclosure, or authorized advisory-state work remains.