Repository navigation
[split-1066] U6 - fix(write-to-file): run the diff cleanup when handleError rejects #1937
Description
Activity
easonLiangWorldedtech commented
on Oct 10, 2026 ContributorAuthorMore actionsScope re-cut for U6 (#1932): which extra files are not U6's, and which one is
Trigger: CodeRabbit Out of Scope Changes check (Error) on #1932 at
443993e57. Row, verbatim:Issue
#1937defines U6 as the singleexecute()error path and lists two files:WriteToFileTool.tsandwriteToFileTool.spec.ts. The PR also changesBaseTool.ts,ClineProvider.ts,DiffViewProvider.ts, and their tests.The row's own second remedy is the one we are taking:
or update issue #1937 to explicitly define and assign the additional coding scope before merge. This comment is that definition; #1937 now carries the same text in its body. It is per file, because the three files have three different answers, and the reason for each is the owning commit, not the fact that the code already exists.1.
src/core/tools/BaseTool.ts(+36/-3) - NOT U6. Owned bye3c10401f feat(tools): onParameterParseFailure teardown boundary, which is U3 (#1931).git log -m 036245c5e..HEAD -- src/core/tools/BaseTool.tsreturns that commit and the org-main merge and nothing else. It appears in #1932's file list only because the split branches live on the fork, so every unit PR in the chain is opened againstmainand GitHub shows the cumulative diff through this unit - which is stated in the PR body under Chain position. Merge order is fixed at U12 (#1927) -> U4 (#1929) -> U5 -> U3 (#1931) -> U6 (#1932) -> U7/FINAL (#1928); by the time U6 merges, U3 has merged and this hunk is no longer in U6's diff.2.
src/core/webview/ClineProvider.ts(+6/-0) - NOT U6. Owned byd172c95ae feat(write-to-file): per-task partial stream state + cleanup primitives, which is U4 (#1929): thewriteToFileTool.clearTaskState(task)call beforetask.dispose(), i.e. the history-task disposal the row names. Same cumulative-diff explanation as above.3.
src/integrations/editor/DiffViewProvider.ts(+121/-10) andsrc/integrations/editor/__tests__/DiffViewProvider.spec.ts(+213/-1) - U6's own, and it cannot live outside U6. Owned by443993e57, a U6 commit. The reason is not that it was written here first:- U6's contract is
a finally around handleError so the diff cleanup runs even when handleError rejects. The cleanup thatfinallyruns isDiffViewProvider.revertChanges(). Before this hunk, for a new file whoseopenDiffEditor()had rejected there was noactiveDiffEditorandrevertChanges()returned early, leaving the empty placeholder and the directoriesopen()had created on disk. The invariant U6 asserts was then only vacuously satisfied at U6's own head: the finally ran, called a function, and that function did nothing. A test ofthe cleanup runsthat cannot distinguishcalledfromdid somethingdoes not constrain the code U6 is about. - The same hunk stops
revertChanges()from saving a dirty buffer on the rollback path. Saving unapproved streamed content is the state theexecute()error path exists to roll back, so the flagwriteApprovedand the buffer behaviour are the same invariant seen from two sides; splitting them across units would let one side move without the other. - The 213 added spec lines are the tests for exactly that behaviour, so they are in scope by the same argument as the code they pin.
Assigned to U6 explicitly:
src/integrations/editor/DiffViewProvider.tsandsrc/integrations/editor/__tests__/DiffViewProvider.spec.ts, for the diff-cleanup target of theexecute()error path only (rollback of an unapproved stream: placeholder, created directories, and the buffer that must not be saved). Anything in that file beyond the rollback target stays out of U6.Head this declaration is written against:
443993e57d65b75c8b1a3a2121af7e495104d538. The follow-up commit that fixes the two remaining rows on #1932 (Persistence Integrity: route the rollback throughdiscardUnapprovedStream()as defined by the earliest unit that defines it, U7/#1928f8f7ce19a; Lifecycle: the two-window + liveness shape from U4/#192961dd05a2c) stays inside the file pair declared above.- U6's contract is
- added a commit that references this issue
on Oct 10, 2026 easonLiangWorldedtech commented
on Oct 10, 2026 ContributorAuthorMore actionsMigrated from #41 comment 6093028889 - this note belongs to the split-1066 chain (chain map: #1989), not to that unrelated closed bug (fork-issue numbering trap).
Scope re-cut for U6 (#1932): which extra files are not U6's, and which one is
Trigger: CodeRabbit Out of Scope Changes check (Error) on #1932 at
443993e57. Row, verbatim:Issue
#1937defines U6 as the singleexecute()error path and lists two files:WriteToFileTool.tsandwriteToFileTool.spec.ts. The PR also changesBaseTool.ts,ClineProvider.ts,DiffViewProvider.ts, and their tests.The row's own second remedy is the one we are taking:
or update issue #1937 to explicitly define and assign the additional coding scope before merge. This comment is that definition; #1937 now carries the same text in its body. It is per file, because the three files have three different answers, and the reason for each is the owning commit, not the fact that the code already exists.1.
src/core/tools/BaseTool.ts(+36/-3) - NOT U6. Owned bye3c10401f feat(tools): onParameterParseFailure teardown boundary, which is U3 (#1931).git log -m 036245c5e..HEAD -- src/core/tools/BaseTool.tsreturns that commit and the org-main merge and nothing else. It appears in #1932's file list only because the split branches live on the fork, so every unit PR in the chain is opened againstmainand GitHub shows the cumulative diff through this unit - which is stated in the PR body under Chain position. Merge order is fixed at U12 (#1927) -> U4 (#1929) -> U5 -> U3 (#1931) -> U6 (#1932) -> U7/FINAL (#1928); by the time U6 merges, U3 has merged and this hunk is no longer in U6's diff.2.
src/core/webview/ClineProvider.ts(+6/-0) - NOT U6. Owned byd172c95ae feat(write-to-file): per-task partial stream state + cleanup primitives, which is U4 (#1929): thewriteToFileTool.clearTaskState(task)call beforetask.dispose(), i.e. the history-task disposal the row names. Same cumulative-diff explanation as above.3.
src/integrations/editor/DiffViewProvider.ts(+121/-10) andsrc/integrations/editor/__tests__/DiffViewProvider.spec.ts(+213/-1) - U6's own, and it cannot live outside U6. Owned by443993e57, a U6 commit. The reason is not that it was written here first:- U6's contract is
a finally around handleError so the diff cleanup runs even when handleError rejects. The cleanup thatfinallyruns isDiffViewProvider.revertChanges(). Before this hunk, for a new file whoseopenDiffEditor()had rejected there was noactiveDiffEditorandrevertChanges()returned early, leaving the empty placeholder and the directoriesopen()had created on disk. The invariant U6 asserts was then only vacuously satisfied at U6's own head: the finally ran, called a function, and that function did nothing. A test ofthe cleanup runsthat cannot distinguishcalledfromdid somethingdoes not constrain the code U6 is about. - The same hunk stops
revertChanges()from saving a dirty buffer on the rollback path. Saving unapproved streamed content is the state theexecute()error path exists to roll back, so the flagwriteApprovedand the buffer behaviour are the same invariant seen from two sides; splitting them across units would let one side move without the other. - The 213 added spec lines are the tests for exactly that behaviour, so they are in scope by the same argument as the code they pin.
Assigned to U6 explicitly:
src/integrations/editor/DiffViewProvider.tsandsrc/integrations/editor/__tests__/DiffViewProvider.spec.ts, for the diff-cleanup target of theexecute()error path only (rollback of an unapproved stream: placeholder, created directories, and the buffer that must not be saved). Anything in that file beyond the rollback target stays out of U6.Head this declaration is written against:
443993e57d65b75c8b1a3a2121af7e495104d538. The follow-up commit that fixes the two remaining rows on #1932 (Persistence Integrity: route the rollback throughdiscardUnapprovedStream()as defined by the earliest unit that defines it, U7/#1928f8f7ce19a; Lifecycle: the two-window + liveness shape from U4/#192961dd05a2c) stays inside the file pair declared above.- U6's contract is
easonLiangWorldedtech commented
on Oct 10, 2026 ContributorAuthorMore actionsMigrated from #41 comment 6094009717 - this note belongs to the split-1066 chain (chain map: #1989), not to that unrelated closed bug (fork-issue numbering trap).
Port sources recorded for #1932 (p1066/u6-execute-error-path-cleanup) - the unit's rollback fixes are ports, and the chain has to converge on one shape of each primitive:hunk ported from why discardFileTab()restore-then-save beforeclose(tab),saveBufferClean()#1931 5c0f21219same primitive, one shape; close()'s 2nd arg ispreserveFocus, so the forced close never discarded a dirty tabsaveChanges(onCommit)commit point +writeCommittedstand-down#1931 2a9bfabfbthe durable-write fact is what the rollback must obey, not the approval fact placeholderPathreleased after the approved save#1929 8084eab2e#1929's Persistence Integrity fix; #1932 carries the same saveChanges()Deliberate deviation: the Critical thread also asks to gate
revertChanges()on an active edit session. Not taken in #1932 - that guard is #1931's shape of the same method (already filed as a follow-up for #1931's next push). #1932 takes the other half (reset()clearsrelPath/newContent), which removes the stale-path trigger the thread's reproductions depend on, and its tests record the current guard rather than the desired one.Correction to the Major rooignore thread: the two missing-parameter early returns did already call
reset(); what none of the three branches did was discard the content an earlier streaming delta had put in the diff view. All three now run the shared discard-then-reset teardown.easonLiangWorldedtech commented
on Oct 10, 2026 ContributorAuthorMore actionsPort note: bring the commit-point semantics with the
DiffViewProviderhalfThe
DiffViewProvidercontent U6 ports from5c0f21219(restore buffers before closing) sits in
the same method as the commit-point signal added in U32a9bfabfb:
saveChanges(diagnosticsEnabled, writeDelayMs, onCommit?)firesonCommitimmediately after the
document write (including the clean-document case, where disk already matches), and
WriteToFileToolkeeps awriteCommittedflag so its catch path does not roll back a write that
already landed.Why it matters here: U6 owns the
execute()catch cleanup. If it rolls the diff view back
without checking the commit point, it undoes a write the user approved. Two further facts about
the catch path, both verified at U3 head5ff7bea73:revertChanges()early-returns when!isEditing, andisEditingis set only byopen(), so
on thesaveDirectly(prevent-focus-disruption) branch the cleanup is a no-op while the
caller still reads "reverted". A test that runs only the default branch passes and misses it.reset()clearscreatedDirs/adoptedCreatedDirswithout touching disk, so anything not
rolled back before the reset is unreachable.
Port source (per-hunk ownership = the earliest commit defining the hunk): U3 / #1931 commits
5c0f21219and2a9bfabfb. CarryonCommitandwriteCommittedin the same positions, or the
two branches will disagree when they merge.Ported from the retired fork tracking item 6093538947 / the U3 note above.
Unit U6 of the PR #1066 split (5/6).
Split plan: #703
PR: #1932
Content source of record: tag
pr1066-source=46d1d218701f0ce2d675b1b315489bacb6b0f77d(easonLiangWorldedtech/Zoo-Code).Unit contract
The
execute()cleanup invariant: afinallyaroundhandleErrorso the diff cleanup runs even whenhandleErrorrejects, thewriteApprovedflag, and the consecutive-mistake-counter order.Why this unit exists on its own
Single provider group + single gate scope (the execute() error path).
Boundary
e3c10401f760(previous unit head, U3)9b93a6f882a4Files and budget
src/core/tools/WriteToFileTool.ts+45/-11 — content subset of sourcesrc/core/tools/__tests__/writeToFileTool.spec.ts+379/-4 — content subset of sourceVerification (must pass by once, binary)
zdt split verify --contract U6.json --worktree <wt> --head 9b93a6f882a4— PASS (every changed file is a content subset of the source of record, or an explicitly sanctionedallowNewfile).Task.spec.ts,writeToFileTool.spec.ts,writeToFileTool-partial-state-cleanup.spec.ts,removeClineFromStack-delegation.spec.ts,presentAssistantMessage-custom-tool.spec.ts).--prune-suppressions --max-warnings=0: clean on every touched file; suppression counts unchanged..changesetfile, no CHANGELOG edit (AGENTS.md).Deviations recorded
reports a filesystem error only once across the streaming and execute phases, originally attributed to U5 (see U5).Reproduce
Scope re-cut (assigned coding scope)
Additional files defined and assigned to this unit (CodeRabbit Out of Scope Changes on #1932 at
443993e57; full reasoning in #41 (comment)):src/integrations/editor/DiffViewProvider.tsandsrc/integrations/editor/__tests__/DiffViewProvider.spec.ts- the diff-cleanup target that this unit'sfinallyinvokes: rollback of an unapproved stream (placeholder, created directories, and the buffer that must not be saved). Without it the invariant this unit asserts is only vacuously satisfied at this unit's head, becauserevertChanges()returned early whenopenDiffEditor()had rejected.BaseTool.tsbelongs to U3 (feat(tools): onParameterParseFailure teardown boundary (split 4/6 of #1066) #1931,e3c10401f) andClineProvider.tsto U4 (feat(write-to-file): per-task partial stream state + cleanup primitives (split 2/6 of #1066) #1929,d172c95ae); both show in fix(write-to-file): run diff cleanup when handleError rejects (split 5/6 of #1066) #1932's file list because the chain is opened againstmainand the diff is cumulative through this unit.