Skip to content

Commit 49c3f58

Browse files
[Fix] Unit tests report teardown errors after Task cleanup (#1527)
* fix(task): await disposal cleanup (#1526) * fix(task): cover cleanup completion branches * fix(task): preserve abort completion semantics * test(task): cover disposal failure branches * test(task): fail fast on stalled disposal * test(task): cover multi-task shutdown disposal * test(formal): model task cleanup protocol * test(formal): model provider cleanup phases * fix(task): simplify disposal promise memoization * test(task): assert rejected abort invocation --------- Co-authored-by: Roomote <roomote@roomote.dev>
1 parent e12a42e commit 49c3f58

13 files changed

Lines changed: 832 additions & 41 deletions
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Task cleanup protocol model check
2+
3+
Zoo Code checks the in-memory task cleanup protocol with a bounded explicit-state explorer. It runs under the umbrella command:
4+
5+
```sh
6+
pnpm lifecycle:model-check
7+
```
8+
9+
For focused debugging, run:
10+
11+
```sh
12+
pnpm cleanup-protocol:model-check
13+
```
14+
15+
This is a separate child model from the persisted task lifecycle and shared-store concurrency models. It follows the native tool-call parser model pattern: keep an independent bounded state space for an independent protocol, require every action and semantic landmark to remain reachable, and connect the abstract claims to focused production tests.
16+
17+
## Bounds and environment actions
18+
19+
The model uses two tasks and explores every reachable interleaving through depth 20, with an explicit 100,000-state budget. Abort, disposal, final-save, provider abort/drain phases, and shutdown-cursor state are modeled directly. Independent abort and disposal calls may interleave freely, while provider-initiated calls are gated to the current shutdown task. Cleanup and editor-reversion settlement or rejection are environment actions, so the explorer does not assume they eventually occur.
20+
21+
The model checks these finite safety properties:
22+
23+
1. repeated abort and disposal calls reuse their first logical handle and start each operation at most once;
24+
2. final message persistence, or the history-task save skip, cannot occur before editor reversion settles or rejects;
25+
3. abort may complete while ancillary output cleanup remains pending;
26+
4. disposal completes only after cleanup and reversion both settle or reject;
27+
5. provider shutdown advances through tasks only after each task's abort and disposal reach terminal states;
28+
6. shutdown advances through tasks in registry order and is complete exactly when every modeled task is drained; and
29+
7. abort, ancillary cleanup, or disposal-start rejection is isolated so shutdown can continue to later tasks.
30+
31+
Named landmarks require abort completion during pending ancillary cleanup, contained cleanup rejection, final-save attempt after rejected reversion, a history-task final-save skip, two-task shutdown completion, and shutdown continuation after rejected abort, ancillary cleanup, or disposal start.
32+
33+
These are bounded safety and reachability claims only. The model does not claim filesystem or editor Promise liveness, fairness, timing bounds, arbitrary task counts, or that cleanup can never remain pending. Deterministic Vitest coverage in `Task.dispose.test.ts`, `Task.spec.ts`, and `ClineProvider.spec.ts` exercises the corresponding production Promise identities, ordering, rejection handling, and multi-task shutdown behavior.

‎docs/architecture/task-lifecycle-model.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ The known-unsafe witnesses currently compare exact shortest action sequences. Th
6363

6464
`TaskHistoryStore.realConcurrency.spec.ts` complements the abstract interleavings with one synchronized integration smoke check through the real `proper-lockfile` and filesystem rename path; broader VS Code E2E remains reserved for restart and extension-host behavior.
6565

66+
## Task cleanup protocol model
67+
68+
The umbrella command also runs a separate bounded child model for in-memory abort, disposal, and provider-shutdown ordering. It models cleanup settlement and rejection as environment transitions and makes no filesystem, editor Promise, fairness, or timing-liveness claim. See [Task cleanup protocol model check](./task-cleanup-protocol-model.md).
69+
6670
## Invariants
6771

6872
The checker currently enforces:

‎package.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"check-types": "turbo check-types --log-order grouped --output-logs new-only",
1414
"test": "turbo test --log-order grouped --output-logs new-only",
1515
"test:mutation-ci": "node --test scripts/stryker-diff.test.mjs",
16-
"lifecycle:model-check": "tsx scripts/check-task-lifecycle.ts && tsx scripts/check-task-store-concurrency.ts",
16+
"lifecycle:model-check": "tsx scripts/check-task-lifecycle.ts && tsx scripts/check-task-store-concurrency.ts && pnpm cleanup-protocol:model-check",
17+
"cleanup-protocol:model-check": "tsx scripts/check-task-cleanup-protocol.ts",
1718
"test:coverage": "turbo test:coverage --log-order grouped --output-logs new-only",
1819
"format": "turbo format --log-order grouped --output-logs new-only",
1920
"build": "turbo build --log-order grouped --output-logs new-only",

‎packages/types/src/task.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export interface TaskLike {
128128
approveAsk(options?: { text?: string; images?: string[] }): void
129129
denyAsk(options?: { text?: string; images?: string[] }): void
130130
submitUserMessage(text: string, images?: string[], mode?: string, providerProfile?: string): Promise<void>
131-
abortTask(): void
131+
abortTask(): Promise<void>
132132
}
133133

134134
export type TaskEvents = {

0 commit comments

Comments
 (0)