diff --git a/tests/OpenCode.Sdk.Tests/Hosting/Launcher/OpenCodeServerSpawnRequestTests.cs b/tests/OpenCode.Sdk.Tests/Hosting/Launcher/OpenCodeServerSpawnRequestTests.cs
new file mode 100644
index 00000000..15d454a2
--- /dev/null
+++ b/tests/OpenCode.Sdk.Tests/Hosting/Launcher/OpenCodeServerSpawnRequestTests.cs
@@ -0,0 +1,191 @@
+using System.ComponentModel;
+using NSubstitute;
+using OpenCode.Sdk.Internal;
+using OpenCode.Sdk.Tests.Support;
+using TUnit.Assertions.Enums;
+
+namespace OpenCode.Sdk.Tests.Hosting.Launcher;
+
+/// The standalone door's pre-spawn refusals and the request it hands to the host's strategy.
+public sealed class OpenCodeServerSpawnRequestTests
+{
+ private static string Command => OperatingSystem.IsWindows() ? @"C:\oc\opencode.exe" : "/opt/oc/opencode";
+
+ /// A rooted executable and a leading argument are both checked before spawn, without pinning #401's parameter name.
+ [Test]
+ [Arguments(true)]
+ [Arguments(false)]
+ public async Task StartAsync_Should_Refuse_A_Nul_In_A_Command_Entry_Before_Spawn(bool executable)
+ {
+ var spawn = new RecordingSpawn();
+ var options = new OpenCodeServerOptions { Command = executable ? [Command + "\0", "serve"] : [Command, "serve\0"] };
+
+ var exception = await Assert.That(async () => await OpenCodeServer.StartWithSeamsAsync(options, Seams(spawn), CancellationToken.None))
+ .Throws();
+
+ await Assert.That(exception!.Message).IsEqualTo(new ArgumentException(
+ "OpenCodeServerOptions.Command entries cannot contain NUL.", exception.ParamName).Message);
+ await Assert.That(spawn.Calls).IsEqualTo(0);
+ }
+
+ /// Neither an environment name nor its value may be silently cut at a NUL by native marshalling.
+ [Test]
+ [Arguments(true)]
+ [Arguments(false)]
+ public async Task StartAsync_Should_Refuse_A_Nul_In_An_Environment_Entry_Before_Spawn(bool name)
+ {
+ var spawn = new RecordingSpawn();
+ var options = new OpenCodeServerOptions
+ {
+ Command = [Command],
+ Environment = new Dictionary(StringComparer.Ordinal) { [name ? "ENTRY\0" : "ENTRY"] = name ? "value" : "value\0" },
+ };
+ var seams = Seams(spawn);
+
+ var exception = await Assert.That(async () => await OpenCodeServer.StartWithSeamsAsync(options, seams, cancellationToken: CancellationToken.None))
+ .Throws();
+
+ await Assert.That(exception!.Message).IsEqualTo(new ArgumentException(
+ "OpenCodeServerOptions.Environment names and values cannot contain NUL.", exception.ParamName).Message);
+ await Assert.That(spawn.Calls).IsEqualTo(0);
+ }
+
+ /// The launcher tail follows every supplied argument; Windows starts that exact command suspended.
+ [Test]
+ public async Task StartAsync_Should_Append_The_Standalone_Arguments_After_The_Supplied_Arguments()
+ {
+ var spawn = new RecordingSpawn();
+ var options = new OpenCodeServerOptions { Command = [Command, "serve", "two words", "quoted\"argument"] };
+
+ _ = await Assert.That(async () => await OpenCodeServer.StartWithSeamsAsync(options, Seams(spawn), CancellationToken.None))
+ .Throws();
+
+ string[] argv = [Command, "serve", "two words", "quoted\"argument", "--stdio", "--port", "0"];
+ if (OperatingSystem.IsWindows())
+ {
+ await Assert.That(spawn.WindowsRequest!.ApplicationPath).IsEqualTo(Command);
+ await Assert.That(spawn.WindowsRequest.CommandLine).IsEqualTo(
+ """C:\oc\opencode.exe serve "two words" "quoted\"argument" --stdio --port 0""");
+ await Assert.That(spawn.WindowsRequest.StartSuspended).IsTrue();
+ }
+ else
+ {
+ await Assert.That(new[] { spawn.PosixRequest!.ExecutablePath }.Concat(spawn.PosixRequest.Arguments))
+ .IsEquivalentTo(argv, CollectionOrdering.Matching);
+ }
+ }
+
+ /// Each start injects a fresh base64url lease credential, never the caller's replacement.
+ [Test]
+ public async Task StartAsync_Should_Inject_A_Fresh_Base64Url_Password_Over_The_Callers_Entry()
+ {
+ var spawn = new RecordingSpawn();
+ var seams = Seams(spawn);
+ const string callerPassword = "caller-password";
+ var options = new OpenCodeServerOptions
+ {
+ Command = [Command],
+ Environment = new Dictionary(StringComparer.Ordinal) { ["OPENCODE_PASSWORD"] = callerPassword },
+ };
+
+ _ = await Assert.That(async () => await OpenCodeServer.StartWithSeamsAsync(options, seams, CancellationToken.None))
+ .Throws();
+ var first = spawn.Environment["OPENCODE_PASSWORD"];
+ _ = await Assert.That(async () => await OpenCodeServer.StartWithSeamsAsync(options, seams, CancellationToken.None))
+ .Throws();
+ var second = spawn.Environment["OPENCODE_PASSWORD"];
+
+ await Assert.That(first).Matches("^[A-Za-z0-9_-]{43}$");
+ await Assert.That(second).Matches("^[A-Za-z0-9_-]{43}$");
+ await Assert.That(second).IsNotEqualTo(first);
+ await Assert.That(first).IsNotEqualTo(callerPassword);
+ await Assert.That(second).IsNotEqualTo(callerPassword);
+ }
+
+ /// Only the host's literal 1 inherits stderr; the ownership lease and readiness stream remain pipes.
+ [Test]
+ [Arguments("1", true)]
+ [Arguments("true", false)]
+ public async Task StartAsync_Should_Inherit_Stderr_Only_For_The_Hosts_Print_Logs_Switch(string printLogs, bool inherit)
+ {
+ var spawn = new RecordingSpawn();
+ var host = new Dictionary(StringComparer.Ordinal) { ["OPENCODE_PRINT_LOGS"] = printLogs };
+ var options = new OpenCodeServerOptions { Command = [Command] };
+ var expected = inherit ? ChildStreamRoute.Inherit : ChildStreamRoute.Pipe;
+
+ _ = await Assert.That(async () => await OpenCodeServer.StartWithSeamsAsync(options, Seams(spawn, host), CancellationToken.None))
+ .Throws();
+
+ if (OperatingSystem.IsWindows())
+ {
+ await Assert.That(spawn.WindowsRequest!.StandardStreams!.Input).IsEqualTo(ChildStreamRoute.Pipe);
+ await Assert.That(spawn.WindowsRequest.StandardStreams.Output).IsEqualTo(ChildStreamRoute.Pipe);
+ await Assert.That(spawn.WindowsRequest.StandardStreams.Error).IsEqualTo(expected);
+ }
+ else
+ {
+ await Assert.That(spawn.PosixRequest!.StandardInput).IsEqualTo(ChildStreamRoute.Pipe);
+ await Assert.That(spawn.PosixRequest.StandardOutput).IsEqualTo(ChildStreamRoute.Pipe);
+ await Assert.That(spawn.PosixRequest.StandardError).IsEqualTo(expected);
+ }
+ }
+
+ /// A spawn failure names a meaningful directory, while a blank directory is passed as null and omitted.
+ [Test]
+ [Arguments(true)]
+ [Arguments(false)]
+ public async Task StartAsync_Should_Name_The_Working_Directory_Only_When_It_Is_Not_Blank(bool directory)
+ {
+ var spawn = new RecordingSpawn();
+ var work = OperatingSystem.IsWindows() ? @"C:\work" : "/work";
+ var options = new OpenCodeServerOptions { Command = [Command], WorkingDirectory = directory ? work : " " };
+
+ var exception = await Assert.That(async () => await OpenCodeServer.StartWithSeamsAsync(options, Seams(spawn), CancellationToken.None))
+ .Throws();
+
+ await Assert.That(exception!.Message).IsEqualTo(directory
+ ? $"Failed to start the server command '{Command}' in the working directory '{work}'."
+ : $"Failed to start the server command '{Command}'.");
+ await Assert.That(OperatingSystem.IsWindows() ? spawn.WindowsRequest!.WorkingDirectory : spawn.PosixRequest!.WorkingDirectory)
+ .IsEqualTo(directory ? work : null);
+ }
+
+ private static LauncherSeams Seams(RecordingSpawn spawn, IReadOnlyDictionary? host = null) => new()
+ {
+ WindowsSpawn = spawn,
+ Spawn = spawn,
+ Job = Substitute.For(),
+ WindowsExits = Substitute.For(),
+ TreeKill = Substitute.For(),
+ Signals = Substitute.For(),
+ Exits = Substitute.For(),
+ Time = new ManualTimeProvider(),
+ HostEnvironment = host ?? new Dictionary(StringComparer.Ordinal),
+ };
+
+ /// Records the host strategy's request and refuses to create a process.
+ private sealed class RecordingSpawn : IPosixSpawn, IWindowsSpawn
+ {
+ public int Calls { get; private set; }
+
+ public PosixSpawnRequest? PosixRequest { get; private set; }
+
+ public WindowsSpawnRequest? WindowsRequest { get; private set; }
+
+ public IReadOnlyDictionary Environment => WindowsRequest?.Environment ?? PosixRequest!.Environment;
+
+ public PosixSpawnedChild Spawn(PosixSpawnRequest request)
+ {
+ Calls++;
+ PosixRequest = request;
+ throw new Win32Exception(2);
+ }
+
+ public WindowsSpawnedChild Spawn(WindowsSpawnRequest request)
+ {
+ Calls++;
+ WindowsRequest = request;
+ throw new Win32Exception(2);
+ }
+ }
+}
diff --git a/tests/OpenCode.Sdk.Tests/Hosting/Launcher/WindowsServerChildTests.cs b/tests/OpenCode.Sdk.Tests/Hosting/Launcher/WindowsServerChildTests.cs
new file mode 100644
index 00000000..3b82d817
--- /dev/null
+++ b/tests/OpenCode.Sdk.Tests/Hosting/Launcher/WindowsServerChildTests.cs
@@ -0,0 +1,145 @@
+using System.ComponentModel;
+using System.Diagnostics;
+using Microsoft.Win32.SafeHandles;
+using NSubstitute;
+using NSubstitute.ExceptionExtensions;
+using OpenCode.Sdk.Internal;
+using OpenCode.Sdk.Tests.Harness;
+using OpenCode.Sdk.Tests.Support;
+using Testably.Abstractions;
+
+namespace OpenCode.Sdk.Tests.Hosting.Launcher;
+
+/// The Windows strategy's spawn and adoption failures, including the running child it must end.
+[NotInParallel(ParallelConstraintKeys.ServerProcess)]
+public sealed class WindowsServerChildTests
+{
+ private static readonly RealFileSystem FileSystem = new();
+ private static readonly TimeSpan Bound = TimeSpan.FromSeconds(30);
+
+ /// A native spawn failure preserves its cause and names the resolution and directory outside the command quotes.
+ [Test]
+ [Arguments(true)]
+ [Arguments(false)]
+ public async Task SpawnAsync_Should_Name_The_Command_And_Preserve_The_Spawn_Failure(bool details)
+ {
+ var seams = Seams();
+ var failure = new Win32Exception(2);
+ seams.WindowsSpawn.Spawn(Arg.Any()).Throws(failure);
+ var start = Start(new ResolvedExecutable("opencode", details ? @"C:\tools\opencode.exe" : "opencode", IsBatchScript: false)) with
+ { WorkingDirectory = details ? @"C:\work" : null };
+
+ var exception = await Assert.That(async () => await WindowsServerChild.SpawnAsync(start, seams))
+ .Throws();
+
+ await Assert.That(exception!.Message).IsEqualTo(details
+ ? @"Failed to start the server command 'opencode' (resolved to 'C:\tools\opencode.exe') in the working directory 'C:\work'."
+ : "Failed to start the server command 'opencode'.");
+ await Assert.That(exception.InnerException).IsSameReferenceAs(failure);
+ }
+
+ /// A watch creation failure ends the already-running child with code 1 and closes the handle it offered the watch; the session leak guard proves no watch is left.
+ [Test]
+ [Timeout(120_000)]
+ public async Task SpawnAsync_Should_End_The_Child_When_Its_Exit_Cannot_Be_Watched(CancellationToken cancellationToken)
+ {
+ if (!OperatingSystem.IsWindows())
+ {
+ BranchReport.Print("POSIX — the Windows watch adoption path requires a Windows process handle");
+ return;
+ }
+
+ await using var exits = new RefusingChildExit();
+ var seams = Seams(exits, exits);
+ var fixture = new ServiceFixtureCommand(FileSystem).Resolve();
+ var resolved = new ExecutableResolver(ExecutableSearchEnvironment.ForCurrentProcess()).Resolve(fixture[0]);
+ var executable = new ResolvedExecutable("fixture", resolved.Path, IsBatchScript: false);
+ var start = Start(executable) with { SuppliedArguments = [fixture[1], "idle"] };
+
+ var exception = await Assert.That(async () => await WindowsServerChild.SpawnAsync(start, seams))
+ .Throws();
+
+ await Assert.That(exception!.Message).IsEqualTo(
+ $"The server command 'fixture' (resolved to '{resolved.Path}') was started, but its exit could not be watched or its output read, so it was ended.");
+ await Assert.That(exception.InnerException).IsSameReferenceAs(exits.Failure);
+ await Assert.That(exits.Held).IsNotNull();
+ await Assert.That(await ProcessObservation.ObserveExitWithinAsync(exits.Held!, Bound, cancellationToken)).IsTrue();
+ await Assert.That(exits.Held!.ExitCode).IsEqualTo(1);
+ await Assert.That(exits.Watched!.IsClosed).IsTrue();
+ }
+
+ private static ServerChildStart Start(ResolvedExecutable executable) => new()
+ {
+ Executable = executable,
+ SuppliedArguments = [],
+ LauncherArguments = [],
+ Password = nameof(WindowsServerChildTests),
+ GracefulShutdownTimeout = TimeSpan.FromSeconds(3),
+ OnStandardOutput = static _ => { },
+ OnStandardError = static _ => { },
+ };
+
+ private static LauncherSeams Seams(IWindowsSpawn? spawn = null, IWindowsChildExit? exits = null) => new()
+ {
+ WindowsSpawn = spawn ?? Substitute.For(),
+ WindowsExits = exits ?? Substitute.For(),
+ Job = Substitute.For(),
+ Signals = Substitute.For(),
+ TreeKill = Substitute.For(),
+ Exits = Substitute.For(),
+ Time = new ManualTimeProvider(),
+ Spawn = Substitute.For(),
+ HostEnvironment = new Dictionary(StringComparer.OrdinalIgnoreCase),
+ };
+
+ /// Takes an independent observation handle before refusing to create any exit watch.
+ private sealed class RefusingChildExit : IWindowsChildExit, IWindowsSpawn, IAsyncDisposable
+ {
+ private readonly WindowsSpawn _spawn = new();
+
+ public Win32Exception Failure { get; } = new(6);
+
+ public Process? Held { get; private set; }
+
+ public SafeProcessHandle? Watched { get; private set; }
+
+ public WindowsSpawnedChild Spawn(WindowsSpawnRequest request)
+ {
+ var child = _spawn.Spawn(request);
+ try
+ {
+ Held = Process.GetProcessById(child.ProcessId);
+ _ = Held.Handle;
+ return child;
+ }
+ catch
+ {
+ // The strategy never saw this child, so nothing else would end it or close its handles.
+ _ = WindowsInterop.TerminateProcess(child.Process, 1);
+ child.MainThread?.Dispose();
+ child.StandardInput?.Dispose();
+ child.StandardOutput?.Dispose();
+ child.StandardError?.Dispose();
+ child.Process.Dispose();
+ throw;
+ }
+ }
+
+ public IWindowsExitWatch Watch(SafeProcessHandle process)
+ {
+ Watched = process;
+ throw Failure;
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ if (Held is { HasExited: false })
+ {
+ Held.Kill();
+ await Held.WaitForExitAsync();
+ }
+
+ Held?.Dispose();
+ }
+ }
+}
diff --git a/tests/OpenCode.Sdk.Tests/Hosting/Platform/Posix/PosixSignalNamesTests.cs b/tests/OpenCode.Sdk.Tests/Hosting/Platform/Posix/PosixSignalNamesTests.cs
index ff33288e..80c1b3e0 100644
--- a/tests/OpenCode.Sdk.Tests/Hosting/Platform/Posix/PosixSignalNamesTests.cs
+++ b/tests/OpenCode.Sdk.Tests/Hosting/Platform/Posix/PosixSignalNamesTests.cs
@@ -11,6 +11,20 @@ public sealed class PosixSignalNamesTests
{
public static IEnumerable StandardSignals() => Enumerable.Range(1, 31);
+ /// The host selects macOS's numbering only on macOS; Windows exposes the otherwise-unused Linux table.
+ [Test]
+ public async Task ForCurrentProcess_Should_Select_The_Hosts_Signal_Numbering()
+ {
+ if (OperatingSystem.IsMacOS())
+ {
+ await Assert.That(PosixSignalNames.ForCurrentProcess.NameOf(10)).IsEqualTo("SIGBUS");
+ }
+ else
+ {
+ await Assert.That(PosixSignalNames.ForCurrentProcess.NameOf(10)).IsEqualTo("SIGUSR1");
+ }
+ }
+
[Test]
[MethodDataSource(nameof(StandardSignals))]
public async Task NameOf_Should_Name_Every_Standard_Signal_On_Both_Systems(int number)
diff --git a/tests/OpenCode.Sdk.Tests/Hosting/Platform/Windows/WindowsExitWatchTests.cs b/tests/OpenCode.Sdk.Tests/Hosting/Platform/Windows/WindowsExitWatchTests.cs
index a0489db8..e968abbf 100644
--- a/tests/OpenCode.Sdk.Tests/Hosting/Platform/Windows/WindowsExitWatchTests.cs
+++ b/tests/OpenCode.Sdk.Tests/Hosting/Platform/Windows/WindowsExitWatchTests.cs
@@ -27,6 +27,7 @@ public sealed class WindowsExitWatchTests
[Timeout(120_000)]
[Arguments(AccessViolation)]
[Arguments(7)]
+ [Arguments(259)]
public async Task Exited_Should_Report_The_Code_In_Its_Full_32_Bits(int code, CancellationToken cancellationToken)
{
if (!OperatingSystem.IsWindows())