Skip to content

[split-1066] U3 - feat(tools): onParameterParseFailure teardown boundary #1936

Description

@easonLiangWorldedtech

Unit U3 of the PR #1066 split (4/6).

Split plan: #703
PR: #1931
Content source of record: tag pr1066-source = 46d1d218701f0ce2d675b1b315489bacb6b0f77d (easonLiangWorldedtech/Zoo-Code).

Unit contract

BaseTool.onParameterParseFailure() teardown boundary plus the WriteToFileTool override: when the final block fails to parse, execute() never runs, so this boundary finalizes the open partial ask, restores the diff document, releases the per-task state, and reports the earlier streaming failure instead of the incidental parse error.

Why this unit exists on its own

Single provider group (BaseTool boundary + one tool override) + single gate scope.

Boundary

Files and budget

  • src/core/tools/BaseTool.ts +36/-3 — byte-identical to source
  • src/core/tools/WriteToFileTool.ts +22/-0 — content subset of source
  • src/core/tools/__tests__/writeToFileTool.spec.ts +192/-0 — content subset of source
  • budget: 253 a+d / 3 files — UNDER-SOFT
  • mutation gate: 30 changed executable lines (15 BaseTool + 15 WriteToFileTool) — under the 500 cap; valid mutants for the whole PR are 116 / 400, so no directive was added by this unit.

Verification (must pass by once, binary)

  • zdt split verify --contract U3.json --worktree <wt> --head e3c10401f760 — PASS (every changed file is a content subset of the source of record, or an explicitly sanctioned allowNew file).
  • Tests: 243 passed / 5 skipped, exit 0 (narrowest relevant suites: Task.spec.ts, writeToFileTool.spec.ts, writeToFileTool-partial-state-cleanup.spec.ts, removeClineFromStack-delegation.spec.ts, presentAssistantMessage-custom-tool.spec.ts).
  • Changed-line coverage: 20 covered / 0 uncovered — PASS.
  • ESLint --prune-suppressions --max-warnings=0: clean on every touched file; suppression counts unchanged.
  • No .changeset file, no CHANGELOG edit (AGENTS.md).

Deviations recorded

  • None.
  • 1 Stryker directive in the diff (hasPathStabilizedForTask, // Stryker disable next-line ConditionalExpression with a concrete reason: the !== undefined clause is not distinguishable by any test). It belongs to U4's content and travels with it.

Reproduce

git fetch https://github.com/easonLiangWorldedtech/Zoo-Code p1066/u3-parse-failure-boundary
node zdt.mjs split verify --contract U3.json --worktree <wt> --head e3c10401f760
node zdt.mjs split measure --worktree <wt> --base 4b23b6a270aa --head e3c10401f760
pnpm --dir src exec eslint --prune-suppressions --max-warnings=0 <touched file>

Activity

  1. easonLiangWorldedtech commented on Oct 10, 2026

    @easonLiangWorldedtech
    ContributorAuthor

    Pending row: cancellation-aware diff open

    The review row on PR 1931 asks that DiffViewProvider.open() and openDiffEditor() become cancellation-aware. Today a task disposed while open() is in flight leaves the pending open and its deferred-scroll timeout running, so a later partial delta can resurrect a diff view for a task that no longer exists.

    Acceptance:

    • Task disposal cancels or is awaited by the in-flight open, and the deferred scroll timeout is disposed.
    • A test disposes the task mid-open() and asserts no diff view survives and no timeout fires afterwards.
    • Negative control recorded for this unit: the number of tests the mutation kills, so the port to sibling units can be compared.

    This unit goes first; the change then ports in merge order 1928, 1931, 1929, 1930, 1932.

  2. easonLiangWorldedtech commented on Oct 10, 2026

    @easonLiangWorldedtech
    ContributorAuthor

    Defect: the rollback guard silently skips the saveDirectly branch, and reset() forgets instead of cleaning

    Found while checking the coupling with U6 (#1932), at U3 head 5ff7bea73. Both halves are U3 code:
    the guard came in with 5c0f21219 and the swallowing caller with 2a9bfabfb.

    1. A guard keyed on a flag only one path sets. DiffViewProvider.revertChanges() opens with
    if (!this.relPath || !this.isEditing) return, but isEditing = true is assigned only inside
    open(). The PREVENT_FOCUS_DISRUPTION branch never calls open(), so in that branch
    revertChanges() early-returns and nothing rolls back: the directories handed over by
    adoptCreatedDirs() stay on disk and the file saveDirectly() wrote stays written. Worse, the
    caller WriteToFileTool.revertDiffChangesBeforeReset() still returns true, so the teardown is
    reported as complete and reportRevertFailure() never fires. A swallowed failure is worse than a
    reported one. A guard condition must be proven reachable from every entry point; using a flag
    that only one path sets turns the guard off for the other path.

    2. reset() forgets instead of cleaning. reset() sets createdDirs = [] and
    adoptedCreatedDirs = [] without removing anything from disk, so "early return + reset" leaves
    the preflight-created directories on disk permanently: after the reset no code path knows them.

    Acceptance criteria

    1. With experiments: { preventFocusDisruption: true }, a failed or denied saveDirectly write
      removes the directories this call created and unlinks a file it created (modify: content
      restored) - the rollback works on the branch that never opened a diff view.
    2. The rollback return value reflects what actually happened: "nothing to do" and "did the work"
      must not be conflated with "silently skipped", and a skipped rollback must reach
      reportRevertFailure().
    3. reset() may only drop the directory list after the directories are gone, or the caller must
      roll back before it resets - proven by a test that fails when reset() runs first.
    4. Every test added for this runs with the experiment ON as well as OFF: the default branch
      passes today and proves nothing about this defect.

    Ported from the retired fork tracking item 6093538947. Timing: next legitimate push of #1931 (a
    review in flight blocks the push, not this item).

  3. easonLiangWorldedtech commented on Oct 10, 2026

    @easonLiangWorldedtech
    ContributorAuthor

    Follow-up on new-file rollback ownership in unit U3 (pull 1931).

    adoptCreatedDirs closes the rollback gap for the execute() preflight only: the streaming path still relies on DiffViewProvider.open() recording the directories it creates itself, and nothing asserts both entry points. A converging guard that only has one writer silently fails on the other path.

    Acceptance: two tests, one per entry point (the execute() preflight, and open() during a partial stream), each proving that a rolled-back new-file write removes every directory that write created and nothing it did not create.

    Owner: unit U3. Scheduled to ride the next legitimate push of that unit.

  4. easonLiangWorldedtech commented on Oct 10, 2026

    @easonLiangWorldedtech
    ContributorAuthor

    Contingency note for unit U3 (pull 1931).

    A discarded change is kept on disk rather than deleted: .tmp-cr-audit/p1066-u3-cancellation.patch, 99 additions and 11 deletions, which makes open() and openDiffEditor() cancellation-aware. It was reverted only because the checklist row it answered belonged to an older head, not because the change was wrong.

    Acceptance: if a maintainer asks for cancellation-aware diff opening, apply the patch on the current head, re-run the DiffViewProvider spec and its negative controls, and re-measure how many tests each control reddens - do not re-derive it.

    Owner: unit U3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions