Repository navigation
Conversation
The reflink mechanism was documented only under a "Performance" heading, next to a table of times, so the disk saving read as an explanation of why the copy is fast rather than a benefit in its own right. Rename the section to "Copy-on-write", add a Disk column to the table, and name the filesystems that support reflink along with what happens on ext4 and NTFS. Add an FAQ question for the fleet-level number, which is what someone running many worktrees actually wants to know. Drop the copy-on-write bullet from the Claude Code comparison. Claude Code ships compiled with Bun, whose fs.copyFile uses clonefile() on macOS, so it is not a difference there: measured on a 2GB file, Bun's copyFileSync consumed 6 MiB against Node's 2111 MiB. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NrPJmECkKALUaqxb9GKCC5
"Copy build caches" names the thing the feature avoids doing. The bullet now leads with sharing and states both benefits: a warm start, and no second copy of the build output on disk. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NrPJmECkKALUaqxb9GKCC5
worktrunk-bot
left a comment
There was a problem hiding this comment.
The reflink claims check out against reflink-copy 0.1.30 and src/copy.rs (APFS/btrfs/XFS/ReFS, full copy on ext4/NTFS), the #copy-on-write anchor slugs correctly and is unique on the step page, nothing in the tree still points at #performance, and all four mirrors plus the help snapshot match the primaries.
One thing: the PR body says the filesystem scope is stated wherever a claim is made, and the homepage bullet is the one place it isn't — it now asserts the disk saving unconditionally, on the surface with the widest audience and the least room to qualify. ext4 is the default on Debian and Ubuntu, so "without a second copy on disk" is wrong for a large share of readers, where the old "skip cold starts by sharing" was a time claim that held everywhere.
Suggestion is on docs/src/content/docs/worktrunk.md since it's primary; README.md and the two skill mirrors carry the same line, so cargo test --test integration test_docs_are_in_sync regenerates them.
"Are copy-on-write clones" stated it unconditionally, which is wrong on ext4 and NTFS. The bullet it replaced made a claim about time, which held everywhere; this one is about disk, so it needs the scope every other file in the change already carries. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NrPJmECkKALUaqxb9GKCC5
"A second copy" described one duplication event; the cost someone weighing a many-worktree workflow cares about is the tenth. "Starts warm" also lost its pair when "skip cold starts" left this bullet, so the metaphor no longer had an anchor on the page. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NrPJmECkKALUaqxb9GKCC5
Pairing "a new worktree is immediately ready" with "ten of them share one copy" built a singular/plural symmetry no neighbouring bullet has, and reads as constructed. Naming the filesystems replaces the abstract "where the filesystem supports it" and matches how the FAQ and step page already scope the claim. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NrPJmECkKALUaqxb9GKCC5
Dropping the constructed benefit pair took the readiness claim with it. Making "ten worktrees" the subject carries the stacking cost, and "without building or copying" names both costs avoided in one clause rather than two matched ones. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NrPJmECkKALUaqxb9GKCC5
`wt step copy-ignored` told every user the same thing — `Copied 65,679 files · 29.5 GB` — whether the filesystem shared the source's blocks or wrote all 29.5 GB out. On ext4 and NTFS, which have no reflink, that is the difference between a free copy and a full one, and nothing in the output said which had happened. #4022 documented the caveat per filesystem; this reports it per machine. ``` ✓ Copied 4,812 files · 14.0 GB (reflinked, no extra disk) ✓ Copied 4,812 files · 14.0 GB (full copy) ✓ Copied 4,812 files · 14.0 GB (3,200 of 4,812 reflinked) ``` The signal was already in hand and thrown away. `reflink_or_copy` returns `Ok(None)` when the platform's clone syscall succeeded and `Ok(Some(bytes))` when it fell through to `fs::copy`, and `copy_leaf` matched `Ok(_)`. It now returns that alongside the byte count, `Progress::record` takes a `DataCopy` and counts each side, `Progress::copy_split` reads the pair back, and `--format=json` gains `reflinked` and `written` — with one rule and no exceptions: a payload reporting a **result** carries all four, zeroed where nothing was copied, and a **plan** (`dry_run: true`) carries none of them, since it says what would be copied rather than what was. That last half is a small behaviour change beyond the new keys — the `--require-include` and empty-entries returns fire before the dry-run branch, so under `--dry-run` they used to emit `files: 0, bytes: 0` while a plan with entries in it emitted neither. They now emit none, so `jq '.reflinked + .written'` no longer returns a number for one plan and null for another. `same_worktree` is unchanged: it reports a result rather than a plan and carries no `dry_run` key. Only the reflinked wording carries a gloss, since that is the term a reader won't know; the contrast then says what a full copy cost without repeating the byte count sitting two words to its left. <details> <summary>Three decisions worth a look</summary> **Symlinks record `DataCopy::Neither` rather than counting as written.** A symlink's content is a path, so it has no extents to share or to write. Counting it on either side would make a `node_modules/` full of bin shims report as a partial reflink failure on a machine where everything with data in it cloned fine. **`WORKTRUNK_TEST_REFLINK=1|0` pins the reported label**, while the copy still attempts a reflink either way. Whether a clone succeeds is a property of the filesystem under the test's temp directory, and CI spans APFS, ext4, and NTFS, so no one snapshot could hold on all three and the branch a given run took would be invisible. It is set per command in the copy-ignored tests rather than in `STATIC_TEST_ENV_VARS`, which reaches every child and would add an `env:` line to every snapshot in the suite. Eleven snapshots pin the reflinked branch and one new test pins `(full copy)`; the mixed case needs two filesystems under one tree, so unit tests cover its counting and its rendering. **`classify_copy` is a named function for the same reason.** The coverage job runs on `ubuntu-24.04`, so ext4 never reaches the reflinked arm and it would have posted as a patch miss. A unit test pins the mapping instead of whichever runner happens to execute it. </details> The signal is exact per file, but it says only that a clone did not happen, never why — an unsupported filesystem and a cross-device copy are indistinguishable here. That is enough for the claim the output makes (those bytes really were written) and not enough to assert "this filesystem has no reflink", which the wording avoids. `wt step promote` also calls `copy_leaf` and reports no split: its copy path only runs as the cross-device fallback when `rename` fails with EXDEV, where a reflink is impossible by definition. > _This was written by Claude Code on behalf of max-sixty_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01NrPJmECkKALUaqxb9GKCC5 --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
wt step copy-ignoredreflinks the files it copies, so a new worktree'starget/shares disk blocks with the primary worktree's until something writes to them. That was documented only under a "Performance" heading, next to a table of times, where it read as an explanation of why the copy is fast rather than a benefit in its own right. Nothing quantified the disk saving, and the homepage bullet ("Copy build caches — skip cold starts…") sold the time and not the space.The saving is large and it survives real use: cargo writes new files for changed crates rather than rewriting dependency rlibs, so the bulk of
target/stays shared as a worktree is built in. Measured across 56 worktrees of this repo on one machine,dureports 2.59 TB where the disk holds 0.71 TB.Changes:
copy-ignored"Performance" section to "Copy-on-write", add a Disk column to the existing table, and name the filesystems that support reflink along with what happens on ext4 and NTFS. Nothing linked to the old#performanceanchor.target/,node_modules/, etc reach ten worktrees without being built or copied.fs.copyFileusesclonefile()on macOS, so it isn't a difference there: on a 2 GB file, Bun'scopyFileSyncconsumed 6 MiB against Node's 2111 MiB. The remaining bullets carry the real differences.The filesystem scope is stated wherever a claim is made, since reflink needs APFS, btrfs, XFS, or ReFS; ext4 and NTFS fall back to a full copy.
Follow-up, not in this PR:
reflink_or_copyreturnsNoneon reflink andSome(bytes)on fallback, andsrc/copy.rs:125matchesOk(_)and discards it. So a user on ext4 gets a full copy, is toldCopied N files · 29.5 GB, and has no way to learn which they got. The docs now state the caveat per filesystem; the command could state it per machine.