Build the native library on VS 2022, not on whatever is latest - #7
Merged
Conversation
WinDevices.dll links the dynamic CRT. dumpbin on the published 0.1.1 shows MSVCP140.dll, VCRUNTIME140.dll and VCRUNTIME140_1.dll among its imports, because CMake defaults to /MD and nothing overrides it. The toolset that builds it therefore sets the minimum Visual C++ Redistributable every consumer's machine must carry: v14x runtimes are compatible forward, not backward. windows-latest silently became windows-2025-vs2026, so 0.1.1 was built with the VS 2026 toolset and shipped inside a package consumed by components that are built, and deployed, on VS 2022. It loaded during verification only because this workstation has a current redistributable installed, which a clean endpoint does not. Pin the three jobs that compile to windows-2022. The reporting job and the git-only version job keep windows-latest; neither produces a binary. Also drop the CMake 3.29 pin from increment-version.yaml, which would have failed the same way build-test.yaml did: install.cmd configures without a generator, and a CMake older than the image's Visual Studio falls back to NMake Makefiles. Static linking of the CRT would remove the redistributable question entirely, but it needs the C API checked for heap ownership crossing the DLL boundary first, so it is left to its own issue. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XnzCrAM46SttZnU6msznyN
Version Increment pushes its tag with GITHUB_TOKEN, and events created with that token deliberately do not start other workflows. The tag-push trigger therefore never fires for an automated release, and publishing had to be dispatched by hand after every version bump. Trigger on workflow_run instead. It fires when Version Increment finishes rather than from a token-created event, so no personal access token has to be stored to make the chain work. Two alternatives were rejected. Duplicating the publish steps inside increment-version.yaml would break Trusted Publishing, because the policy on nuget.org is bound to this workflow's file name. Converting this into a reusable workflow called from there splits the OIDC claim between workflow_ref and job_workflow_ref, and which of the two nuget.org validates is not something to discover through a live publish. Also: - the job is skipped unless the triggering run succeeded, since workflow_run fires on failure too - for workflow_run the checkout pins the triggering run's head_sha rather than the branch tip, which may already have moved - the publish decision moves into one gate step, so the three conditional steps that depend on it cannot drift apart Claude-Session: https://claude.ai/code/session_01XnzCrAM46SttZnU6msznyN Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
CodeQL flagged actions/untrusted-checkout at high severity against the checkout I added for the workflow_run path. It is right. A workflow_run job is privileged: it holds id-token: write and can exchange it for a nuget.org publishing token. This job then builds what it checks out, running cmake and dotnet over that tree. Pinning workflow_run.head_sha means executing code from a commit this workflow did not choose, in exactly the context that can publish a package. Version Increment is dispatch-only today, so reaching it needs write access, but the shape is an escalation path to the package feed and should not exist. Take the default checkout instead, which is the default branch and therefore trusted. Version Increment tags a commit on that branch, so the tag is present when this runs. If the branch has moved past the tag in between, the exact-tag guard refuses to publish rather than releasing a mislabelled package - the correct failure, and the reason that guard was added. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XnzCrAM46SttZnU6msznyN
3 tasks
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.
The measurement
dumpbin /dependentson the publishedWinDevicesNet 0.1.1native library:Nothing in the CMake configuration sets
CMAKE_MSVC_RUNTIME_LIBRARY, so the default/MDapplies and the library links the dynamic CRT.Why that matters here
The toolset that builds this DLL sets the minimum Visual C++ Redistributable every consumer's machine must carry. The v14x runtimes are compatible forward, not backward.
windows-latestsilently becamewindows-2025-vs2026. So0.1.1was built with the VS 2026 toolset and shipped inside a package intended for components that are built — and deployed — on VS 2022. It loaded during verification only because the verifying workstation happens to have a current redistributable installed. A clean endpoint, of the kind ADR 0015 pins the baseline to, does not.Change
Pin the three jobs that compile to
windows-2022:build-test.yamlbuildbuild-test.yamlsummaryincrement-version.yamlincrement-versionincrement-version.yamlbuild-and-releasepublish-nuget.yamlpackAlso drops the CMake 3.29 pin from
increment-version.yaml. It would have failed exactly asbuild-test.yamldid:install.cmdconfigures without a generator, and a CMake older than the image's Visual Studio falls back to NMake Makefiles and then reportsCMAKE_CXX_COMPILER not set. That job had not run since the image changed, so the breakage was latent.Deliberately not done here
Linking the CRT statically (
/MT) would remove the redistributable question altogether and make the package self-contained. It is the stronger long-term answer, but it changes heap ownership semantics across the DLL boundary: with a static CRT the library and its host no longer share a heap, so any allocation made on one side and freed on the other becomes a bug. The C API appears to use explicit create/destroy pairs, which would be safe — but that needs checking rather than assuming, so it belongs in its own issue.🤖 Generated with Claude Code
https://claude.ai/code/session_01XnzCrAM46SttZnU6msznyN