Skip to content

Snapshot and resolve command details with ExecutionContext#261

Open
broken-circle wants to merge 1 commit into
swiftlang:mainfrom
broken-circle:feature/execution-context
Open

Snapshot and resolve command details with ExecutionContext#261
broken-circle wants to merge 1 commit into
swiftlang:mainfrom
broken-circle:feature/execution-context

Conversation

@broken-circle

@broken-circle broken-circle commented May 10, 2026

Copy link
Copy Markdown
Contributor

Partially addresses #222 by adding SubprocessError.executionContext, representing a snapshot of the executable, arguments, environment, and working directory configured at the time the error was thrown, along with values resolved at spawn time.

Design

A new public ExecutionContext struct contains properties representing the supplied inputs and their resolved values. Resolution happens at spawn time rather than when the error is inspected, so the resolved values reflect what the child actually received; the state from which they derive can change over the parent's lifetime. A few resolved values are purposefully absent or adjusted:

  • resolvedExecutable is nil on Windows when the executable is given by name: CreateProcessW() performs the search internally and does not report the path it used, and Subprocess does not issue a separate post-spawn lookup to recover it. Path-based executables report a path on all platforms.
  • resolvedEnvironment is nil for environments given as raw bytes (no faithful String representation), and on Windows may include a PATH entry that Subprocess adds so the child can locate its DLLs.
  • resolvedWorkingDirectory is the configured directory when one is set, and otherwise the parent's working directory captured at spawn.

The context is attached in Configuration.run(). A failure during spawn is caught there with the configured inputs only, since no resolved values exist yet; any error surfacing after a successful spawn propagates back through the same function and is caught with the resolved context. Every error that leaves a run() call passes through one of these two points, so StandardInputWriter, SubprocessOutputSequence, and the collected output types need no awareness of ExecutionContext. Only SubprocessError is wrapped, so an error thrown from the caller's own body closure propagates unchanged. executionContext is nil only for errors observed outside a run() call, such as Execution-level process monitoring.

Testing

Nine existing tests in IntegrationTests that asserted on SubprocessError equality have been updated to directly assert on the relevant properties, since the addition of executionContext caused the type-level comparison to fail.

ProcessMonitoringTests.testInvalidProcessIdentifier() has been extended to verify that Execution-level errors carry no executionContext.

Future directions

#222 also requests including the exit code/signal and captured standard input/output on the error. Both involve scoping and design questions distinct from the snapshot (for example, whether the captured streams should be included verbatim or truncated). I'll address these in follow-up PRs.

@jakepetroules

Copy link
Copy Markdown
Contributor

The values represent the caller's inputs, not a resolved snapshot. For example, if Environment.inherit was used, then ExecutionContext.environment reflects .inherit, not a snapshot of the parent process's environment variables.

The state used to derive the resolved values can change throughout the lifetime of a process, and so I think it's important we surface the resolved values at the time of the error.

Perhaps the executionContext could have both "unresolved" and "resolved" variants of the executable and environment -- depending on where the error was thrown from, the "resolved" versions might be unavailable and therefore nil, but we should provide them to the caller if we have them.

@broken-circle

broken-circle commented May 11, 2026

Copy link
Copy Markdown
Contributor Author

@jakepetroules For sure, resolved environment values can be added on all platforms, and resolved executables are straightforward on Unix.

Windows has some hurdles for the resolved executable when the caller passes executable(_:) and we go through the built-in executable search of CreateProcessW and CreateProcessWithLogonW. Neither returns the resolved path, and PROCESS_INFORMATION carries only handles and IDs. We could call QueryFullProcessImageNameW post-spawn to recover it, but that's an extra syscall and point of failure. An alternative would be to leave resolvedExecutable as nil in that specific case and implement the post-spawn lookup if there's demand for it. Callers that pass path(_:) or go through the executablePathOverride slow path would still get resolved values as on Unix.

Do you want to resolve the working directory too? If the provided working directory was nil, we'd resolve to the parent's CWD at spawn. That's an extra syscall per spawn, but it would be consistent with the other resolutions.

Were you thinking these features should be part of this PR, or as a follow-up? I'd go with a separate PR, but I'm good with either.

Update: Rebased this onto main and updated the design to fit the refactored API. Added resolution of the executable, environment, and working directory.

@broken-circle broken-circle force-pushed the feature/execution-context branch from 23c0fe5 to e09077a Compare May 30, 2026 14:17
@broken-circle broken-circle marked this pull request as ready for review May 30, 2026 14:21
@broken-circle broken-circle requested a review from iCharlesHu as a code owner May 30, 2026 14:21
@broken-circle broken-circle changed the title Add ExecutionContext to snapshot command details Snapshot and resolve command details with ExecutionContext May 30, 2026
@jakepetroules

Copy link
Copy Markdown
Contributor

This is also an API change. I added the keyword. @iCharlesHu should this also be added to the "post-1.0" milestone?

@broken-circle broken-circle force-pushed the feature/execution-context branch from e09077a to 231543e Compare June 3, 2026 14:20
@broken-circle

broken-circle commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

I rebased this onto main to capture the FreeBSD CI fix #290. If we mark it post-1.0, I'll rebase periodically to keep it free of conflicts.

@broken-circle broken-circle force-pushed the feature/execution-context branch from 231543e to 1dd1333 Compare June 3, 2026 18:55
`SubprocessError` currently surfaces what went wrong via `code` and
`underlyingError`, but not what was being run.

Add `SubprocessError.executionContext`, representing a snapshot of the
executable, arguments, environment, and working directory configured at
the time the error was thrown, along with values resolved at spawn time.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants