Repository navigation
Description
Chain note: U6 (#1932) is being re-split into U6a and U6b
Posted on the unit issue #1937 first, where it belongs; this is the chain-level row update only. Full plan, figures with heads, additivity, density and the labels are in #1937 (comment) post's id will be added by edit? no - stated below): see the plan comment on #1937 posted 2026-11-12.
- Unit list change: U6 splits into U6a (this PR fix(editor): release abandoned diff streams and owned placeholders (split 5/6 of #1066, unit U6a) #1932, branch
p1066/u6-execute-error-path-cleanup, unit issue [split-1066] U6 - fix(write-to-file): run the diff cleanup when handleError rejects #1937, contentDiffViewProvider.ts+ its spec) and U6b (new PR, branchp1066/u6b-tool-stream-teardown, unit issue [split-1066] U6 - fix(write-to-file): run the diff cleanup when handleError rejects #1937 shared with U6a as the same unit's second half, contentWriteToFileTool.ts,BaseTool.ts,ClineProvider.ts+ their tests and the suppressions decrease). - Declared merge order becomes: U3 feat(tools): onParameterParseFailure teardown boundary (split 4/6 of #1066) #1931 -> U4 feat(write-to-file): per-task partial stream state + cleanup primitives (split 2/6 of #1066) #1929 -> U5 U5a of the split of this pull: rollback recovery context and the report of whether a revert ran #1930 -> U6a fix(editor): release abandoned diff streams and owned placeholders (split 5/6 of #1066, unit U6a) #1932 -> U6b -> U7 fix(write-to-file): clean partial state on missing-param and rooignore denial + integrate the #1066 split series (6/6 + FINAL) #1928 (final integration). U7 is unchanged.
- Cross-unit content point named here because it is chain state, not unit scope:
BaseTool.tsblob827011fba0d5at fix(editor): release abandoned diff streams and owned placeholders (split 5/6 of #1066, unit U6a) #1932's head is byte-identical to the blob on U7 fix(write-to-file): clean partial state on missing-param and rooignore denial + integrate the #1066 split series (6/6 + FINAL) #1928's live branch head (94f0925066a4, freshls-remote2026-11-12T01:32:28Z), andClineProvider.tsblobd4db11ff5dfeat fix(editor): release abandoned diff streams and owned placeholders (split 5/6 of #1066, unit U6a) #1932's head exists on no other unit branch (all siblings carryaa4cab38ed43). U6b carries both, so no content leaves the chain when they move out of fix(editor): release abandoned diff streams and owned placeholders (split 5/6 of #1066, unit U6a) #1932. - Gate figures behind the change (my measurements, instrumented counts, not CI): parent
fe447fa7a766450 counted executable lines / 163 selectors / 410 instrumented mutants against the 500 / 400 caps; U6a210b94a981bd207 / 68 / 183; U6b28c086751c16243 / 95 / 227; sums exact. - This note is one REST core. No unit branch was moved by it and nothing was merged, labelled or closed.
Addendum 2026-11-12, U6 re-cut again
U6 was cut a second time. The row above for U6 now covers two units:
| unit | issue | pull | branch | state |
|---|---|---|---|---|
| U6a | #1937 | #1932 | easonLiangWorldedtech:p1066/u6-execute-error-path-cleanup at 3c5036a | pushed, provider-side half only (207 counted executable lines, 68 selectors, 183 instrumented mutants against main tip 53cb5b5) |
| U6b | #1937 | #2434 | easonLiangWorldedtech:p1066/u6b-tool-stream-teardown at 27465f8 | open against main, depends on #1932 (243 lines, 95 selectors, 227 instrumented mutants against 3c5036a) |
Figures are my measurement with the stock selector of scripts/stryker-diff.mjs plus a local Stryker 10 fast dry run on the identical selector list; instrumented mutant counts are counts, not verdicts. Additivity is exact against the previously reported parent: 207 + 243 = 450 lines, 68 + 95 = 163 selectors, 183 + 227 = 410 mutants.
Declared merge order with the re-cut: U3 #1931 -> U4 #1929 -> U5 #1930 -> U6a #1932 -> U6b #2434 -> U7 #1928. While U6a is open, GitHub reports #2434 against main as the cumulative 450 lines and 410 mutants; that is the chain view, not the unit delta. Unit 1 also carries a sanctioned repair of the eight TS2540 read-only isDirty assignments in DiffViewProvider.spec.ts (lines 1414, 1447, 1475, 1500, 1548, 1674, 1745, 1805) via a locally widened structural type, with no assertion and no any; a red compile leg is not what CI reported for #1932 today (see the plan comment for the posture read).
Addendum 2026-11-12, U5 cut into two pulls
U5 was cut, so the U5 row below now covers two pulls with a declared order between them, U5a first and U5b second. The plan is #1935 (comment) and the unit issue carries both pulls in its body.
| unit | issue | pull | branch | state |
|---|---|---|---|---|
| U5a | #1935 | #1930 | easonLiangWorldedtech:p1066/u5-streaming-failure-capture at acd836f | pushed, diff-view half only, merges first, 226 counted executable lines, 55 selectors, 192 instrumented mutants against main tip 53cb5b5 |
| U5b | #1935 | #2435 | easonLiangWorldedtech:p1066/u5b-tool-level-stream-state at c3dbed4 | open against main, depends on #1930 and merges second, 293 lines, 109 selectors, 268 instrumented mutants against acd836f |
Figures are my measurement with the stock selector of scripts/stryker-diff.mjs, blob 7dfe0006d2d5, which caps changed executable lines at 500 and instrumented mutants at 400, plus a local Stryker 10 fast dry run on the identical selector list; instrumented mutant counts are counts, not verdicts, the mutation leg is unmeasured locally, and CI at the pushed head is the only proof of the gate. Additivity is exact against the pull whole measured before the cut on main tip 53cb5b5: 226 + 293 = 519 lines, 55 + 109 = 164 selectors, 192 + 268 = 460 mutants. The 519 lines is 19 over the 500 cap and the shipped gate exits 1 on the line cap at that tree, which is what forced this cut; it is a line-cap trigger, not the mutant-cap trigger that split 1913.
Declared merge order with the U5 cut: U3 #1931 -> U4 #1929 -> U5a #1930 -> U5b #2435 -> U6a #1932 -> U6b #2434 -> U7 #1928. While U5a is open, GitHub reports pull 2435 against main as the cumulative 13 files, 4930 insertions and 130 deletions; that is the chain view, not the unit delta, which is 10 files, 2853 insertions and 90 deletions against pull 1930. This addendum moved no unit branch, merged nothing, labelled nothing, closed nothing and resolved nothing.
Activity
easonLiangWorldedtech commentedon Oct 10, 2026
Rule: a provider-state await belongs inside the cleanup-owned boundary
Two units of the #1066 chain grew the same defect from opposite directions, which is why this is worth a line here rather than two commit messages:
tryand then putdiffViewProvider.open()/update()after it, atfb21709cdlines 533-549. A rejection there reachedBaseTool.handle()with the per-task entry and itsTaskAbortedlistener still attached. Fixed in61dd05a2cby moving the diff-view work inside the boundary and giving the catch two windows (setup failure releases; diff-view failure keeps the entry and marks it, because releasing at the delta is what lets the next delta replay the operation that just failed).open()/update()are inside a localtry(443993e57), butawait provider?.getState()sits outside it at lines 510-511, so the same leak survives on the other side of the same function.The rule: any await that happens after per-task state has been registered must be inside the boundary that releases that state - including the awaits that look like read-only setup (
providerRef.deref(),getState(), a settings lookup, anisEnabled()read).getState()is not a pure read: it is an await into the extension host and it can reject or be superseded by a task switch.Same-shape sites to check beyond U4/U6 (measured at the heads named above,
git grepoversrc/core/tools):Cross-references: U4 fix
61dd05a2c(this rule's first half), U6 follow-up commit (second half, will cite this comment), U3 #19311b09bab32already carries the liveness-check shape that U6 ports for its Lifecycle row.easonLiangWorldedtech commentedon Oct 10, 2026
Chain-level row: Persistence Integrity (a rollback path can persist unapproved streamed content)
This row is red at the same time on #1916 / #1921 / #1929 / #1930, so it is one chain-level defect rather than four per-PR defects. Recording the ownership split here before anyone ports it.
DiffViewProvider.restorePreStreamBuffer()/saveBufferClean()are owned by U5a of the split of this pull: rollback recovery context and the report of whether a revert ran #1930 (p1066/u5-streaming-failure-capture).git log --all -S restorePreStreamBuffer -- src/integrations/editor/DiffViewProvider.tsputs the first definition in6cae369d9("fix(diff-view): treat a refused discard as a rollback failure"), andgit branch -a --contains 6cae369d9shows it only on that branch.applyEdit() === falsemeans the rollback failed: do notsave()the streamed document, restore or close it without saving, and report the failure. That is the rule fix(write-to-file): clean partial state on missing-param and rooignore denial + integrate the #1066 split series (6/6 + FINAL) #1928 already broadcast forDiffViewProvider.discardUnapprovedStream()(itseditorFailurechannel).Ownership and semantic baseline are two different facts and must be labelled separately. The earliest definer owns the primitive; the semantic baseline may be a rule another unit already broadcast. Marking only the owner makes every unit invent its own variant while porting, which is how one row ends up red on four PRs.
Shape shipped on #1930: the contract lives in the callee.
restorePreStreamBuffer()throws whenapplyEdit()returnsfalse, so thesaveBufferClean()call that follows it cannot run — nothing saveable survives — and the failure reachesrevertChanges()→WriteToFileTool.revertDiffChangesBeforeReset(), which already returns it asrollbackErrorfor the caller to report.discardFileTab()is byte-for-byte unchanged, because #1932 depends on its current shape (restore + save hoisted above the tab loop). A refused restore is asserted to reach the user as a failure, not as a successful rollback.easonLiangWorldedtech commentedon Oct 10, 2026
Port source of record for the write_to_file rollback shape
eb245494aonp1066/u5-streaming-failure-captureis the source of record for the shared rollback shape:revertChanges()requires bothrelPathandisEditing;reset()clearsrelPath(andnewContent);discardFileTab()restores the buffer to its pre-stream content and saves it clean before closing, because the second argument oftabGroups.close()ispreserveFocusand not a force-discard flag; a close the editor refuses now fails the rollback instead of deleting the file underneath an open tab.WriteToFileToolreverts the diff view only while an edit is in progress.215e32d3ecarries the matching eslint suppression prune.5c0f21219onp1066/u3-parse-failure-boundaryports that half verbatim and adds theexecute()cleanup hazard report, matchingonParameterParseFailure()andcleanupFailedPartialStream().Chain-level requirement: port both verbatim to the remaining units in merge order, and record per unit how many tests each negative control kills. The counts differ per unit by design, because the same guard covers several call sites and each unit reaches a different subset of them.
easonLiangWorldedtech commentedon Oct 10, 2026
Chain rule: a suppression reduction must ship in the same commit
The compile job runs the repository lint, and the strict run compares the counts in
src/eslint-suppressions.jsonin both directions. A change that removes a suppressed violation -replacing an
as anycast with bracket notation, for example - makes the stored count too high, andthe run exits 2 with "There are suppressions left that do not occur anymore" even though the file
itself reports no violations.
This bit PR 1930: the first push reported a clean per-file lint and still failed compile, because a
per-file run cannot see a count drift.
Acceptance for every unit in this chain:
eslint . --ext=ts --max-warnings=0. A per-file run is not sufficient.--prune-suppressions, rewrite the JSON with tab indentation, andconfirm the diff touches only the affected count lines.
cleanup commit.
together, and CI builds the merge commit rather than the branch tip.
easonLiangWorldedtech commentedon Oct 10, 2026
Two chain-level entries for this chain, registered from the local queue.
Series convention, not a defect: the format check script and the root prettier ignore file arrive through the PR merge ref, not through a unit branch's own base, so every unit needs its own pure-formatting commit once it merges current org main. Register it once as a convention, not as one issue per unit. Acceptance: in a worktree checked out with core.autocrlf=false, the format check reports zero offenders at the current merge ref, and only files inside that PR's diff are touched. Shared files converge because the formatter output is deterministic, so ordering between units does not matter.
Fixing one call site does not fix a defect class. The Persistence Integrity row stayed red after one call site was fixed, because the existing-file branch of the same method had its own copy of the unchecked restore-and-save. When a row names a class, enumerate the other call sites and register them - changed only if the row covers them.
Unit-scope debt for U5 (five unchecked result sites and one await-boundary follow-up) is on issue 1935.
Addendum to entry 1, measured on the current base (2026-10-10, unit U7): org main deleted the root prettier ignore file between 09e7326 and d1f5845 - the same batch that added the format check script - so the intent is that the whole tree is formatted, not a subset. Consequence: the compile job (pnpm format:check, i.e. prettier --check over the tree) now reaches 37 files that were previously ignored, and three of them were unit U7's own spec files, which turned that unit's compile job red. The unit-level fallout is already fixed in pull 1928 commit b2b3b0d.
Two things the chain should settle once, so no later unit rediscovers them:
No action required by any unit that has not merged onto the new base yet.
easonLiangWorldedtech commentedon Oct 10, 2026
Chain-level tooling note for the 1066 split units: org main is not tsc-clean on its own. Merging it brings 12 type errors (webviewMessageHandler.readOriginalContent.spec.ts accounts for 8, plus ClineProvider.spec.ts and WebviewFocusTracker), which moves the merged-tree baseline of every unit from 62 to 74.
Consequence for how we report: an absolute tsc count is meaningless when upstream carries a dirty baseline. The check has to be an A/B on the same tree - run tsc at HEAD and at HEAD~1, or with and without the change, and require the error sets to be identical by name; this is the same trick as proving a local red with a stash A/B.
In commit messages write "A/B equivalent, error sets identical by name", not "tsc 0 errors": when the baseline is not zero a bare 0 reads as a claim about the baseline.
Acceptance: either org main reaches zero with the temporary tsconfig paths mapping for the types package, or every unit records its own merged-tree A/B result instead of an absolute number.
easonLiangWorldedtech commentedon Oct 10, 2026
Chain-level entry from unit U6 (pull 1932): revertChanges() has no "is an edit session active" gate, so a caller that holds no session can still reach the new-file unlink branch.
Unit U6 removed the stale-path trigger instead of adding the gate: reset() now clears relPath and newContent (commit 694bed4), so the branch is no longer reachable from the path that triggered it here. The shape question is still chain-level because unit U7 carries the gate in a different form (5c0f212), and the two units now differ deliberately.
Acceptance if the chain wants one shape: the gate exists at the public entry point in every unit that carries the unlink branch, plus a test that revertChanges() with no session is a no-op. A guard that sits only on an internal caller is not a guard.
Recorded so the next unit does not re-derive the difference between the two units; no action required by U6.
easonLiangWorldedtech commentedon Oct 10, 2026
One fail-closed rollback path for the write-to-file stream lifecycle
Defect: the chain currently has four separate rollback exits that each decide on their own whether to reset per-task stream state - the streaming-failure rollback, the approval-denial revert, the parse-failure release, and the task-abort teardown. Each of them can call reset while a dirty, never-approved buffer is still on disk or still open in a diff view, because closing diff views skips dirty tabs. When the restore or close itself fails, the state is dropped anyway, so a later retry can publish content the user never approved, and the per-task entry plus its abort listener can be retained.
Acceptance, not bound to a commit:
Negative-control shape: one mutant per production call site, each reddening only the tests naming that behaviour; run each mutant against the pre-fix spec to show the branch was previously uncovered; if a mutant reddens far more tests than the target, assume the mutant is wrong first; restore mutants byte for byte and verify with a hash.
Related but separate: the parse-failure release hook is never reached on the production malformed-completion path, because the caller emits its own tool result and returns before delegating to the tool handler. That leaves the per-task entry and its abort listener retained and suppresses later previews. Its fix belongs to the unit that introduced the hook, and it must preserve the one-result-per-call behaviour and the existing report ordering.
easonLiangWorldedtech commentedon Oct 11, 2026
A discarded save result can report a successful save that never persisted
Defect: the approved-save path awaits the document save and drops the returned boolean. A save that reports failure therefore continues into the success bookkeeping, the placeholder is released, and the caller is told the approved content is on disk when it is not. The statement is inherited from upstream code that predates this chain, so it is not a regression introduced by the split, but every unit branch carries a copy and the file is inside each unit's diff.
Acceptance, not bound to a commit:
Negative-control shape: reverting to a bare await reddens exactly the save-false tests; moving the throw after the placeholder release reddens the ordering assertion; a mutant that reddens far more tests than the target is a broken mutant. Restore byte for byte and verify with a hash.
easonLiangWorldedtech commentedon Nov 12, 2026
Chain note: U6 (#1932) is being re-split into U6a and U6b
Posted on the unit issue #1937 first, where it belongs; this is the chain-level row update only. Full plan, figures with heads, additivity, density and the labels are in #1937 (comment) post's id will be added by edit? no - stated below): see the plan comment on #1937 posted 2026-11-12.
p1066/u6-execute-error-path-cleanup, unit issue [split-1066] U6 - fix(write-to-file): run the diff cleanup when handleError rejects #1937, contentDiffViewProvider.ts+ its spec) and U6b (new PR, branchp1066/u6b-tool-stream-teardown, unit issue [split-1066] U6 - fix(write-to-file): run the diff cleanup when handleError rejects #1937 shared with U6a as the same unit's second half, contentWriteToFileTool.ts,BaseTool.ts,ClineProvider.ts+ their tests and the suppressions decrease).BaseTool.tsblob827011fba0d5at fix(editor): release abandoned diff streams and owned placeholders (split 5/6 of #1066, unit U6a) #1932's head is byte-identical to the blob on U7 fix(write-to-file): clean partial state on missing-param and rooignore denial + integrate the #1066 split series (6/6 + FINAL) #1928's live branch head (94f0925066a4, freshls-remote2026-11-12T01:32:28Z), andClineProvider.tsblobd4db11ff5dfeat fix(editor): release abandoned diff streams and owned placeholders (split 5/6 of #1066, unit U6a) #1932's head exists on no other unit branch (all siblings carryaa4cab38ed43). U6b carries both, so no content leaves the chain when they move out of fix(editor): release abandoned diff streams and owned placeholders (split 5/6 of #1066, unit U6a) #1932.fe447fa7a766450 counted executable lines / 163 selectors / 410 instrumented mutants against the 500 / 400 caps; U6a210b94a981bd207 / 68 / 183; U6b28c086751c16243 / 95 / 227; sums exact.easonLiangWorldedtech commentedon Nov 12, 2026
Chain-level row: two unit bodies declare different merge sequences for this chain, and no lane is resolving it
Filed by lane u9-1917 at lead's ruling that where two bodies declare different sequences the disagreement goes to the tracking issue as a human-visible item rather than being settled by a lead picking one, because a merge sequence is a maintainer-facing promise and not a measurement. Nothing below picks a winner.
Both texts quoted verbatim, with the line number inside each published body.
#1931 (unit U3), body line 31:
#1932 (unit U6a), body line 32, in the price-table row for unit U6b:
#1932 (unit U6a), body line 52, in the row-ownership paragraph:
Four differences, named rather than smoothed over:
What the transport does and does not settle. Measured 2026-11-12 with git reads only, 0 API cores, each head compared against org main tip
efc22ed9f6f75975106f170cf29cc9bcc0b86682:2dd4c696bf38(U5a, U5a of the split of this pull: rollback recovery context and the report of whether a revert ran #1930) is an ancestor of4baf005673f4(U5b, U5b of the split of pull 1930: per-task stream state, captured once and released once #2435);git merge-basewith main tip returnsefc22ed9f6f7for U47cf37b717791adeae8af48c8df62aa6b5f1a8f49, U5a2dd4c696bf38, U5b4baf005673f4, U34ca8b97015090c48d00e7db0d8c8f308cb86443e, U6a5bc7709dfe515d35dd77fa2741a08bf8a6ce4250, U6b725b8a3eb42fe3bd8c71e23b56a4ac7729ce5450and U79ebacb5aa961, whileb8e8a48dec1a(U12, fix(task): stage-independent saveClineMessages + finalize open partial tool ask (split 1/6 of #1066) #1927) bases on72143527fd33306e5541116093c2cbf803cce9e0, an older main;d52c1614f6e4780ce94b54c0357278b842cdebc1, which is the head U6a carried until 2026-11-12; U6a now carries5bc7709dfe51, so as transported U6b trails U6a by one commit and only the promised sequence says which way to repair that;src/integrations/editor/DiffViewProvider.tsis changed by U4, U5a, U5b, U3, U6a, U6b and U7, andsrc/core/tools/WriteToFileTool.tsby U4, U5b, U3, U6b and U7, so which branch lands first decides whose lines a mutation row sits on, and therefore which unit owes the mutant and what the cumulative line count of a later merge is.What is not being changed because of this item. Mutant-row attributions in #1932 and #1931 stay as published: ownership of a row follows the unit that adds the line and not the sequence the branches merge in, which is a ruling of this campaign. Lanes will keep pricing each unit against its own merge base and quoting unit and cumulative shapes apart, so a maintainer's sequence decision does not silently move a published number.
Coordinates named: org main
efc22ed9f6f7, fork main53cb5b5e7c80, U6a5bc7709dfe51onp1066/u6-execute-error-path-cleanup, U6b725b8a3eb42fonp1066/u6b-tool-stream-teardown, U34ca8b9701509onp1066/u3-parse-failure-boundary, U47cf37b717791, U5a2dd4c696bf38, U5b4baf005673f4, U79ebacb5aa961, U12b8e8a48dec1a. All read from the transport with git, not from any body.