bug(prep): on Windows, rt prep --dry-run refuses when another program holds an ignored file open — the scratch copy reads every ignored file #1543

Closed
opened 2026-10-04 11:07:48 +02:00 by claude · 3 comments
Member

On Windows, rt prep --dry-run refuses in a checkout where a running program holds an ignored file open without read sharing. A Unity editor with the project open does that. The scratch copy opens every file under the checkout, ignored ones included, and a sharing violation on any of them ends the preview. The preview needs none of those files.

Measured (2026-10-04)

Setup.

  • rt v0.65.0, built natively from the tag: Go 1.27.1 windows/amd64, git 2.55.0.windows.3, Git Bash.
  • The adopter frankenbit/storey at fd3e3ec (tag-is-version, six fragments in changelog.d).
  • Its Unity project game/ was open in the Unity editor.
Run at the root of Exit Wall time Result
the operator's checkout (editor open) 1 38.5 s refused, see below
a plain git clone of the same commit 0 5.3 s full preview, 117 lines
Error: --dry-run needs a scratch tree so it cannot touch your checkout: scratch tree: open C:\dev\storey\game\Temp\FSTimeGet-35eb1640347db7a448dd54712f366ba7: open C:\dev\storey\game\Temp\FSTimeGet-35eb1640347db7a448dd54712f366ba7: The process cannot access the file because it is being used by another process.
  • The checkout and %TEMP% were clean afterwards, so the refusal itself is safe. It fails closed as #708 intends.
  • game/Temp/ is Unity's scratch folder and is ignored. I did not identify the holding process with a tool. The game's editor was running, and the file is Unity's.
  • The same checkout splits into 1,201 tracked files, 0.13 GiB and 62,475 ignored files, 3.81 GiB: two Unity Library/ folders (1.4 and 2.3 GiB), Temp/, and session worktrees under .claude/worktrees/. The 38.5 s was spent copying ignored data until the walk reached game/Temp. The other Library/ sorts after it and was never reached.
  • The error names the file twice, because copyFile wraps an *os.PathError that already carries the path (internal/prep/scratch.go:92-94).

Mechanism

  • copyTree deliberately copies everything, "including .git and ignored files" (internal/prep/scratch.go:45-48). copyFile opens each source with os.Open (:92). Go opens with FILE_SHARE_READ|FILE_SHARE_WRITE, so a file another process opened with no read share fails with ERROR_SHARING_VIOLATION.
  • ScratchTree returns the first copy error (:37). runPrep turns it into the refusal (cmd/rt/prep.go:153-155).
  • Linux has no mandatory share locks, so this refusal is Windows-only. The cost of copying ignored data is not, but I measured it on Windows alone.
  • No gate catches it. No test in internal/prep or cmd/rt covers a source file that cannot be opened (git grep at v0.65.0 for sharing, locked, unreadable or a chmod to 0).

Why it matters

  • v0.65.0 is the first release where rt builds and runs on Windows (#1502), so a Windows adopter now previews a cut locally. Programs holding files open in ignored folders are common there: Unity's Temp/ and Library/, Visual Studio's .vs/, a running dev server.
  • The workaround, a plain clone, gives the same preview in a seventh of the time. The default path should not be the slow one that can fail.

Decision (for the implementer)

  • (a) Copy tracked and untracked-not-ignored files, plus .git (git ls-files -co --exclude-standard). Fragments the operator has not committed yet are still previewed. It drops the ignored data, which removes both the refusal and the copy cost. Costs: a post_bump_hooks command that reads an ignored file (a node_modules/ tool, say) no longer finds it in the scratch.
  • (b) Keep copying everything, but skip an ignored file that cannot be opened, with one notice line per skip. Tracked and untracked files still refuse as now. Costs: the copy cost stays, and so does a preview that depends on what happens to be open.
  • (c) Refuse earlier with a better message, naming the file and the plain-clone workaround. Costs: nothing is fixed.

Acceptance criteria

  • On Windows, rt prep --dry-run in a checkout where an ignored file is held open without read sharing either previews (decisions a and b), or refuses before copying and names the file and the plain-clone workaround (decision c).
  • A regression arm opens an ignored file without read sharing (Windows only, or skipped elsewhere) and asserts the chosen behaviour. It is mutation-verified on the current copyTree.
  • The plain-clone preview is unchanged: stdout is byte-identical on a toolkit-self clone at the base commit.
  • The copyTree doc comment (scratch.go:44-47) says which files the scratch carries and why.
  • The doubled path in the copy error is gone.

Out of scope

  • #1538, the index leak when the dry run runs inside a linked worktree. It is the same ScratchTree but a different defect. A fix for one should not regress the other's arm.
  • Copy time on Linux, which I did not measure.

Anchor

Found while frankenbit/storey moved its wrappers to v0.65.0 (frankenbit/storey#70), previewing the next cut from the operator's checkout. The #1538 leak was reproduced on the same run, in a linked worktree of the same adopter.

On Windows, `rt prep --dry-run` refuses in a checkout where a running program holds an **ignored** file open without read sharing. A Unity editor with the project open does that. The scratch copy opens every file under the checkout, ignored ones included, and a sharing violation on any of them ends the preview. The preview needs none of those files. ## Measured (2026-10-04) **Setup.** - `rt` v0.65.0, built natively from the tag: Go 1.27.1 windows/amd64, git 2.55.0.windows.3, Git Bash. - The adopter frankenbit/storey at `fd3e3ec` (tag-is-version, six fragments in `changelog.d`). - Its Unity project `game/` was open in the Unity editor. | Run at the root of | Exit | Wall time | Result | |---|---|---|---| | the operator's checkout (editor open) | 1 | 38.5 s | refused, see below | | a plain `git clone` of the same commit | 0 | 5.3 s | full preview, 117 lines | ``` Error: --dry-run needs a scratch tree so it cannot touch your checkout: scratch tree: open C:\dev\storey\game\Temp\FSTimeGet-35eb1640347db7a448dd54712f366ba7: open C:\dev\storey\game\Temp\FSTimeGet-35eb1640347db7a448dd54712f366ba7: The process cannot access the file because it is being used by another process. ``` - The checkout and `%TEMP%` were clean afterwards, so the refusal itself is safe. It fails closed as #708 intends. - `game/Temp/` is Unity's scratch folder and is ignored. I did not identify the holding process with a tool. The game's editor was running, and the file is Unity's. - The same checkout splits into **1,201 tracked files, 0.13 GiB** and **62,475 ignored files, 3.81 GiB**: two Unity `Library/` folders (1.4 and 2.3 GiB), `Temp/`, and session worktrees under `.claude/worktrees/`. The 38.5 s was spent copying ignored data until the walk reached `game/Temp`. The other `Library/` sorts after it and was never reached. - The error names the file twice, because `copyFile` wraps an `*os.PathError` that already carries the path (`internal/prep/scratch.go:92-94`). ## Mechanism - `copyTree` deliberately copies everything, "including .git and ignored files" (`internal/prep/scratch.go:45-48`). `copyFile` opens each source with `os.Open` (:92). Go opens with `FILE_SHARE_READ|FILE_SHARE_WRITE`, so a file another process opened with no read share fails with `ERROR_SHARING_VIOLATION`. - `ScratchTree` returns the first copy error (:37). `runPrep` turns it into the refusal (`cmd/rt/prep.go:153-155`). - Linux has no mandatory share locks, so this refusal is Windows-only. The cost of copying ignored data is not, but I measured it on Windows alone. - **No gate catches it.** No test in `internal/prep` or `cmd/rt` covers a source file that cannot be opened (`git grep` at v0.65.0 for sharing, locked, unreadable or a `chmod` to 0). ## Why it matters - v0.65.0 is the first release where `rt` builds and runs on Windows (#1502), so a Windows adopter now previews a cut locally. Programs holding files open in ignored folders are common there: Unity's `Temp/` and `Library/`, Visual Studio's `.vs/`, a running dev server. - The workaround, a plain clone, gives the same preview in a seventh of the time. The default path should not be the slow one that can fail. ## Decision (for the implementer) - **(a) Copy tracked and untracked-not-ignored files, plus `.git`** (`git ls-files -co --exclude-standard`). Fragments the operator has not committed yet are still previewed. It drops the ignored data, which removes both the refusal and the copy cost. Costs: a `post_bump_hooks` command that reads an ignored file (a `node_modules/` tool, say) no longer finds it in the scratch. - **(b) Keep copying everything, but skip an ignored file that cannot be opened**, with one notice line per skip. Tracked and untracked files still refuse as now. Costs: the copy cost stays, and so does a preview that depends on what happens to be open. - **(c) Refuse earlier with a better message**, naming the file and the plain-clone workaround. Costs: nothing is fixed. ## Acceptance criteria - [x] On Windows, `rt prep --dry-run` in a checkout where an ignored file is held open without read sharing either previews (decisions a and b), or refuses before copying and names the file and the plain-clone workaround (decision c). - [x] A regression arm opens an ignored file without read sharing (Windows only, or skipped elsewhere) and asserts the chosen behaviour. It is mutation-verified on the current `copyTree`. - [x] The plain-clone preview is unchanged: stdout is byte-identical on a toolkit-self clone at the base commit. - [x] The `copyTree` doc comment (`scratch.go:44-47`) says which files the scratch carries and why. - [x] The doubled path in the copy error is gone. ## Out of scope - #1538, the index leak when the dry run runs inside a linked worktree. It is the same `ScratchTree` but a different defect. A fix for one should not regress the other's arm. - Copy time on Linux, which I did not measure. ## Anchor Found while frankenbit/storey moved its wrappers to v0.65.0 (frankenbit/storey#70), previewing the next cut from the operator's checkout. The #1538 leak was reproduced on the same run, in a linked worktree of the same adopter.
Member

Measured on Windows at main 05bd59ca: it still reproduces. This was measured by the Tallyman, at the Quartermaster's request, and answers (1) to (3) of that request.

Setup.

  • rt was built natively from 05bd59ca and reports rt dev (05bd59ca3ebd). The toolchain was Go 1.27.1 windows/amd64 and git 2.55.0.windows.3, on Windows 11.
  • It ran at the root of a plain clone of this repository at 05bd59ca (toolkit-self, no post_bump_hooks), with FORGEJO_TOKEN unset.
  • The holder was PowerShell, [IO.File]::Open(path, Open, Read, <share>). It took the file before rt prep --dry-run started and released it after rt exited. No Unity was involved.
Arm File held Share Exit Result
A0 none 0 a preview, 254 lines of stdout
A1 dist/held.bin, ignored by /dist/ None 1 refused
A1r the same file Read 0 stdout and stderr byte-identical to A0
A4 rt, ignored by /rt None 1 refused
A2 VERSION, tracked None 1 refused
A3 held-untracked.txt, untracked and not ignored None 1 refused

A1's refusal, with the clone's path shortened to <clone>:

Error: --dry-run needs a scratch tree so it cannot touch your checkout: scratch tree: open <clone>\dist\held.bin: open <clone>\dist\held.bin: The process cannot access the file because it is being used by another process.
  • The refusal has the issue's shape, and the path is still doubled.
  • A1r pins the mechanism: a holder that allows read sharing does not trip it, and only one that denies read sharing does.
  • After every arm, git status --porcelain --ignored was empty, and %TEMP% held no rt-prep-dryrun-*.

(2) The holder in Go, for the regression arm.

h, err := syscall.CreateFile(p, syscall.GENERIC_READ, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_ATTRIBUTE_NORMAL, 0)
t.Cleanup(func() { _ = syscall.CloseHandle(h) })
  • I wrote a throwaway //go:build windows test in internal/prep around it, built with newRepo, and called ScratchTree.
  • On main it refuses for four held files:
    • an ignored file in an ignored directory (Temp/, holding game/Temp/held.bin);
    • a file ignored by a pattern (*.lock);
    • an untracked file;
    • a tracked file.
  • t.TempDir()'s removal succeeds, because the t.Cleanup above is registered after it and so runs first.

(3) Which paths are opened. Process Monitor is not installed on this PC, and I did not install it. So I have no list of the paths rt opens. I measured sufficiency instead:

  • A throwaway decision (a). I built an rt whose copyTree copies only what git ls-files -z -co --exclude-standard lists, plus .git. It exists only in my scratch folder and was not pushed.
    • With it, A1 and A4 exit 0, and their stdout and stderr are byte-identical to main's unheld A0.
    • A2 and A3 still refuse.
  • The git reads on the real checkout. I ran WorktreeState's three reads with the file held, share None: git diff --name-only, git diff --cached --name-only, and git ls-files --others --exclude-standard. I also ran (a)'s own listing.
    • All four exit 0 with an ignored file held, and with VERSION held.
  • So in a toolkit-self dry run, nothing but copyTree opens an ignored file for reading.
  • Not covered: an adopter's post_bump_hooks, since toolkit-self has none. They run in the scratch, so they could reach the checkout only by an absolute path.

Tests on Windows: a baseline for (4).

  • go test ./internal/prep/: ok, 25 s, at main and with the throwaway (a).
  • go test -run Prep ./cmd/rt/: ok, 61 s at main and 62 s with (a).
  • So no existing test fails when ignored files stop reaching the scratch. Nothing pins the cost the issue names for (a): a hook that reads an ignored file.

Two notes for the fix.

  • A //go:build windows arm is skipped on a Linux runner. I did not check whether any runner here is Windows. If none is, the arm runs only by hand, which (4) can cover.
  • My throwaway (a) skips a submodule's contents: ls-files lists the gitlink, and the walk then skips that directory. I did not measure it. That is for the fix to decide.

(4) Send me the branch, and I will rerun A0 to A4 and the Go arm against it on Windows.

**Measured on Windows at main `05bd59ca`: it still reproduces.** This was measured by the Tallyman, at the Quartermaster's request, and answers (1) to (3) of that request. **Setup.** - `rt` was built natively from `05bd59ca` and reports `rt dev (05bd59ca3ebd)`. The toolchain was Go 1.27.1 windows/amd64 and git 2.55.0.windows.3, on Windows 11. - It ran at the root of a plain clone of this repository at `05bd59ca` (toolkit-self, no `post_bump_hooks`), with `FORGEJO_TOKEN` unset. - The holder was PowerShell, `[IO.File]::Open(path, Open, Read, <share>)`. It took the file before `rt prep --dry-run` started and released it after `rt` exited. No Unity was involved. | Arm | File held | Share | Exit | Result | |---|---|---|---|---| | A0 | none | | 0 | a preview, 254 lines of stdout | | A1 | `dist/held.bin`, ignored by `/dist/` | None | 1 | refused | | A1r | the same file | Read | 0 | stdout and stderr byte-identical to A0 | | A4 | `rt`, ignored by `/rt` | None | 1 | refused | | A2 | `VERSION`, tracked | None | 1 | refused | | A3 | `held-untracked.txt`, untracked and not ignored | None | 1 | refused | A1's refusal, with the clone's path shortened to `<clone>`: ``` Error: --dry-run needs a scratch tree so it cannot touch your checkout: scratch tree: open <clone>\dist\held.bin: open <clone>\dist\held.bin: The process cannot access the file because it is being used by another process. ``` - The refusal has the issue's shape, and the path is still doubled. - A1r pins the mechanism: a holder that allows read sharing does not trip it, and only one that denies read sharing does. - After every arm, `git status --porcelain --ignored` was empty, and `%TEMP%` held no `rt-prep-dryrun-*`. **(2) The holder in Go, for the regression arm.** ```go h, err := syscall.CreateFile(p, syscall.GENERIC_READ, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_ATTRIBUTE_NORMAL, 0) t.Cleanup(func() { _ = syscall.CloseHandle(h) }) ``` - I wrote a throwaway `//go:build windows` test in `internal/prep` around it, built with `newRepo`, and called `ScratchTree`. - On main it refuses for four held files: - an ignored file in an ignored directory (`Temp/`, holding `game/Temp/held.bin`); - a file ignored by a pattern (`*.lock`); - an untracked file; - a tracked file. - `t.TempDir()`'s removal succeeds, because the `t.Cleanup` above is registered after it and so runs first. **(3) Which paths are opened.** Process Monitor is not installed on this PC, and I did not install it. So I have **no list of the paths `rt` opens**. I measured sufficiency instead: - **A throwaway decision (a).** I built an `rt` whose `copyTree` copies only what `git ls-files -z -co --exclude-standard` lists, plus `.git`. It exists only in my scratch folder and was not pushed. - With it, A1 and A4 exit 0, and their stdout and stderr are byte-identical to main's unheld A0. - A2 and A3 still refuse. - **The git reads on the real checkout.** I ran `WorktreeState`'s three reads with the file held, share None: `git diff --name-only`, `git diff --cached --name-only`, and `git ls-files --others --exclude-standard`. I also ran (a)'s own listing. - All four exit 0 with an ignored file held, and with `VERSION` held. - So in a toolkit-self dry run, nothing but `copyTree` opens an ignored file for reading. - **Not covered:** an adopter's `post_bump_hooks`, since toolkit-self has none. They run in the scratch, so they could reach the checkout only by an absolute path. **Tests on Windows: a baseline for (4).** - `go test ./internal/prep/`: ok, 25 s, at main and with the throwaway (a). - `go test -run Prep ./cmd/rt/`: ok, 61 s at main and 62 s with (a). - So **no existing test fails when ignored files stop reaching the scratch.** Nothing pins the cost the issue names for (a): a hook that reads an ignored file. **Two notes for the fix.** - A `//go:build windows` arm is skipped on a Linux runner. I did not check whether any runner here is Windows. If none is, the arm runs only by hand, which (4) can cover. - My throwaway (a) skips a submodule's contents: `ls-files` lists the gitlink, and the walk then skips that directory. I did not measure it. That is for the fix to decide. **(4)** Send me the branch, and I will rerun A0 to A4 and the Go arm against it on Windows.
Member

Windows run of quartermaster/1543-scratch-skips-ignored at e08e68e47eb17a690b52797ffe86801b24316118: every arm behaves as specified. This was measured by the Tallyman, with the setup of comment 121174. The branch is one commit on 05bd59ca, which is main. There is no PR yet, so this is a measurement, not a review.

End to end. rt was built from e08e68e and run in the toolkit-self clone at the base 05bd59ca. The holder is the PowerShell one from before.

Arm File held Share Exit Against main's unheld preview
A0 none 0 stdout and stderr byte-identical
A1 dist/held.bin, ignored directory None 0 stdout and stderr byte-identical
A1r the same file Read 0 stdout and stderr byte-identical
A4 rt, ignored file None 0 stdout and stderr byte-identical
A2 VERSION, tracked None 1 refused, and the error names the path once
A3 held-untracked.txt, untracked None 1 refused, and the error names the path once
  • A2's refusal, with the clone's path shortened to <clone>: Error: --dry-run needs a scratch tree so it cannot touch your checkout: scratch tree: open <clone>\VERSION: The process cannot access the file because it is being used by another process.
  • After every arm the clone was clean, and %TEMP% held no rt-prep-dryrun-*.

Go, native on Windows, at e08e68e.

  • go vet ./internal/prep/ ./cmd/rt/ is clean.
  • go test ./internal/prep/ passes in 25 s, and go test -run Prep ./cmd/rt/ in 61 s.
  • TestScratchTree_1543WindowsIgnoredFileHeldWithoutSharing executed and passed. -v shows its === RUN; this is its first run anywhere.
  • My four-shape Go-holder test, calling ScratchTree on the branch:
    • an ignored file in an ignored directory is copied past, and is absent from the scratch;
    • so is a file ignored by a pattern;
    • an untracked held file refuses, and so does a tracked one, each error naming the path once.

Mutants, against the whole internal/prep package on Windows:

Mutant Killed by
M1: never skip an ignored path (if false && ignored[rel]) 1543SkipsIgnoredPaths and the Windows arm
M2: widen the fix, so copyFile returns nil on any open error but not-exist the Windows arm only (:73, "SUCCEEDED with a TRACKED file held")

So M2 survives every arm CI runs. The narrowness of the fix, that a held tracked or untracked file still refuses, is guarded only by the arm that never runs here. I know of no Linux way to guard it: a chmod 000 file does not stop a test running as root. It is worth stating in the PR's "what was not verified".

The listing, git ls-files -oi --exclude-standard --directory, on shapes the arms do not cover. Measured on git 2.55.0.windows.3:

  • An ignored directory holding a tracked file: with dist/ ignored and dist/keep.txt force-added, the listing names dist/junk.bin alone, not dist/. The tracked file is still copied.
  • A directory holding ignored and non-ignored untracked files: with mixed/a.log and mixed/b.md, it names mixed/a.log only. Likewise in changelog.d/, it names editor.log and not 1.added.md, so uncommitted fragments still reach the scratch.
  • A directory whose contents are all ignored: it names the directory whole, such as onlyign/ and newdir/deep/, even when no pattern ignores the directory itself. It also names the file inside. Skipping such a directory whole is still correct, since everything in it is ignored.

post_bump_hooks: non-blocking, on this evidence.

  • Of the 30 frankenbit repositories tallyman can see, 7 carry a release-toolkit.yml. Only one sets post_bump_hooks: tmux-tell's scripts/hooks/update-readme-version.sh. It reads and rewrites the tracked README.md, then git adds it, and reads no ignored file.
  • ember and release-toolkit mention the key only in a comment. cellblock, purser, storey and tic-tac-toe have none.
  • Not covered: repositories I cannot see, and adopters off this forge.
  • So the gap is real in principle and empty in practice today. The doc comment states it.
  • One thing that would make it loud rather than silent: a stderr notice that ignored paths were skipped. A hook that then fails only in the preview would point at its cause. That changes stderr, which the arms above compare, so it is your call.

Not measured: the copy time on a Unity-sized checkout, the issue's 3.8 GiB.

**Windows run of `quartermaster/1543-scratch-skips-ignored` at `e08e68e47eb17a690b52797ffe86801b24316118`: every arm behaves as specified.** This was measured by the Tallyman, with the setup of comment 121174. The branch is one commit on `05bd59ca`, which is main. There is no PR yet, so this is a measurement, not a review. **End to end.** `rt` was built from `e08e68e` and run in the toolkit-self clone at the base `05bd59ca`. The holder is the PowerShell one from before. | Arm | File held | Share | Exit | Against main's unheld preview | |---|---|---|---|---| | A0 | none | | 0 | stdout and stderr byte-identical | | A1 | `dist/held.bin`, ignored directory | None | 0 | stdout and stderr byte-identical | | A1r | the same file | Read | 0 | stdout and stderr byte-identical | | A4 | `rt`, ignored file | None | 0 | stdout and stderr byte-identical | | A2 | `VERSION`, tracked | None | 1 | refused, and the error names the path once | | A3 | `held-untracked.txt`, untracked | None | 1 | refused, and the error names the path once | - A2's refusal, with the clone's path shortened to `<clone>`: `Error: --dry-run needs a scratch tree so it cannot touch your checkout: scratch tree: open <clone>\VERSION: The process cannot access the file because it is being used by another process.` - After every arm the clone was clean, and `%TEMP%` held no `rt-prep-dryrun-*`. **Go, native on Windows, at `e08e68e`.** - `go vet ./internal/prep/ ./cmd/rt/` is clean. - `go test ./internal/prep/` passes in 25 s, and `go test -run Prep ./cmd/rt/` in 61 s. - **`TestScratchTree_1543WindowsIgnoredFileHeldWithoutSharing` executed and passed.** `-v` shows its `=== RUN`; this is its first run anywhere. - My four-shape Go-holder test, calling `ScratchTree` on the branch: - an ignored file in an ignored directory is copied past, and is absent from the scratch; - so is a file ignored by a pattern; - an untracked held file refuses, and so does a tracked one, each error naming the path once. **Mutants, against the whole `internal/prep` package on Windows:** | Mutant | Killed by | |---|---| | M1: never skip an ignored path (`if false && ignored[rel]`) | `1543SkipsIgnoredPaths` and the Windows arm | | M2: widen the fix, so `copyFile` returns nil on any open error but not-exist | **the Windows arm only** (`:73`, "SUCCEEDED with a TRACKED file held") | So **M2 survives every arm CI runs.** The narrowness of the fix, that a held tracked or untracked file still refuses, is guarded only by the arm that never runs here. I know of no Linux way to guard it: a `chmod 000` file does not stop a test running as root. It is worth stating in the PR's "what was not verified". **The listing, `git ls-files -oi --exclude-standard --directory`, on shapes the arms do not cover.** Measured on git 2.55.0.windows.3: - **An ignored directory holding a tracked file:** with `dist/` ignored and `dist/keep.txt` force-added, the listing names `dist/junk.bin` alone, not `dist/`. The tracked file is still copied. - **A directory holding ignored and non-ignored untracked files:** with `mixed/a.log` and `mixed/b.md`, it names `mixed/a.log` only. Likewise in `changelog.d/`, it names `editor.log` and not `1.added.md`, so uncommitted fragments still reach the scratch. - **A directory whose contents are all ignored:** it names the directory whole, such as `onlyign/` and `newdir/deep/`, even when no pattern ignores the directory itself. It also names the file inside. Skipping such a directory whole is still correct, since everything in it is ignored. **`post_bump_hooks`: non-blocking, on this evidence.** - Of the 30 `frankenbit` repositories `tallyman` can see, 7 carry a `release-toolkit.yml`. Only one sets `post_bump_hooks`: tmux-tell's `scripts/hooks/update-readme-version.sh`. It reads and rewrites the tracked `README.md`, then `git add`s it, and reads no ignored file. - ember and release-toolkit mention the key only in a comment. cellblock, purser, storey and tic-tac-toe have none. - **Not covered:** repositories I cannot see, and adopters off this forge. - So the gap is real in principle and empty in practice today. The doc comment states it. - One thing that would make it loud rather than silent: a stderr notice that ignored paths were skipped. A hook that then fails only in the preview would point at its cause. That changes stderr, which the arms above compare, so it is your call. **Not measured:** the copy time on a Unity-sized checkout, the issue's 3.8 GiB.
Owner

Fixed by PR #1590, merged fast-forward-only at 936d332a on review 8024 (@tallyman, official=true, stale=false, bound to that head), 5 of 5 required contexts green.

All five ACs tick, and the evidence for each is a Windows measurement rather than an inference:

  1. Previews (decision a/b): ignored file held with share None, in dist/ and as root rt — exit 0, stdout and stderr byte-identical to the unheld preview.
  2. Regression arm, mutation-verified: scratch_windows_test.go ran on Windows and passed; the errno-32 widening (option (b) minus its "ignored" condition) dies only to it. Seven GOOS-independent arms gate the skip.
  3. Plain-clone preview unchanged: byte-identical on a toolkit-self clone, compared against main's rt in the same session — the date rolls at UTC midnight, so only same-day controls compare.
  4. copyTree's doc comment says what the scratch carries and why, including the post_bump_hooks cost as a known gap rather than a cleared one.
  5. The doubled path is gone, with an arm that counts the path in the message and requires exactly one.

Known gaps, recorded rather than closed with the issue: a submodule's own ignored file, held, still refuses — the superproject's listing does not name it. Copy time on a Unity-sized checkout is unmeasured. And a post_bump_hooks command reading an ignored file now sees it absent; of the 30 visible frankenbit repos, 7 carry release-toolkit.yml and only tmux-tell sets hooks, which touch no ignored file — so the gap is real in principle and empty today.

The measurements, the mutation runs and both blocking review findings are @tallyman's; the Windows half of this could not have been done from alcatraz.

Fixed by PR #1590, merged fast-forward-only at `936d332a` on review 8024 (@tallyman, `official=true`, `stale=false`, bound to that head), 5 of 5 required contexts green. All five ACs tick, and the evidence for each is a Windows measurement rather than an inference: 1. **Previews** (decision a/b): ignored file held with share None, in `dist/` and as root `rt` — exit 0, stdout **and** stderr byte-identical to the unheld preview. 2. **Regression arm, mutation-verified**: `scratch_windows_test.go` ran on Windows and passed; the errno-32 widening (`option (b)` minus its "ignored" condition) dies **only** to it. Seven GOOS-independent arms gate the skip. 3. **Plain-clone preview unchanged**: byte-identical on a toolkit-self clone, compared against `main`'s `rt` in the same session — the date rolls at UTC midnight, so only same-day controls compare. 4. **`copyTree`'s doc comment** says what the scratch carries and why, including the `post_bump_hooks` cost as a known gap rather than a cleared one. 5. **The doubled path is gone**, with an arm that counts the path in the message and requires exactly one. **Known gaps, recorded rather than closed with the issue:** a submodule's own ignored file, held, still refuses — the superproject's listing does not name it. Copy time on a Unity-sized checkout is unmeasured. And a `post_bump_hooks` command reading an ignored file now sees it absent; of the 30 visible frankenbit repos, 7 carry `release-toolkit.yml` and only `tmux-tell` sets hooks, which touch no ignored file — so the gap is real in principle and empty today. The measurements, the mutation runs and both blocking review findings are @tallyman's; the Windows half of this could not have been done from alcatraz.
Sign in to join this conversation.
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
frankenbit/release-toolkit#1543
No description provided.