# Proof audit pass 1 — runtime fixture matrix

PR [#26038](https://git.cafe/pingdotgg/t3code/pull/26038), branch `fix/issue-25995`.
Before: [dc84b0962e](https://github.com/pingdotgg/t3code/commit/dc84b0962efa4f4e05ef392c08986a9cd7c994d1). After: [06c0db5ad7](https://github.com/pingdotgg/t3code/commit/06c0db5ad787d743bd95e64935c0d111ad68f8e1).
Read-only source audit in `/home/ubuntu/wt/pr-26038`; no server, provider, browser, test runner, or capture was started. Test coverage below means the named test definition was inspected, not that its execution was independently verified. PR metadata reports 40 successful checks, zero pending/failing, on the requested head.

## Findings first

1. **Runtime proof is still missing at the requested revisions.** The PR body has transcripts from other before/after commits and explicitly says deleted-thread removal was not checked in a running app. Treat those as historical context, not completed rows in this handoff. In particular, a real deleted worktree must still disappear in R03–R05, and after the eligible sweep in R06/R09.
2. **A release span alone does not establish the trigger.** `ProviderSessionManager.writeReleasedSessionEvents` iterates attached thread IDs. Detaching/deleting a thread removes its attachment; a later release can produce the span with no corresponding stopped event for that deleted thread. Use a live attached thread for real negative captures and an attached surviving child for R04. Record the actual persisted `provider-session.updated` payload, not just the span. U01 is explicitly a service harness case, not a promised ordinary-app reproduction.
3. **The stopped-session tests cover the principal cases, but do not cover every new scheduling branch.** Error status, deterministic active/pending/mixed-outbox transitions, activity/grace requeueing, a deletion arriving inside an older sweep, top-level sweep failure, and Windows matching in the new event filter need the harness variants identified below. Existing retention/path tests are labelled partial rather than represented as trigger tests. These are proof gaps, not confirmed production defects from this audit.
4. **No changed UI render path exists.** The only production diff is [storageCleanup.ts](https://github.com/pingdotgg/t3code/blob/06c0db5ad787d743bd95e64935c0d111ad68f8e1/apps/server/src/storageCleanup.ts). Its report schema, report rendering, permissions, and wire contracts are unchanged. Viewport and theme are N/A. A runtime event/report/trace recording replaces still images; there is no reason to multiply these rows across web, desktop, mobile, light/dark, or screen widths. The existing Settings report can refresh less often because automatic sweeps are skipped.

## Paths and branch inventory

- `StorageCleanup.layer` is installed in the real server. `start` subscribes to terminal metadata, settings changes, and orchestration domain events. It requests a startup sweep and then repeats hourly. A relevant settings change also requests a sweep. `runNow` queues a manual sweep; WS `server.runStorageCleanup` calls it. WS `server.getStorageCleanupReport` streams the same `reports` reference. These entry points all share the worker and `sweep`.
- Domain-event input: `thread.deleted` increments the deletion counter and requests cleanup whenever any resolved environment/project policy enables deletion cleanup. Only `provider-session.updated` with `stopped` or `error` proceeds through the session gate. Other event types and nonterminal session statuses do nothing. A stop/error passes if any deleted worktree is waiting, if the deletion counter has not been swept, or if cwd equals/is strictly inside a retained deleted worktree. It must still pass the resolved policy guard. Empty retained paths, exact path, descendant, outside/prefix sibling, multiple retained paths, and Windows case normalization are distinct inputs.
- `cleanWorktrees` now returns `{ waiting, kept }`; no enabled worktree policies returns false/empty. `keep` records paths only for deleted owners with deletion cleanup enabled. `waitForStop` additionally marks running/pending group work, open terminals (both checks), settle-script grace, nonfailed pending deletion effects, changed activity/ownership during recheck, and live provider sessions. Outbox `succeeded`/`cancelled` rows are excluded; all-failed rows retain the checkout by path without setting the global wait flag; a mixed failed/nonfailed set must set it.
- Other retention reasons collapse to one path-scoped class: dirty tracked/untracked/ignored files under the selected keep rule, incompatible ownership/branches, symlink/outside-managed-root/project-root/not-linked protection, or final repository/commit/settings/submodule rechecks. They do not acquire a separate broad wakeup. A per-worktree failure also retains just its path; a failure of the whole worktree phase conservatively sets global waiting. Removed/nonexistent worktrees must fall out of retained state on the next successful sweep. Disabled deletion cleanup and live-owner candidates never populate deletion wait/path state.
- `sweep` snapshots the deletion counter at its start, publishes the worktree wait/path result, and records only that starting counter as swept. A deletion arriving later must remain outstanding. The worker coalesces pending automatic requests and serializes manual/automatic sweeps. Counts, report truncation at 200, titles, error wording, and file-category rendering are not changed; the retained path list must not be truncated to the report limit.
- Provider normalization is shared. Codex through the configured CLIProxyAPI hub is sufficient for the real-app rows; no adapter changes require six separate provider runs. Web/desktop/mobile and local/remote clients invoke the same server paths; no connection-mode contract changed. Host path casing is the one platform-specific branch and is assigned H06.

## Capture contract and common seeding

Every row below is a **recording**: an ordered runtime transcript, not a screenshot or a single report. Compare the exact before/after commits above. Use isolated T3 homes and disposable repositories under a new timestamped run directory in the capturer's scratch subdirectory; never the live userdata. `T3CODE_DEV_SEED=0` is appropriate for these deliberately small scenario fixtures. Preserve raw `server.trace.ndjson`, driver action/event transcript, report stream, and filesystem/Git observations before upload.

Use authenticated WS RPC with the repository's typed RPC client/serialization (plain ad-hoc JSON-RPC is not an established substitute). The real endpoints are `projects.mutate`, `orchestration.dispatchCommand`, `server.updateSettings`, `server.runStorageCleanup`, `server.getStorageCleanupReport`, `terminal.open`, and `terminal.close`. Commands include `thread.create`, `message.dispatch`, `thread.delete`, `thread.archive`, `thread.settle`, and `provider-session.detach`; normal turn completion does not itself imply session release. Settings are also reachable at `/settings/storage` without needing to open a browser for this capture.

Common policy: `storageCleanup.worktreeOnDelete: true`, `worktreeAfterDays: null`, `worktreeSettledAfterDays: null`, `worktreeOnMerge: false`, `worktreeUnchanged: false`; choose `worktreeKeepWhen: "any-local-files"`. Clear project overrides or record their exact resolved rules. `worktreeCleanup: null` inherits those legacy storage rules; `mode: "off"` overrides them. For project custom rules use `projectSettingsOverrides[projectId].worktreeCleanup = { mode: "custom", rules: ... }`. Seed a managed linked Git worktree with a real branch and an initial commit; establish project/thread ownership through app RPC, not projection-table edits. Keep owner/child idle and free of pending requests/tasks unless that is the fixture.

Subscribe to reports and orchestration events before actions. Await startup and any settings-triggered report before marking the baseline. Do not call manual cleanup or change settings during a stop-only observation window: either would manufacture a sweep. Keep the server alive through the observation window, record its boundaries and the next deliberate positive-control trigger, and show the latest report timestamps unchanged where a stop is skipped. Trace spans are exported at completion: preserve a properly flushed trace and inspect start times so a sweep already running before the event is not attributed to it. There is no public `StorageCleanup.drain` RPC; negative real recordings therefore complement, rather than replace, the deterministic unit tests.

For actual releases prefer the normal 30-minute idle timeout after a short completed Codex turn, with the thread still attached. It is not configurable through ordinary settings; shortening `layerWithOptions` is a harness run and must be labelled. A graceful shutdown is an optional second trigger, but the before server must demonstrably process the event and sweep before teardown; teardown cancellation alone cannot prove filtering. Do not assume Stop, archive, or detach gives an attached-thread `stopped` event—verify the persisted event first. Never terminate a provider by a name/path match.

Minimum real capture set is **one Linux runtime × no viewport × no theme × R01–R07 and R09**, before and after; R02/R03 can share a staged scenario, and R09 can be their locked-worktree variant. R08 supplies unchanged-trigger controls in the same runs. U01–U03 use named unit evidence. H01–H07 are explicitly labelled harness/coverage extensions required to claim every changed branch, not separate UI capture requests. Runs may cover several rows; retain row/action boundaries. No duration target is imposed: this change reduces sweep frequency, not the cost of each sweep. A small DB proves the trigger; a production-size timing run is optional.

## Fixture matrix

Status: **PENDING REAL** = requested recording absent; **UNIT ONLY** = exact named test exists, execution evidence not independently reviewed; **HARNESS GAP** = no direct complete scheduling test/capture found; **PARTIAL UNIT** = named tests cover retention or adjacent behavior only. All rows are recording, including harness event/TestClock transcripts.

| ID | Fixture data and trigger | Diff effect / before → after expectation | Drive through the real app or labelled harness | Required evidence and existing named unit coverage | Status |
| --- | --- | --- | --- | --- | --- |
| R01 | One live attached Codex thread; zero deleted worktrees; startup complete; emit `provider-session.updated: stopped`. Include ordinary running/ready updates before release. | **Changed:** before stop requests a full sweep/readThreads; after no new sweep/readThreads or report. Nonterminal updates cause no sweep on either side. | WS create project/thread, send a short turn, await completion, leave attached for idle release. Shutdown is optional corroboration, subject to the caveat above. | Recording: baseline automatic report; actual stopped event with cwd; `ProviderSessionManager.writeReleasedSessionEvents`; before `StorageCleanup.sweep` + `StorageCleanup.readThreads`; after bounded absence and unchanged report while alive. Unit: `does not sweep when a live thread's session stops and no deleted worktree waits`. | PENDING REAL |
| R02 | Two deleted worktrees retained only for dirty tracked files, no transient blockers; a live session stops outside both. Include sibling-prefix cwd (`feature-other`) as outside. | **Changed:** before sweeps and keeps dirty paths; after no sweep. Neither side deletes files. Matching must examine all paths, not just the first. | Edit scratch tracked files; delete owner threads via RPC; allow deletion effects to finish; take a labelled baseline manual report if needed. Run attached Codex in another project/worktree and idle-release it. | Recording: retained reasons `Has uncommitted changes ...`, both paths still exist; outside stopped cwd; before extra report/readThreads, after unchanged report. Unit: `does not sweep for a kept deleted worktree when a session elsewhere stops` (one path; sibling/multiple variants extend it). | PENDING REAL |
| R03 | Retained dirty deleted path becomes clean; session still attached to a surviving thread in that path stops. Test exact cwd and a child directory; repeat outside stop after successful removal. | **Preserved positive; new gate exercised:** both sides sweep and remove with `Thread was deleted`. After removal, unrelated stops must again skip. Descendant must match; path equality is separate. | Keep a live surviving child/session or another attached runtime in the deleted checkout; restore only the fixture edit, then allow idle release. For descendant cwd, establish it when creating the runtime/thread, not by merely issuing shell `cd`. | Recording: baseline dirty-kept report → file restored → stopped cwd → automatic sweep → removed report plus path absent and `git worktree list` entry gone → later outside stop skipped after. Unit: `retries a deleted thread's worktree kept for another reason when a session in it stops` (exact cwd only). | PENDING REAL; descendant/reset variants lack direct scheduling unit |
| R04 | Deleted parent owns checkout shared by a valid live native subagent descendant; parent/child same branch, child idle; child's live session protects path, then stops. | **Preserved:** a stop on the child must remove the deleted parent's worktree. Matching cannot require the event's thread ID to equal the deleted owner. | Have the real provider report a native child in the parent's worktree; retain the child attached after deleting the parent. Confirm lineage/branch/cwd in app records. If the chosen provider lifecycle does not leave such a child, use the named service harness and label it; do not fake native lineage in SQLite. | Recording: `Provider session is still open` → actual live-child stopped event → sweep → deleted owner `removed`, path absent. Unit: `removes a deleted thread's worktree once a live thread's session in it stops`. | PENDING REAL; exact harness fallback exists |
| R05 | Deleted clean worktree protected only by a terminal; second dirty deleted path optional; close terminal, then stop a session **elsewhere**. | **Preserved:** terminal closure itself only updates metadata; later outside stop must still sweep and remove the now-unblocked deleted worktree. Dirty second path stays. | Open terminal via RPC under another live thread with cwd/worktreePath in the target checkout so deleting the owner does not automatically clean up that terminal. Delete owner, await open-terminal report, close terminal, idle-release outside Codex session. | Recording: `Open terminal` → terminal-close metadata → outside stopped event → automatic sweep → removed report and filesystem absence. Unit: `removes a deleted thread's worktree once its terminal closes and its session stops` deliberately uses outside cwd. | PENDING REAL |
| R06 | Fresh `thread.deleted` after a completed no-deletion sweep, with no live provider needed. | **Preserved:** deletion always requests an automatic sweep even when wait=false/kept=[] and even if there is no later stop. Removal must still happen on an eligible sweep. | Delete an idle managed-worktree owner via RPC. Inspect outbox effects and report. If initial sweep finds pending deletion effects, record that honestly; after they finish, use a separately labelled manual sweep to prove removal, or the later stop in R05. Do not claim deletion implies immediate removal before effects finish. | Recording: `thread.deleted` → automatic sweep/report; final eligible report `Thread was deleted` and path absent. Units: `removes a deleted thread's worktree with no subagents` / `... with native subagent` verify removal through runNow, not the event trigger. | PENDING REAL |
| R07 | Effective deletion cleanup disabled everywhere; repeat with only a project custom rule enabled, then disabled again. All policies disabled is a separate phase from deletion disabled but age policy enabled. | **Guard preserved; state return changed:** no event-driven sweep while disabled, including stop/error/deletion. Project-only enable must permit R06/R05; settings changes themselves still sweep. Live age-policy candidates must not create deletion-wait state. | WS settings patches using resolved rules described above. Await each settings report before event windows. Include environment `mode: off` plus one project's custom-on override, then remove it. | Recording: actual effective settings, phase baselines, stopped/deleted events, absence/presence of event sweeps. Unit `applies the remaining subagent's rules when deletion cleanup is off` covers live-owner rule selection only. | PENDING REAL; policy scheduling unit gap |
| R08 | Unchanged startup/manual/relevant-settings triggers; include one archived live owner eligible by age in a separate control phase. | **Unchanged control:** each trigger still sweeps; archived shells remain included. Hourly behavior is source-identical and assigned H07 if accelerated proof is wanted. | Restart isolated server for startup; call runStorageCleanup for manual; change one cleanup rule for settings. Create/archive fixture thread and use age 0 after turn completion; keep provider effects quiescent. | Recording: trigger, sweep, report and archive removal; manual trigger is `manual`, startup/settings `automatic`. Units: `automatically removes an eligible archived thread checkout`; `serializes concurrent requests and returns each run's report`. | PENDING REAL control; can share other runs |
| R09 | Deleted worktree removal fails because scratch worktree is Git-locked; unrelated stop, then unlock and stop a session in the retained path. | **Changed scheduling, preserved failure:** before unrelated stop retries; after it skips. Failure must retain path so in-path stop retries and eventually removes after unlock. It must not mark all session stops as relevant. | `git worktree lock` on the disposable fixture checkout, delete owner; establish failed baseline with effects complete; outside release; unlock; release a surviving attached in-path session. Can extend R02/R03 with this variant. | Recording: failed report reason beginning `git worktree remove: fatal:`, path exists; outside stop skips after; unlock + inside stop removes. Unit `reports git failures without aborting the remaining categories` covers failure reporting only, not retained-path event scheduling. | PENDING REAL; scheduling unit gap |
| U01 | Deleted owner's own synthetic stopped event clears live-session blocker. | **Preserved:** both sweep and remove the deleted worktree. | **Unit-only harness:** normal app deletion detaches the owner, so an own-thread stopped event after deletion is not guaranteed. No fabricated RPC event should be called a real provider release. | Exact test: `removes a deleted thread's worktree once its session stops`. Record filtered test result if this row is used as proof. | UNIT ONLY |
| U02 | Deleted worktree with only permanently `failed` outbox effects; unrelated stopped event. | **Changed:** before sweeps; after skips; both must retain checkout as `Thread deletion is still pending`. | **Unit-only harness:** no ordinary RPC sets an outbox effect to arbitrary failed status. Fixture's setOutboxStatus covers this directly; do not mutate the real app DB to simulate it. | Exact test: `does not sweep for a deleted thread whose cleanup effect failed when a session elsewhere stops`. In-path retry still must retain while failed rows remain; that extension is not in this test. | UNIT ONLY |
| U03 | Stop delivered while deletion-triggered sweep is blocked in its live-session check, before that sweep publishes waiting state. | **Preserved race:** both ultimately remove; after must queue follow-up despite the previous completed sweep saying no deletion waits. | **Unit-only deterministic harness:** Deferred gates in the test control the exact ordering; racing real requests is not dependable evidence. | Exact test: `removes a deleted thread's worktree when its session stops during the deletion's sweep`. It covers a stop within the deletion sweep, not H03's new deletion inside an older sweep. | UNIT ONLY |
| H01 | Deleted path waits for live group running/queued/background/runtime-request work, or outbox `pending`/`running`; include mixed failed+pending, then all succeeded/cancelled. Outside stop follows completion. | **New wait flag:** any outside stop must sweep while a transient blocker was remembered; after blocker completion a safe path is removed. All-failed must instead follow U02. | App can create pending work/requests and deletion effects, but exact outbox/check ordering needs a **labelled service harness**. Need a direct scheduling test for these status combinations; none found. | Partial tests: `checks the whole owner group (%s)` running/pending variants; `retains %s while background work is pending`; `retains %s while a runtime request is pending`. Those are not a deleted-path outside-stop scheduling test. | HARNESS GAP |
| H02 | Deleted parent with just-settled child (60s grace), or child activity/ownership changes between snapshot, first recheck, and measurement recheck. Clear blocker then outside stop. | **New wait flag:** retained path must globally wake on later stop; grace alone does not schedule a timer. Do not expect automatic removal at exactly 60s with no later trigger. | Real commands can settle/change activity, but deterministic phase placement uses **TestClock + Deferred harness**. Need scheduling extension; existing tests assert retention only. | Partial tests: `waits out the settle grace of a subagent that just settled`; `cancels removal when a child changes during measurement (%s)`; `keeps a worktree when %s during size measurement`. | PARTIAL UNIT; HARNESS GAP for later-stop removal |
| H03 | After startup, an older sweep reads no deleted paths; a new deletion arrives while that sweep is in progress; its result publishes false/empty; a subsequent stop arrives before a sweep started after that deletion completes. Include two deletions across sweeps. | **Counter invariant:** later stop must still request/coalesce a follow-up; older result cannot acknowledge a deletion it did not start after. Final eligible deleted paths are removed. | **Labelled deterministic harness:** app RPC can race deletion/manual sweep but cannot pause the exact read/publish boundary. Requires Deferred milestones; no direct named test found for this ordering. | U03 is adjacent but not this sequence. Capture counter/order through actions/events/sweep starts, not arbitrary sleeps. | HARNESS GAP |
| H04 | Stop before initial worktree phase publishes; separately whole cleanWorktrees phase fails (SQL/decode/readThreads failure), then recovers. | **Conservative fallback:** stop remains eligible before initialized state and after global failure. On recovery with no deleted paths, unrelated stops skip again. Global failure differs from R09's per-path failure. | **Labelled fault-injection harness:** no normal validated RPC creates a malformed projection or selected SQL failure; need injected service failure. No direct named scheduling test found. | Record whole-phase failed report with null path/thread, later outside stop causing sweep, then recovery/reset. Do not substitute the per-worktree Git-lock test. | HARNESS GAP |
| H05 | Repeat gate truth values with `provider-session.updated: error`; nonterminal statuses and unrelated events when wait=true or deletion outstanding. | **Changed gate applies equally to error:** error skips/sweeps as stopped does; other status/event types never trigger even with remembered waiting. | Normal provider/runtime failure can reach error, but no stable safe app fault control was found; use a **labelled event harness**. The newly added stop helper hardcodes stopped, so it is not error coverage. Nonterminal phases can also be recorded in R01. | No direct named cleanup event-filter test found for error/nonterminal variants. Require actual error event in real run if real failure is used. | HARNESS GAP |
| H06 | Retained path vs cwd: Windows casing-only exact/descendant; Linux casing-different; `..` normalization; sibling-prefix/ancestor exclusion. | **New path gate:** Windows normalized exact/descendant match; Linux distinct case and outside paths do not. Matching fix on requested head resolves the old review finding, but lacks direct trigger proof. | Linux exact/descendant/outside runs are R02/R03. **HostProcess + win32 Path harness** for Windows; a Linux string named `C:\\...` is not Windows validation. | Partial existing `compares thread paths with host casing rules and excludes existing projects (%s)` concerns orphan paths, not this event filter. Need a dedicated stopped/error scheduling variant. | PARTIAL UNIT; HARNESS GAP for new path gate |
| H07 | Multiple kept/deleted paths, one transient wait plus static keeps; >200 report entries with a retained path omitted; hourly refresh replaces old wait/path state. | **Aggregate state:** any transient blocker globally enables stops; no-transient multiple paths match any member; truncating report cannot lose retry path; hourly still sweeps without session triggers. | Two-path real representative is R02/R05. Large-count and one-hour advancement use a **labelled harness**, not a large production DB or a one-hour wall-clock capture. | Partial `caps entries while preserving totals and prioritizing removals`, `keeps orphan results visible when thread results fill the report`, and startup archived-removal test cover report/startup behavior only. No direct scheduling/one-hour state-refresh test found. | PARTIAL UNIT; HARNESS GAP for aggregate/refresh scheduling |

## Acceptance and handoff

For a minimum practical runtime proof, complete R01–R07/R09 paired sequences and include R08 controls in those recordings; R04 can use its explicitly labelled named-unit fallback if the real provider cannot preserve the needed native child. Supply exact test execution evidence for U01–U03. This is deliberately independent of the optional historical PR transcript. To assert exhaustive coverage of the changed state machine, also resolve H01–H07 with labelled harness evidence or explicitly retain the limitation; no screenshot can close them.

No code correctness blocker was established in this proof-planning pass. The old Windows review comment is addressed in the requested source; a regression test for that new filter still belongs in H06. No edits, pushes, PR comments, or repository cleanup were performed. No servers were started. Scope covered: full two-file diff, cleanup callers/event/settings/terminal worker paths, provider release/detach lifecycle, RPC/settings contracts, report consumer, existing test definitions, PR body/review comments/check metadata. No runtime results are claimed.

FRICTION: forge | One repeat of cafe comments/reviews returned retryable SERVER_ERROR (ConnectionRefused); subsequent reads succeeded. | Reliable forge connection would avoid the retry.

VERDICT: INSUFFICIENT R01–R09 exact-revision recordings; U01–U03 execution evidence; H01–H07 complete harness evidence
