Skip to content

Report every unresolvable SDK package, not just the first - #28

Merged
matt-edmondson merged 1 commit into
mainfrom
claude/exciting-albattani-fru842
Sep 26, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
claude/exciting-albattani-fru842

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #27

The defect

scripts/update-sdks.ps1 sets $ErrorActionPreference = 'Stop' at line 54, which makes a bare Write-Error terminating. Neither Write-Error in the resolution loop was wrapped in a try/catch, so the first unresolvable package threw straight out of the script and the continue beneath each one was dead code.

Measured at main (ea3d28a), PowerShell 7.4.6 on Linux, against a fixture pinning three packages and a closed port as the feed:

Found 3 ktsu.Sdk reference(s):
  ktsu.Sdk: 1.0.0
  ktsu.Sdk.App: 1.0.0
  ktsu.Sdk.Tool: 1.0.0
THREW: Could not resolve the latest version of ktsu.Sdk from http://127.0.0.1:9 : Connection refused

ktsu.Sdk.App and ktsu.Sdk.Tool are never attempted. Because the loop runs Group-Object Name | Sort-Object Name, whichever package sorts first decides how much of the run happens at all — and ktsu.Sdk, the package nearly every repository pins, sorts first.

The change

Failures are named on the error stream as they happen — -ErrorAction Continue, which is the explicit form of what the continue was already trying to express — and collected into $failed. The run then fails once, after every package has been looked at:

if ($failed.Count -gt 0) {
	throw "Could not resolve $($failed.Count) of $($targets.Count + $failed.Count) $Prefix package(s): $($failed -join ', ')"
}

The throw stays ahead of the write phase. That placement is the part worth arguing about, because reporting the failures and converging the packages that did resolve looks like the more helpful behaviour. It is not: it would leave global.json on 2.29.0 for one member of the family and 1.0.0 for the rest — two versions of the same SDK in one repository, which is the exact state this script exists to repair, and which docs/sdk-pinning.md already commits to avoiding ("A failure leaves the repository untouched and red"). All-or-nothing is preserved; only the reporting changes.

The issue also notes that the run "never gets to report which references are stale for packages that did resolve successfully". That is left alone deliberately — a staleness report computed from a partial set of targets would read as a complete one, and the failure is the actionable output.

Tests

The resolution path had no coverage at all: every existing case passes -Version explicitly, which skips Get-LatestReleasedVersion entirely. Two cases added, neither touching the network.

case feed covers
every unresolvable package is reported, not just the first closed port (127.0.0.1:9) the catch branch; the defect itself
a resolvable package neither hides a failure nor is written past one in-process HttpListener stub both branches at once, plus the all-or-nothing guarantee

The second case is the interesting one. ktsu.Sdk resolves to 2.29.0 while the fixture pins 1.0.0, ktsu.Sdk.App 404s (the catch branch) and ktsu.Sdk.Tool publishes only a prerelease (the -not $latest branch). It asserts both failures are named and that global.json is untouched, so a fix that reported-and-carried-on fails it even though it would pass the first case.

The stub feed is ~40 lines of Start-StubFeed/Stop-StubFeed using HttpListener on a Start-ThreadJob. Invoke-RestMethod rejects file:// ("The 'file' scheme is not supported"), so a static fixture directory is not an option and a real HTTP endpoint is the only way to reach the -not $latest branch. It binds the first free port in 18080–18179 and answers an unmapped package id with 404.

Proved failing without the fix. Reverting only scripts/update-sdks.ps1 and keeping both tests:

ok    the bare prefix package is tracked
ok    an adjacent package name is left alone
ok    a partial bump is repaired even when one entry is already current
ok    global.json and a csproj are converged on one version
ok    a prerelease pin is replaced whole, not in part
ok    unrelated content and formatting survive
ok    an already-current repository is untouched
FAIL  every unresolvable package is reported, not just the first
      the failure never named: ktsu.Sdk.App, ktsu.Sdk.Tool
      it said: Could not resolve the latest version of ktsu.Sdk from http://127.0.0.1:9 : Connection refused (127.0.0.1:9)
FAIL  a resolvable package neither hides a failure nor is written past one
      the failure never named: ktsu.Sdk.Tool
      it said: Could not resolve the latest version of ktsu.Sdk.App from http://127.0.0.1:18080 : Response status code does not indicate success: 404 (Not Found).

2 of 9 case(s) failed.     exit 1

Both fail on the right thing — the packages the run never reached — rather than on a message that merely changed shape. The 7 pre-existing cases are unaffected in both directions.

Verification

  • scripts/tests/update-sdks.tests.ps1 — 9 of 9 passed, exit 0
  • Same suite against reverted update-sdks.ps1 — 2 of 9 failed, exit 1, as above
  • markdownlint docs/sdk-pinning.md — clean

Run on PowerShell 7.4.6 on Linux. Worth recording for the next run in this container: pwsh is not installed and dotnet tool install is broken here for every package, the same limitation recorded on ktsu-dev/Sdk#34 and on #26. The official tarball from the PowerShell GitHub releases works and is what I used.

Docs

docs/sdk-pinning.md gains a paragraph stating the behaviour and why the run fails before writing rather than converging what it can. It sits next to the existing "A failure leaves the repository untouched and red" sentence, which this makes true of the multi-package case too.

🤖 Generated with Claude Code

https://claude.ai/code/session_012betVHk3gFcj5RYkEe4vrm


Generated by Claude Code

$ErrorActionPreference is Stop in update-sdks.ps1, which makes a bare
Write-Error terminating. The two Write-Error calls in the resolution loop
were not wrapped, so the first unresolvable package threw straight out of
the script and the `continue` beneath each one was dead code. A maintainer
saw one failure per run: fix it, rerun, discover the next.

Failures are now named on the error stream as they happen and collected,
and the run fails once after every package has been looked at. The throw
stays ahead of the write phase, so a run that could not resolve part of the
family does not converge the rest and leave the repository on two versions
of the same SDK -- the partially applied state this script exists to repair.

Two test cases cover the path, which had none: every case in the suite
passed -Version explicitly, which skips Get-LatestReleasedVersion entirely.
Neither touches the network -- one points at a closed port, the other at an
in-process HttpListener stub that provokes both failure branches at once.

Fixes #27

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012betVHk3gFcj5RYkEe4vrm
@matt-edmondson
matt-edmondson merged commit 6568ed3 into main Sep 26, 2026
3 checks passed
@matt-edmondson
matt-edmondson deleted the claude/exciting-albattani-fru842 branch September 26, 2026 00:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

update-sdks.ps1 aborts the whole run on the first unresolvable package instead of reporting all of them

2 participants