Bundle dotnet-script 2.0.1 and roll forward on targets without .NET 8 - #2135
Draft
NickJosevski wants to merge 2 commits into
Draft
Bundle dotnet-script 2.0.1 and roll forward on targets without .NET 8#2135NickJosevski wants to merge 2 commits into
NickJosevski wants to merge 2 commits into
Conversation
dotnet-script is framework-dependent and requests Microsoft.NETCore.App 8.0.0 with no rollForward, so the default Minor policy binds it to an 8.x runtime and nothing higher. On a target with no 8.x runtime it fails to launch with exit 150 before any script runs. Calamari itself is published self-contained and is unaffected - this is only the separate dotnet-script process. Applies "rollForward": "Major" by overwriting the extracted runtimeconfig with a copy held in source control, so the vendored zip stays byte-for-byte upstream and the policy stays reviewable as text that a future re-vendor cannot silently drop. Major rather than LatestMajor, which the decision doc recommended. LatestMajor regresses a target that works today: dotnet-script derives its generated csproj TFM from the loaded corelib, so rolling the host to 10 makes it emit net10.0, and on a box with the .NET 10 runtime but only the .NET 8 SDK the restore then fails with NETSDK1045. Measured in a container with runtimes 10.0.11 + 8.0.29 and SDK 8.0.423 only: no policy and Major both work on 8.0.29, LatestMajor exits 1. Major fixes the reported failure and moves nobody else. Also adds Octopus.Action.Script.CSharp.RollForward for a per-step override, set as DOTNET_ROLL_FORWARD only when the variable is non-empty. That outranks the runtimeconfig and is the only mechanism that reaches a dotnet-script the customer installed themselves, which Calamari prefers over the bundled copy. Verified end to end on a Hosted Windows worker through the real dotnet-script.cmd launch path: a runtimeconfig requesting framework 6.0.0 with no policy fails to launch, and the same config with rollForward Major starts on 8.0.27. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The zip is vendored byte-for-byte from the upstream GitHub release (sha256 6675357...e0e1e13, verifiable against the release asset). Its runtimeconfig is byte-identical to 1.6.0's, so the roll-forward policy in the previous commit is still needed and is unaffected by the bump. Takes upstream's 2.0 default of an isolated assembly load context, which fixes dotnet-script #763 - native NuGet assets (SQLite, SkiaSharp, Microsoft.Data.SqlClient) have never worked in C# script steps - and gives a script the package version it asked for instead of whichever version dotnet-script itself carries. The cost is that a type loaded via Assembly.LoadFrom is no longer reference-equal to the same type in the script's own closure, so Octopus.Action.Script.CSharp.DisableIsolatedLoadContext restores the old behaviour for a step that needs it. Also strips the 1.6.0 --isolated-load-context flag from a step's script parameters. 2.0 dropped that option, and dotnet-script forwards options it does not recognise into the *script's* argument list rather than rejecting them - measured on 1.6.0 and 2.0.1 alike, silently and with exit 0. Left in place the flag becomes Env.ScriptArgs[0] and shifts every real argument along by one, which would silently break the existing steps that pass it. Isolation is the default now, so dropping the flag preserves what those steps asked for. The two UsingIsolatedAssemblyLoadContext cases are replaced rather than updated. The old test asserted that isolation *off* fails outright, which held only because 1.6.0 bundled NuGet 6.10.0.107 - lower than the 6.10.1.5 the script pins, and the default load context refuses a downgrade while accepting an upgrade. 2.0.1 bundles 6.14.3.1, so the same collision resolves silently to the wrong assembly. The replacements assert the version binding in each direction, so they test the load context rather than an accident of which version is vendored. Separately shippable: dropping this commit leaves the roll-forward fix intact. Roslyn 4.11 -> 5.0.0-2.final also raises the C# ceiling from 13 to 14, which is a widening but a one-way door - a script using C# 14 will not compile if the bundle is ever rolled back. DotnetScriptFixture is 11/11 and DotnetScriptBootstrapperFixture plus DotnetScriptRuntimeConfigFixture are 26/26. DotnetScriptProxyFixture fails the same 4 of 11 as clean main, verified side by side. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
NickJosevski
force-pushed
the
nj/dotnet-script-201-rollforward-consolidated
branch
from
August 18, 2026 07:48
6fd826a to
a7a86e4
Compare
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.
Supersedes #2132, #2133 and #2134. They will close and were to help explore the space.
Problem
dotnet-scriptis framework-dependent.Microsoft.NETCore.App 8.0.0with norollForward.8.x runtimefails withexit 150before any script runs.Upgrading to 2.0.1 does not fix the launch failure.
The 2.0.1 release zip carries a runtimeconfig byte-identical to 1.6.0's.
Commit 1:
rollForward: MajorThe build overwrites the extracted runtimeconfig with a copy held in source control. The vendored zip stays byte-for-byte upstream. The policy stays reviewable as text.
Octopus.Action.Script.CSharp.RollForwardoverrides the policy per step viaDOTNET_ROLL_FORWARD.Runtimeconfig instead of a default env var (#2095) for four reasons:
dotnet-script.cmd.SilentProcessRunnerthen writes the dictionary over the inherited environment. A machine-levelDOTNET_ROLL_FORWARDis silently overridden.Why
Majorand notLatestMajordotnet-script derives its generated csproj TFM from the loaded runtime. Measured on a container with SDK 8 only and runtimes 8 + 10:
MajorLatestMajorNETSDK1045, even with zero package refsLatestMajorrolls the host to 10. The generated project becomesnet10.0. SDK 8 cannot restore anet10.0project.LatestMajortakes C# script steps away from a target that works today.Majorfixes the reported failure. On an SDK-10-only target: exit 150 before, runs on 10.0.10 after. Everywhere 8.x existsMajoris a no-op.Commit 2: dotnet-script 2.0.1
Vendored byte-for-byte from upstream, sha256
667535727b0660223cfb9dcfc240831d8ca2a2af8f2e98214ee556334e0e1e13.Isolation default. 2.0 makes the isolated assembly load context the default. Isolation makes packages with native binaries work (dotnet-script#763). A script also gets the package version it asked for. The cost: a type loaded via
Assembly.LoadFromis no longer reference-equal to the same type in the script's closure. This commit keeps upstream's default.Octopus.Action.Script.CSharp.DisableIsolatedLoadContextrestores pre-2.0 behaviour for a step that needs the old behaviour. This decision is the one thing worth a second opinion. Dropping the commit leaves the roll-forward fix intact.The legacy flag is stripped. 2.0 dropped
--isolated-load-context. dotnet-script forwards unrecognised options into the script's argument list with exit 0. Left in place, the flag becomesEnv.ScriptArgs[0]and shifts every real argument along by one. Existing test cases show customers pass the flag today. Calamari strips the flag and logs a verbose line.Rewritten isolation tests. The old
UsingIsolatedAssemblyLoadContext(False)asserted a load failure. The failure only held while the host bundled an olderNuGet.Protocolthan the script pinned. 2.0.1 bundles a newer copy. The default context accepts an upgrade and refuses a downgrade. The replacement tests assert the version binding in each direction:6.10.1.5isolated,6.14.3.1not.Verification
DotnetScriptFixture11/11. Bootstrapper and runtimeconfig fixtures 26/26. The proxy fixture fails the same 4 of 11 as cleanmain.dotnet-script.cmd: the runtimeconfig is honoured. A6.0.0request withMajorresolved to 8.0.27. The same request with no policy failed to launch.DOTNET_ROLL_FORWARD=Majoroutranked aLatestMajorruntimeconfig. An env-var default does leak into grandchild processes of a customer's script.Not addressed
dotnet restoreon every execution. Runtime-only targets have never worked (C# script (dotnet-script) steps require the .NET SDK docs#3333).🤖 Generated with Claude Code