Conversation
…reed Shrink mode computed the resize target from Get-PartitionSupportedSize's SizeMax and then created the partition with -UseMaximumSize. Both were wrong, and measurement showed why. SizeMax is the partition's size plus the contiguous unallocated run behind it, not the partition's size. Measured: a 40 GB partition with a 4 GB gap behind it reported SizeMax 44 GB, while a 70 GB tail beyond a further partition was not counted. So a target of "SizeMax minus the amount asked for" made the volume give up less than the plan named - and, where more space adjoined the volume than the user asked to free, grew it instead of shrinking it. -UseMaximumSize creates the largest possible partition on the disk, not one in the space just freed. Measured on a disk laid out A(15) | gap 15 | C(5) | tail 25: the partition landed in the tail and the freed 15 GB was left unused. - compute the target from the volume's current size, so it gives up exactly the amount asked for - place the new partition explicitly, at the offset the shrunk volume actually ends at and sized to the free run behind it, so it can only land there - keep -UseMaximumSize in the .vhdx branch alone, where the disk was created moments earlier and holds one free region - name the adjoining space while the amount is being chosen, and say in the plan how large the drive will come out and why that exceeds what is freed - refuse with wording a person can act on, never with a rejection name - guard the subtraction that decides what is written, because by then the volume has already been shrunk Confirmed end to end on a USB disk laid out shrinkme(40) | 4 free | 5 | 70.23 free: freeing 6 GB left the volume at 34 GB, produced a 10 GB partition immediately behind it, and left the 70.23 GB region untouched. Closes #83 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…re refusing it Two findings from a second review and one measurement it asked for. New-Partition refuses an offset that is not a whole number of megabytes, and Resize-Partition aligns to the cluster rather than to that boundary - measured 20 to 389 bytes off its target across five runs. So the offset handed to New-Partition is usually unaligned, and all five awkward shrink amounts (6.37, 3.14159, 0.77, 1.005 and 2.0009 GB) were refused with "The specified offset is not valid" - after the volume had already been shrunk. The earlier end-to-end confirmation passed only because 6 GB happened to be aligned. Resolve-AlignedPlacement nudges the start to the next megabyte and gives back from the size exactly what the nudge took; the same probe then accepted all five. The note naming the space behind the drive was printed before the 50 GB viability check, so a volume about to be refused was first told that space would join its Dev Drive. It now comes after the refusal and before the question. - guard the drive against coming out below the Dev Drive minimum, and say when the space behind the volume did not come to what the plan named - give Format-ShrinkRefusal a real default arm, so a rejection added later cannot inherit another one's wording, and make its sizes mandatory rather than defaulting to a number nobody measured - derive the extra space in the plan note from the two figures shown, so the three always add up on screen - say "about this much, and likely more" where the adjoining space could not be measured, since alignment can leave the drive marginally smaller - use ConvertTo-ByteCount in free-space mode too, which the first pass missed - pin the wiring the change turns on: reverting one line had left every test green while the plan understated the drive Closes #83 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 26, 2026
ALERTua
added a commit
that referenced
this pull request
Aug 26, 2026
Record the two shrink facts measured in #97
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #83.
What was wrong
Shrink mode computed the resize target from
Get-PartitionSupportedSize'sSizeMax, then created the partition with-UseMaximumSize. Both are wrong, and #83 as filed described only the mildest of the consequences.Two behaviours, neither documented, both measured on scratch disks:
SizeMaxis the partition's size plus the contiguous unallocated run behind it. Microsoft documents only "the minimum and maximum size of the partition". Measured twice in one layout: a 20 GB partition with a 10 GB gap behind it reportedSizeMax30 GB; a 5 GB partition with a 24.98 GB tail behind it reported 29.98 GB. A later run confirmed the contiguous part: a 40 GB partition with a 4 GB gap behind it, and a 70 GB tail beyond a further partition, reported 44 GB.-UseMaximumSizecreates the largest possible partition on the disk, not one in the space just freed. Measured on a disk laid outA(15 GB) | gap 15 GB | C(5 GB) | tail 25 GB: the partition landed in the tail at 24.98 GB and the freed 15 GB was left untouched.Together those gave three defects:
SizeMax − askedinstead ofsize − askedWhat it does now
-UseMaximumSizesurvives only in the.vhdxbranch, where the disk was created moments earlier and holds exactly one free region. A syntax-tree assertion enforces that: anyNew-Partitionwithout an explicit-Sizemust name$vhdxDiskNumber.The arithmetic moved into
Resolve-ShrinkPlan, a pure function, so the numbers are testable without a disk. It guards before subtracting and answers zero sizes on every rejection, so no caller can act on a number it never got.Confirmed on real hardware
A USB disk laid out
shrinkme 40 GB | 4 GB free | 5 GB partition | 70.23 GB free, freeing 6 GB:The far region being seven times the resulting drive is the point: the old code would have put a 70 GB partition there. The disk was restored to a single empty NTFS volume afterwards.
dev_drive.ps1was never executed — the probe lifts its functions out of the syntax tree, which is how this repository exercises them.Checks
Parse OK;
Invoke-ScriptAnalyzer -Path . -Recurseno findings; 695 tests pass (665 before). The pre-commit hook ran the same three.Both new syntax-tree assertions were proved to have teeth by reintroducing their defects on scratch copies:
Review
An independent review raised 18 points and all were applied. The three that mattered most:
"Windows reports MaxBelowCurrent"— a token this script invented, which Windows never said and which names nothing a person can act on. There is now one wording for both call sites, throughFormat-ShrinkRefusal, and a test forbids the token appearing in user-facing text.New-Partitionas a parameter-binding error rather than as an explanation. It now has a guard that says what happened, and a test pins that the guard precedes the creation.Also applied: a comment that claimed
[uint64]subtraction wraps around (measured: it yields a negativeDouble); a rejection token colliding with an unrelated one inResolve-DevDriveSizeInput;[decimal]$nullsilently turning "unknown" into zero; inconsistent unit naming across the returned fields;([uint64][math]::Round($GB * 1GB))written three times, nowConvertTo-ByteCount; and two test-block boundaries that my own edits had quietly loosened.Nothing from that review was declined.
Separately, and after it: a first attempt also lowered the minimum shrink the question accepts by whatever adjoins the volume, added a viability check counting that space toward the 50 GB Dev Drive minimum, and skipped the resize when nothing had to be given up. That was removed at the owner's request as more flow than the problem warranted. The minimum stays a flat 50 GB, so the drive is always at least that whatever adjoins it, and the note at the input point is the whole of what the user gains.
🤖 Generated with Claude Code
Second review, and the measurement it asked for
A second independent review read the finished change. It raised 13 points; 12 were applied, and one of its open questions turned out to be a defect that would have shipped.
It asked whether an explicit
-Offsetsurvives a shrink amount that is not a round number of gigabytes. The end-to-end run above used 6 GB, which happens to land on a megabyte boundary. Measured on a scratch virtual disk with five awkward amounts:New-Partitionaccepts only an offset that is a whole number of megabytes, andResize-Partitionaligns to the cluster instead - measured 20 to 389 bytes off its target every time. So the unaligned offset is the ordinary case, not a rare one, and the run would have died after the volume was already shrunk, the moment a user typed 50.5 instead of 50.Resolve-AlignedPlacementnow nudges the start forward to the next megabyte and takes the same amount off the size. Re-running the same probe against the shipped function:The other finding worth naming: the note about the space behind the drive was printed before the 50 GB viability check, so a volume that was about to be refused was first told that space would join its Dev Drive, and the refusal never mentioned it. The note now comes after the refusal and before the question.
Also applied: a
defaultarm in the refusal wording that would have let a later rejection inherit another one's message; two size parameters defaulting to a number nobody measured; the plan note's three figures not adding up, now derived from the two that are shown; "at least this much" where alignment can leave the drive marginally smaller, now "about this much, and likely more";ConvertTo-ByteCountmissed in free-space mode; and a guard so a drive that comes out below the 50 GB minimum stops the run instead of being reported and created anyway.The finding that mattered most for the suite: reverting a single line left every one of the 695 tests green while the plan understated the drive and the note read "comes out 200 GB rather than the 200 GB being freed". The wiring the change turns on is now pinned.
Declined, one: separately checking that the size question rejects numbers with many decimal places. The note now computes the extra space as the difference between the two figures it prints, so the arithmetic on screen holds however many decimals arrive.
718 tests pass.