Skip to content

Commit b3bee0e

Browse files
dotnet: reject per-client WorkingDirectory with in-process transport
Mirror the Node/Rust behavior: the in-process (FFI) host spawns the worker without a working-directory parameter, so a per-client WorkingDirectory can't be honored. Fail loud (ArgumentException) instead of silently ignoring it, consistent with the existing Environment/Telemetry in-process guards. Update the E2E harness to point this process's cwd at the desired directory for in-process tests (the FFI worker inherits it at spawn) and restore it after each test via InProcessEnvIsolation, matching sdkTestContext.ts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a96d1a82-c80e-40f1-a3df-a9708429b68b
1 parent edbe6c6 commit b3bee0e

3 files changed

Lines changed: 48 additions & 1 deletion

File tree

dotnet/src/Client.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,17 @@ private static void ValidateEnvironmentOptions(CopilotClientOptions options, Run
237237
nameof(options));
238238
}
239239

240+
if (options.WorkingDirectory is not null)
241+
{
242+
throw new ArgumentException(
243+
$"{nameof(CopilotClientOptions)}.{nameof(CopilotClientOptions.WorkingDirectory)} is not supported with " +
244+
$"{nameof(RuntimeConnection)}.{nameof(RuntimeConnection.ForInProcess)}(): the in-process transport hosts " +
245+
"the native runtime in the shared host process and spawns the worker without a working-directory " +
246+
"parameter, so a per-client working directory cannot be honored in-process. Use a child-process " +
247+
"transport, or set the process working directory before creating the client.",
248+
nameof(options));
249+
}
250+
240251
return;
241252
}
242253

dotnet/test/Harness/E2ETestContext.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,9 +244,17 @@ public CopilotClient CreateClient(
244244
{
245245
options ??= new CopilotClientOptions();
246246

247-
options.WorkingDirectory ??= WorkDir;
248247
options.Logger ??= Logger;
249248

249+
// Resolve the working directory the worker should run in. Child-process and
250+
// URI transports take it as a per-client option; the in-process transport
251+
// rejects a per-client WorkingDirectory (the native host spawns the worker
252+
// without a cwd parameter), so — mirroring the Node/Rust harnesses — we point
253+
// THIS process's cwd at the desired directory before the worker spawns and
254+
// clear the per-client option. InProcessEnvIsolationAttribute.After restores
255+
// the cwd after the test.
256+
var desiredWorkingDirectory = options.WorkingDirectory ?? WorkDir;
257+
250258
// Tests must supply environment via the 'environment' parameter, which the
251259
// harness routes to the right place per transport (the connection for
252260
// child-process transports, the host process for in-process). Setting
@@ -300,12 +308,24 @@ public CopilotClient CreateClient(
300308
{
301309
InProcessEnvIsolation.Apply(name, value);
302310
}
311+
312+
// A per-client WorkingDirectory is rejected in-process; instead point this
313+
// process's cwd at the desired directory so the worker inherits it at spawn
314+
// (restored after the test by InProcessEnvIsolationAttribute).
315+
options.WorkingDirectory = null;
316+
InProcessEnvIsolation.SetWorkingDirectory(desiredWorkingDirectory);
303317
}
304318
else if (options.Connection is ChildProcessRuntimeConnection child)
305319
{
306320
// Child-process transport: hand the environment to the spawned child
307321
// via the connection, where per-client environment is coherent.
308322
child.Environment = env;
323+
options.WorkingDirectory = desiredWorkingDirectory;
324+
}
325+
else
326+
{
327+
// URI / existing-runtime transport: per-client WorkingDirectory applies normally.
328+
options.WorkingDirectory = desiredWorkingDirectory;
309329
}
310330

311331
// Auto-inject auth token unless connecting to an existing runtime via URI.

dotnet/test/Harness/InProcessEnvIsolation.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ internal static class InProcessEnvIsolation
2222
// Captured at load, before any fixture/test mutates env.
2323
private static readonly Dictionary<string, string?> s_ambient = CaptureEnvironment();
2424

25+
// The process working directory captured at load, restored after each test so an
26+
// in-process test that repoints the cwd (the FFI worker inherits it at spawn)
27+
// can't leak that change into the next test.
28+
private static readonly string s_ambientCwd = Directory.GetCurrentDirectory();
29+
2530
// Runs at assembly load so the ambient env is snapshotted before the shared
2631
// fixture mirrors per-test env onto the process. Justifies suppressing CA2255.
2732
#pragma warning disable CA2255 // ModuleInitializer discouraged in libraries; intentional in this test harness.
@@ -56,8 +61,19 @@ public static void NeutralizeAmbientCredentials()
5661
}
5762
}
5863

64+
// Points the process working directory at the given path so the in-process FFI
65+
// worker inherits it at spawn (the native host has no per-client cwd parameter).
66+
// RestoreAmbient() returns the process to its load-time cwd after the test.
67+
public static void SetWorkingDirectory(string path) =>
68+
Directory.SetCurrentDirectory(path);
69+
5970
public static void RestoreAmbient()
6071
{
72+
if (!string.Equals(Directory.GetCurrentDirectory(), s_ambientCwd, StringComparison.Ordinal))
73+
{
74+
Directory.SetCurrentDirectory(s_ambientCwd);
75+
}
76+
6177
foreach (DictionaryEntry entry in Environment.GetEnvironmentVariables())
6278
{
6379
var name = (string)entry.Key;

0 commit comments

Comments
 (0)