Repository navigation
[split-1066] U7 - fix(write-to-file): clean partial state on missing-param and rooignore denial #1938
Description
Activity
easonLiangWorldedtech commented
on Oct 10, 2026 ContributorAuthorMore actionsMigrated from #41 comment 6093269719 - this note belongs to the split-1066 chain (chain map: #1989), not to that unrelated closed bug (fork-issue numbering trap).
Follow-up:
closeAllDiffViews()must run after the buffer restore insidediscardUnapprovedStream()Found while fixing the Persistence Integrity row on #1932 (shipped in
19e85126d, where the primitive is ported). It is not a regression - the primitive is new in U7/#1928, so nothing onmainbehaves differently - which is why it is filed here instead of being pushed under a review that is already in flight.The defect. In the primitive as defined by U7/#1928
f8f7ce19a(and therefore in U4/#192961dd05a2c), the editor work runs in this order:disposeActiveEditorListener(),cancelDeferredScroll()await this.closeAllDiffViews()- if the buffer is dirty: empty it (create) or restore the pre-stream content (modify), then save the emptied placeholder
closeFileTab(absolutePath), then unlink the placeholder and remove the created directories
A
vscode.difftab is dirty exactly while its modified side holds the streamed content, andcloseAllDiffViews()deliberately skips dirty tabs (closing one would prompt to save). So step 2 skips the only tab that matters, step 3 makes the buffer clean, and step 4 matchesTabInputTexttabs - which a diff tab is not (openDiffEditor()creates aTabInputTextDiff). Net effect: the discard unlinks the file underneath a diff tab that is still open, now clean and empty.Measured, not argued. The regression test on #1932 (
closes the dirty vscode.diff tab that only TabInputTextDiff matches, before the unlink) stubs nothing of the provider's own tab handling and models the tab'sisDirtyas a getter over the document's dirty flag. Against the U7 order it fails withexpected -1 to be greater than -1oncallOrder.indexOf("closeTab")- the tab is never closed. Against the corrected order it passes, and the negative control (moving the call back before the restore) turns exactly 3 tests red, including that one.The fix is one line: move
await this.closeAllDiffViews()from before the dirty-buffer block to just beforecloseFileTab(absolutePath). The buffer is emptied first, so the tab is clean when it is closed and no save prompt is possible - which is the invariantcloseAllDiffViews()'s dirty check exists to protect.Where it has to land. U7/#1928
f8f7ce19a(the unit that defines the primitive) and U4/#192961dd05a2c(which ports it). Each is a one-line move plus twoexpect(callOrder)assertions that currently expectcloseDiffViewsfirst:expect(callOrder).toEqual(["closeDiffViews", "applyEdit", "save", "closeFileTab"])→["applyEdit", "save", "closeDiffViews", "closeFileTab"]expect(callOrder).toEqual(["closeDiffViews", "applyEdit", "closeFileTab"])→["applyEdit", "closeDiffViews", "closeFileTab"]
Landing timing (lead's call): both units have a review request in flight (
f8f7ce19aat 02:22,61dd05a2cat 02:47), so pushing now would void two reviews for a non-regression. Carry this with the next push either unit makes for any other reason; if both go green without it, fix it in one follow-up PR after the chain merges. U6/#1932 already carries the corrected order, so the chain converges either way.easonLiangWorldedtech commented
on Oct 10, 2026 ContributorAuthorMore actionsHead is missing the rollback batch
The head of PR 1928 does not yet carry the cleanup and rollback fixes that landed on the sibling units:
eb245494a(branchp1066/u5-streaming-failure-capture):revertChanges()requires bothrelPathandisEditing,reset()clearsrelPath,discardFileTab()restores the buffer and saves it clean before closing instead of passingtruetotabGroups.close()as a force-discard flag, and a refused close now fails the rollback.5c0f21219(branchp1066/u3-parse-failure-boundary): the same shape plus theexecute()cleanup hazard report.
Without it this unit's diff is incomplete and the merge will conflict. Bring the batch over in the same push as the cancellation change, and re-run this unit's specs, type check, and negative controls afterwards.
easonLiangWorldedtech commented
on Oct 10, 2026 ContributorAuthorMore actionsFollow-up for the second half of the Persistence Integrity pre-merge checklist row on pull 1928 (unit U7, early-return and denial cleanup).
Shipped with the fix: a missing provider is treated as a failed metadata stage and pendingTaskMetadataRepair is retained instead of being cleared.
Still open: in-flight saveClineMessages and persistTaskMetadata calls are not tracked, so disposeOnce can conclude there is nothing pending while a stage is still running.
Acceptance: disposeOnce awaits every in-flight metadata or message write, or the writes are serialised behind one promise chain, and a test proves that a write started just before disposal still lands and still sets pendingTaskMetadataRepair when it fails.
Owner: unit U7. Recorded locally first, back-filed here so the argued half of the row has a scoped target.
easonLiangWorldedtech commented
on Oct 10, 2026 ContributorAuthorMore actionsRecord, no action required: persistTaskMetadata() carried an unreachable trailing return true after an unconditional throw/return path. Removing it (commit f873a3d) moves no test - all 180 tests in the affected file stay green with the statement restored - which is the same fact the surviving BooleanLiteral mutant at that line reports.
Registered so the mutation advisory for that line is not re-derived as an untested behaviour change: the statement was dead code, and no test can distinguish its removal.
Unit U7 of the PR #1066 split (6/6 + FINAL).
Split plan: #703
PR: #1928
Content source of record: tag
pr1066-source=46d1d218701f0ce2d675b1b315489bacb6b0f77d(easonLiangWorldedtech/Zoo-Code).Unit contract
The early-return and rooignore-denial branches clear the per-task partial state and finalize the open ask, so a denied or truncated write does not leave stale state or a dirty diff document.
Why this unit exists on its own
Single provider group + single gate scope. This is the last unit, so its unit PR and the FINAL integration PR are the same PR (#1928) — the sole merge target of the series.
Boundary
9b93a6f882a4(previous unit head, U6)646c7873926fFiles and budget
src/core/tools/WriteToFileTool.ts+27/-2 — byte-identical to sourcesrc/core/tools/__tests__/writeToFileTool.spec.ts+210/-2 — byte-identical to sourceVerification (must pass by once, binary)
zdt split verify --contract U7.json --worktree <wt> --head 646c7873926f— 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
Reproduce