Skip to content

Add CommandOptions with a working directory for the spawned process - #43

Merged
matt-edmondson merged 1 commit into
mainfrom
feat/39-working-directory
Aug 19, 2026
Merged

Add CommandOptions with a working directory for the spawned process#43
matt-edmondson merged 1 commit into
mainfrom
feat/39-working-directory

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #39.

Problem

ProcessStartInfo.WorkingDirectory was never assigned, so every command inherited the host process's current directory. The workarounds available to callers were a tool-specific flag where one exists (git -C), mutating the process-global Environment.CurrentDirectory (not thread-safe, races with concurrent calls), or shelling out through cmd /c cd && ... (reintroduces the quoting problems the argument-vector overloads exist to avoid).

Approach

Rather than add a parameter to every overload — and again for environment variables in #40 — this introduces a CommandOptions record to carry process-shaping settings:

public sealed record CommandOptions
{
	public AbsoluteDirectoryPath? WorkingDirectory { get; init; }
	public Elevation Elevation { get; init; } = Elevation.Default;
}

Three new overloads on the argument-vector shape accept it. All fourteen existing overloads keep their signatures and delegate through it, so no existing call changes behaviour and future options cost no new overloads.

WorkingDirectory is an AbsoluteDirectoryPath rather than a string on purpose: a relative directory would have to be resolved against the caller's current directory, which is exactly the process-global state this option exists to stop callers depending on. Making it absolute renders that ambiguity unrepresentable rather than merely discouraged.

Dependency note

This adds ktsu.Semantics.Paths and ktsu.Semantics.Strings, matching how AppDataStorage, SingleAppInstance and Schema consume them (the SDK's KTSU0006 analyzer requires the Strings reference be explicit, since WeakString is declared there).

Worth being deliberate about the cost: on net5.0net7.0 and netstandard2.0/2.1 this pulls System.Text.Json, System.IO.Pipelines and System.Text.Encodings.Web in transitively, and those packages emit "doesn't support net5.0/6.0/7.0" build warnings — 10 of them, against a previously warning-free build. They are MSBuild warnings from targets files, not compiler warnings, so TreatWarningsAsErrors does not escalate them and the build stays green. The affected TFMs are all out of support, and netstandard2.1 already covers those consumers.

Testing

  • ExecuteAsyncShouldStartTheProcessInTheGivenWorkingDirectory — runs cmd /c cd / pwd in a per-test temp directory and compares the reported leaf (leaf rather than full path, so a symlinked temp directory as on macOS doesn't produce a spurious failure).
  • ExecuteAsyncShouldInheritTheCurrentDirectoryWhenNoWorkingDirectoryIsGiven — pins that an unset working directory leaves the previous behaviour untouched.
  • ExecuteAsyncShouldThrowArgumentNullExceptionWhenOptionsAreNull.

Full suite: 27 passed, 2 skipped (elevated tests self-skip to avoid a UAC prompt). Builds clean across all eight target frameworks.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LwKTWcmxe5mh6DsLRj5NGQ

…rocess

ProcessStartInfo.WorkingDirectory was never assigned, so every command
inherited the host process's current directory. Callers wanting to drive
a tool somewhere else were pushed onto a tool-specific flag where one
exists, onto mutating the process-global Environment.CurrentDirectory,
or onto shelling out through cmd /c cd, which reintroduces the quoting
problems the argument-vector overloads exist to avoid.

Introduce CommandOptions to carry process-shaping settings, and add
three overloads on the argument-vector shape that take it. All fourteen
existing overloads keep their signatures and delegate through it, so
nothing existing changes behaviour.

WorkingDirectory is an AbsoluteDirectoryPath rather than a string: a
relative directory would have to be resolved against the caller's
current directory, which is the process-global state the option exists
to stop callers depending on.

This adds ktsu.Semantics.Paths and ktsu.Semantics.Strings as package
references, matching how AppDataStorage, SingleAppInstance and Schema
consume them.

Fixes #39
@matt-edmondson
matt-edmondson force-pushed the feat/39-working-directory branch from b7b8478 to c99e717 Compare August 19, 2026 10:43
@sonarqubecloud

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit 9606d42 into main Aug 19, 2026
3 checks passed
@matt-edmondson
matt-edmondson deleted the feat/39-working-directory branch August 19, 2026 10:58
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.

No way to set the working directory of the spawned process

1 participant