Commit Graph

171 Commits

Author SHA1 Message Date
viyatb-oai
82c7480c8f fix: keep relay staging lint clean
Co-authored-by: Codex noreply@openai.com
2026-06-09 17:34:31 -07:00
viyatb-oai
5f9bac19d0 fix: keep responder staging lint clean
Co-authored-by: Codex noreply@openai.com
2026-06-09 17:11:26 -07:00
viyatb-oai
8887eccef9 fix: remove fulfilled Noise lint expectations
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:51:24 -07:00
viyatb-oai
e206c191d6 fix: keep Noise relay runtime lint-clean
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:36:49 -07:00
viyatb-oai
73686bd7d9 fix: keep executor Noise layer lint-clean
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:18:19 -07:00
viyatb-oai
7a153b809a fix: inline Noise responder public key
Co-authored-by: Codex <noreply@openai.com>
2026-06-09 16:16:49 -07:00
viyatb-oai
fead5a9a12 fix: keep Noise executor transport lintable
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:16:49 -07:00
viyatb-oai
7f040af64e style: apply Noise relay formatting
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:16:49 -07:00
viyatb-oai
ddfa2544ba refactor: defer Noise remote opt-in
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:16:48 -07:00
viyatb-oai
8ae85753fc fix: harden Noise executor relay
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:16:48 -07:00
viyatb-oai
e7554548e0 fix: map Noise identity generation errors
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:16:48 -07:00
viyatb-oai
1db84eb913 refactor: introduce Noise responder with executor runtime
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:16:47 -07:00
viyatb-oai
2df71e6837 refactor: make Noise environment runtime landable
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:16:47 -07:00
viyatb-oai
3a92723378 feat: add Noise environment transport
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:16:46 -07:00
viyatb-oai
fa5ddce8d3 feat: add Noise rendezvous provider API
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:16:40 -07:00
viyatb-oai
742aeb4b60 test: cover Noise relay codec boundaries
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:16:35 -07:00
viyatb-oai
c8e987aaeb fix: keep Noise harness transport self-contained
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:16:34 -07:00
viyatb-oai
2488836aca refactor: keep direct Noise client independently landable
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:16:30 -07:00
viyatb-oai
6a163833f9 fix: keep Noise client error surface minimal
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:16:30 -07:00
viyatb-oai
9dbd96efa5 feat: expose Noise exec-server client API
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:16:30 -07:00
viyatb-oai
95d74efb58 fix: introduce Noise client wiring with its caller
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:16:29 -07:00
viyatb-oai
4cb5e54139 refactor: align Noise harness production boundary
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:16:29 -07:00
viyatb-oai
64ceb5cdf8 refactor: introduce Noise initiator with harness transport
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:16:29 -07:00
viyatb-oai
867e2d90fc fix: keep Noise harness transport self-contained
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:16:29 -07:00
viyatb-oai
6bee93157a feat: add Noise harness transport
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:16:28 -07:00
viyatb-oai
6197847e7a fix: annotate relay sequence test arguments
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:16:28 -07:00
viyatb-oai
da84288616 feat: add Noise relay codec state
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:16:27 -07:00
viyatb-oai
657fe01851 refactor: defer relay helpers to their callers
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:16:24 -07:00
viyatb-oai
0e4f4d0ac8 refactor: defer Noise reset construction to responder
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:16:23 -07:00
viyatb-oai
3ebabf7eef feat: add Noise relay handshake frames
Co-authored-by: Codex noreply@openai.com
2026-06-09 16:16:23 -07:00
viyatb-oai
fd62a24935 refactor: keep Noise foundation independently usable
Co-authored-by: Codex noreply@openai.com
2026-06-09 15:17:16 -07:00
viyatb-oai
df0bd43d1a feat: add Noise channel foundation
Co-authored-by: Codex noreply@openai.com
2026-06-09 15:17:16 -07:00
pakrym-oai
f2969f36e8 [codex] Handle Ctrl-C for non-TTY unified exec (#26734)
## Why

A long-running unified exec process started with `tty: false` could not
be interrupted via `write_stdin`: ordinary non-TTY stdin writes are
rejected once stdin is closed, but an exact U+0003 payload should still
map to a process interrupt. The interrupt should flow through the same
process lifecycle path as a real signal so Codex preserves
process-reported output and exit metadata instead of fabricating a
Ctrl-C exit code or tearing down the session early.

## What Changed

- Add `process/signal` to exec-server with `ProcessSignal::Interrupt`
and an empty response.
- Add a non-consuming `ProcessHandle::signal` path for spawned
processes; on Unix it sends SIGINT to the process group and leaves
terminate/hard-kill unchanged.
- Route non-TTY U+0003 `write_stdin` through `process.signal(...)`
instead of `terminate`, then let the normal post-write collection path
drain output and observe exit.
- Add exec-server coverage where a shell `trap INT` handler prints the
signal and exits with its own code.
- Add unified exec coverage where a `tty: false` process traps SIGINT,
emits output, and exits with its own code.

## Validation

- `just test -p codex-exec-server
exec_process_signal_interrupts_process`
- `just test -p codex-exec-server`
- `just test -p codex-core
write_stdin_ctrl_c_interrupts_non_tty_session`
2026-06-09 15:10:17 -07:00
pakrym-oai
6a6a5f925e [codex] Add environment shell info (#26480)
## Why

Shell detection needs to be available through the `Environment`
abstraction so callers can ask the selected local or remote environment
for shell metadata without adding a separate HTTP endpoint or parallel
info-source path. This keeps shell metadata shaped like the existing
environment-owned filesystem capability and lets remote environments
answer through exec-server JSON-RPC.

## What changed

- Added `environment/info` to the exec-server protocol/client/server and
exposed `Environment::info()`.
- Added local and remote environment info providers on `Environment`,
following the existing capability-provider pattern used for filesystem
access.
- Moved the shared shell detection logic into `codex-shell-command` and
kept core shell APIs as wrappers around that implementation.
- Returned shell metadata as `EnvironmentInfo { shell: ShellInfo }`
using the existing shell detection path.
- Added a remote environment test that calls `Environment::info()`
through an exec-server-backed environment.

## Validation

- `git diff --check`
- `just test -p codex-shell-command`
- `just test -p codex-core -E 'test(/shell::tests::/)'`\n- `just test -p
codex-exec-server environment`
2026-06-04 22:36:25 -07:00
starr-openai
53ac02356e exec-server: canonicalize bound filesystem paths (#25149)
## Summary
- add executor filesystem canonicalization as a bound-path operation
- route remote canonicalization through the exec-server filesystem RPC
surface
- keep path normalization attached to the filesystem that owns the path

## Stack
- 2/5 in the skills path authority stack extracted from
https://github.com/openai/codex/pull/25098
- follows merged https://github.com/openai/codex/pull/25121

## Validation
- `cd
/Users/starr/code/codex-worktrees/pr-25098-restack-review-pr1b/codex-rs
&& just fmt`
- Not run: tests/checks (not requested)
- GitHub CI pending on rewritten head
2026-06-01 11:53:31 -07:00
starr-openai
fb94703b21 exec-server: add environment path refs (#25121)
## Summary
- add public `codex_exec_server::EnvironmentPathRef`
- bind an absolute path to its owning executor filesystem
- keep path operations in the next review slice

## Stack
- 1/5 in the skills path authority stack extracted from
https://github.com/openai/codex/pull/25098

## Validation
- `cd /Users/starr/code/codex-worktrees/pr-25098-restack4/codex-rs &&
just fmt`
- GitHub CI pending on rewritten head
2026-06-01 10:55:52 -07:00
starr-openai
a717e4ef31 exec-server: preserve fs helper CoreFoundation env (#25118)
## Summary
- preserve macOS `__CF_USER_TEXT_ENCODING` when launching the sandboxed
fs helper
- keep the fs-helper env narrow; this adds only the CoreFoundation
startup var instead of copying the broader MCP stdio baseline
- add focused coverage that the helper keeps that var without admitting
`HOME`

## Diagnosis
The sandboxed fs helper is not launched like a normal child process.
Exec-server rebuilds its environment from an allowlist, then calls
`env_clear()` before re-execing Codex with `--codex-run-as-fs-helper`.
That helper dispatches before the normal Codex startup path and only
needs to boot a small Tokio runtime, read one JSON request from stdin,
perform the direct filesystem operation, and write one JSON response.

The reported macOS hang sampled the helper before Rust main, in
CoreFoundation initialization while resolving the default text encoding:
`_CFStringGetUserDefaultEncoding -> getpwuid_r -> notify_register_check
-> bootstrap_look_up3 -> mach_msg2_trap`. The fs-helper allowlist kept
`PATH` and temp vars for runtime needs, but it dropped macOS
`__CF_USER_TEXT_ENCODING`. Other Codex subprocess launchers that
intentionally build a minimal Unix baseline, such as MCP stdio, already
preserve that variable.

My read is that stripping `__CF_USER_TEXT_ENCODING` forced this internal
helper down CoreFoundation's fallback user-lookup path, and that lookup
intermittently wedged on the affected machine before the helper could
read stdin or touch the target file. Preserving only this macOS startup
variable avoids that fallback without broadening the fs-helper
environment to shell-like vars such as `HOME`, `USER`, locale settings,
terminal settings, or proxy credentials.

Internal Slack thread omitted from the public PR body.

## Validation
- `cd codex-rs && just fmt`
- `git diff --check`
2026-05-29 12:20:17 -07:00
Eric Horacek
451b386442 [exec-server] Kill dropped filesystem helpers (#25116)
## Summary
- terminate sandbox filesystem helpers when the Tokio child handle is
dropped

## Why
A sandbox filesystem helper can stall during process startup before
reading stdin. If the owning async operation is cancelled or torn down,
the spawned helper should not remain running as an orphaned process.

Setting `kill_on_drop(true)` gives the filesystem helper the cleanup
behavior that Tokio child processes otherwise do not enable by default.

This intentionally does not add a timeout. It does not detect or recover
an active hung file edit while the owning future remains alive. A more
precise startup-health mechanism can be handled separately.

## Validation
- `just test -p codex-exec-server` (186 tests passed; benchmark smoke
passed)
- `just fmt`
- `just fix -p codex-exec-server`
- `git diff --check`
2026-05-29 11:40:44 -07:00
xl-openai
e29bbb5368 feat: Add focused diagnostics for MCP HTTP send failures (#25013)
Adds failure-only logging for MCP streamable HTTP post_message calls and
the underlying reqwest send path, capturing the MCP method/request id,
endpoint shape, auth-header presence, timeout/connect classification,
and sanitized error source chain without logging headers, bodies,
tokens, or full URLs.
2026-05-29 10:09:33 -07:00
viyatb-oai
a027135bc6 fix(exec-server): reject websocket requests with Origin headers (#24947)
## Why

`codex exec-server` has a local WebSocket listener, but it did not apply
the same browser-origin request handling as the `app-server` WebSocket
transport. Requests that carry an `Origin` header should not be upgraded
by this local transport, keeping both local WebSocket servers consistent
and avoiding unexpected browser-initiated connections.

## What changed

- Added an Axum middleware guard in
`codex-rs/exec-server/src/server/transport.rs` that returns `403
Forbidden` for requests carrying an `Origin` header.
- Added an integration test in `codex-rs/exec-server/tests/websocket.rs`
that covers rejection of an `Origin`-bearing WebSocket handshake.
- Kept ordinary WebSocket clients unchanged: existing no-`Origin`
initialization and process behavior remains covered by the crate tests.

## Validation

- `just test -p codex-exec-server` test phase (`186 passed`; run outside
the parent macOS sandbox so nested sandbox tests can execute)
- `just clippy -p codex-exec-server`
2026-05-28 14:44:14 -07:00
Steve Coffey
c57dee98b7 Allow API-key auth for remote exec-server registration (#24666)
## Overview
Allow remote `codex exec-server` registration to use existing API-key
auth while restricting where those credentials can be sent.

- Accept `CodexAuth::ApiKey` for the normal `--remote` registration
path.
- Restrict API-key remote registration to HTTPS `openai.com` and
`openai.org` hosts and subdomains, with explicit HTTP loopback support
for local development.
- Disable registry registration redirects so credentials cannot be
forwarded to an unvalidated destination.
- Retain `--use-agent-identity-auth` as the explicit Agent Identity
path.
- Document remote registration using `CODEX_API_KEY`.

## Big picture
Callers can now provide an API key directly to `exec-server`
registration without first establishing ChatGPT login state:

```sh
CODEX_API_KEY="$OPENAI_API_KEY" \
codex exec-server \
  --remote "https://<host>.openai.org/api" \
  --environment-id "$ENVIRONMENT_ID"
```

## Validation
- `cargo fmt --all` (`just fmt` is not installed on this host)
- `cargo test -p codex-cli -p codex-exec-server`
2026-05-27 21:17:38 +00:00
Adam Perry @ OpenAI
cca1e0ba1d Uprev Rust toolchain pins to 1.95.0 (#24684)
## Summary
- Bump the workspace Rust toolchain from `1.93.0` to `1.95.0` across
Cargo, Bazel, CI, release workflows, devcontainers, and the Codex
environment config.
- Refresh `MODULE.bazel.lock` so the Bazel Rust toolchain artifacts
match the new version.
- Leave purpose-specific toolchains unchanged, including the
`argument-comment-lint` nightly and the upstream `rusty_v8` `1.91.0`
build pin.
- Includes fixes for new lints from `just fix` and a few codex-authored
fixes for lints without a suggestion.
2026-05-26 20:59:47 -07:00
starr-openai
de80fa6e31 Reconnect disconnected exec-server websocket clients with fresh sessions (#23867)
## Summary
- replace the one-shot lazy remote exec-server cache with a
lock-protected current client
- when the cached websocket client is already disconnected, create one
fresh websocket client/session on the next `get()`
- keep existing disconnect failure behavior for old process sessions and
HTTP body streams; do not add session resume or request retry

## Why
The prior PR direction was trying to grow into session restore: resume
the old `session_id`, preserve existing process handles, and add
reconnect retry policy. That is more machinery than we want for this
slice.

For now, the useful minimum is simpler: later fresh remote operations
should not be stuck behind a dead cached websocket client, but anything
already attached to the dead connection should fail loudly through the
existing disconnect path. The server already has detached-session
cleanup via its existing TTL, so this PR does not need to add
client-side session preservation.

## What Changed
- `LazyRemoteExecServerClient::get()` now keeps the current concrete
client in a small mutex-protected cache plus one async connect lock.
- If that cached client is still connected, `get()` returns it.
- If that cached websocket client has observed the transport close,
`get()` creates a brand-new websocket client with a brand-new
exec-server session and replaces the cache.
- If that cached client is stdio-backed, behavior stays one-shot: the
dead client is returned and later work surfaces the existing disconnect
error.
- No `resume_session_id`, backoff, request replay, or existing
`RemoteExecProcess` rebinding is added here.
- Added focused websocket coverage that proves two concurrent `get()`
calls after disconnect share one fresh replacement client/session.
2026-05-21 18:43:45 +02:00
richardopenai
000bf5ce6d Migrate exec-server remote registration to environments (#23633)
## Summary
- migrate exec-server remote registration naming from executor to
environment
- align CLI, public Rust exports, registry error messages, and relay
test fixtures with the environment registry contract
- keep the live registration path and response model consistent with
`/cloud/environment/{environment_id}/register`

## Verification
- `cargo test -p codex-exec-server
remote::tests::register_environment_posts_with_auth_provider_headers
--manifest-path /Users/richardlee/code/codex/codex-rs/Cargo.toml`
- `cargo test -p codex-exec-server --test relay
multiplexed_remote_environment_routes_independent_virtual_streams
--manifest-path /Users/richardlee/code/codex/codex-rs/Cargo.toml`
- `cargo check -p codex-cli --manifest-path
/Users/richardlee/code/codex/codex-rs/Cargo.toml` (still running when PR
opened; will update after completion if needed)
2026-05-20 00:25:04 -07:00
starr-openai
83af3abc68 Refactor exec-server websocket pump (#23327)
## Why
Exec-server websocket handling had separate reader and writer tasks for
the same socket. That made websocket control-frame handling asymmetric:
the task reading frames could observe `Ping`, but the task allowed to
write frames was elsewhere. This PR moves each physical websocket onto
one always-running pump so the socket owner can handle application
frames and websocket control frames together.

## What changed
- Refactored direct exec-server websocket connections in `connection.rs`
to use one task that owns the websocket for outbound JSON-RPC, inbound
JSON-RPC, periodic keepalive pings, and `Ping` -> `Pong` replies.
- Refactored relay websocket handling in `relay.rs` the same way for
both the harness-side logical connection and the multiplexed executor
physical socket.
- Preserved the existing keepalive ownership policy: outbound direct
websocket clients still send periodic pings, inbound Axum accepts only
reply with pongs, and relay physical websocket endpoints keep their
existing periodic pings.
- Added focused websocket pump tests for ping/pong, binary JSON-RPC,
relay data, malformed relay text frames, and close/disconnect behavior.
- Reconnect behavior is intentionally left for a follow-up.

## Validation
- Devbox Bazel focused unit target:
- `//codex-rs/exec-server:exec-server-unit-tests
--test_filter='websocket_connection_|harness_connection_|multiplexed_executor_'`
2026-05-19 13:31:57 -07:00
starr-openai
5c43a64e2b Make local environment optional in EnvironmentManager (#23369)
## Summary
- make `EnvironmentManager` local environment/runtime paths optional
- simplify constructor surface around snapshot materialization
- rename local env accessors to `require_local_environment` /
`try_local_environment`

## Validation
- devbox Bazel build for touched crate surfaces
- `//codex-rs/exec-server:exec-server-unit-tests`
- `//codex-rs/app-server-client:app-server-client-unit-tests`
- filtered touched `//codex-rs/core:core-unit-tests` cases
2026-05-19 12:55:34 -07:00
starr-openai
64ead6a83a Add exec-server websocket keepalive (#23226)
## Summary
- send periodic websocket Ping frames from outbound exec-server
websocket clients
- cover direct exec-server websocket clients plus rendezvous
harness/executor websocket connections
- keep inbound axum-accepted exec-server websocket connections passive
- add focused keepalive coverage for direct and relay websocket paths

## Validation
- /Users/starr/code/openai/project/dotslash-gen/bin/bazel test
//codex-rs/exec-server:exec-server-unit-tests
--test_filter='websocket_connection_sends_keepalive_ping|harness_connection_sends_keepalive_ping|multiplexed_executor_sends_keepalive_ping'
- /Users/starr/code/openai/project/dotslash-gen/bin/bazel test
//codex-rs/exec-server:exec-server-relay-test
--test_filter=multiplexed_remote_executor_routes_independent_virtual_streams
2026-05-18 03:07:32 +00:00
Michael Zeng
b200dd1b6f exec-server: support auth-backed remote executor registration (#22769)
This updates remote `exec-server` registration to use normal Codex auth
instead of a registry-issued credential. The registry request is built
from the existing auth-provider path, which preserves the biscuit-only
registry contract introduced in
[openai/openai#924101](https://github.com/openai/openai/pull/924101)
while removing the old remote registry bearer env var and its direct
transport assumptions.

The default remote flow uses persisted ChatGPT auth from the normal
Codex config/storage path. This PR also includes the containerized Agent
Identity path needed by
[openai/openai#924260](https://github.com/openai/openai/pull/924260):
remote `exec-server` accepts `--allow-agent-identity-auth`, permits
Agent Identity auth loaded from `CODEX_ACCESS_TOKEN` only when that flag
is present, and reuses the existing Agent task registration plus derived
`AgentAssertion` header generation. API-key auth remains unsupported,
and Agent Identity stays opt-in.

Validation performed beyond normal presubmit coverage:
- `cargo fmt --all --check`
- `cargo check -p codex-cli`
- `cargo test -p codex-exec-server`
- `cargo test -p codex-cli exec_server_agent_identity_auth_flag_`
- `cargo test -p codex-cli remote_exec_server_auth_mode_`

I also attempted `cargo test -p codex-cli`. The new CLI tests passed
inside that run, but the suite ended on an unrelated local
marketplace-state failure in
`plugin_list_excludes_unconfigured_repo_local_marketplaces`.
2026-05-16 12:48:28 -07:00
starr-openai
255748638c Fix remote environment test fixtures (#22572)
## Why
The Docker remote-env coverage was failing before it reached the
behavior those tests are meant to exercise. The remote-aware test
fixture only registered the remote environment, so tests that
intentionally select both `local` and `remote` could not start a turn.
After that was fixed, two tests exposed stale fixtures: the approval
test was auto-approving under workspace-write, and the remote
`view_image` test was writing invalid PNG bytes.

## What Changed
- Added `EnvironmentManager::create_for_tests_with_local(...)` so tests
can keep the provider default while also selecting `local` explicitly.
- Updated `build_remote_aware()` to use that test-only manager when a
remote exec-server URL is present.
- Changed the remote apply-patch approval helper to use
`SandboxPolicy::new_read_only_policy()` so the test actually exercises
approval caching per environment.
- Replaced the hardcoded remote `view_image` PNG blob with the existing
`png_bytes(...)` helper so the test uses a valid image fixture.

## Validation
Ran these isolated Docker remote-env tests on the devbox with
`$remote-tests` setup:
-
`suite::remote_env::apply_patch_freeform_routes_to_selected_remote_environment`
-
`suite::remote_env::apply_patch_approvals_are_remembered_per_environment`
-
`suite::remote_env::apply_patch_intercepted_exec_command_routes_to_selected_remote_environment`
-
`suite::remote_env::exec_command_routes_to_selected_remote_environment`
- `suite::view_image::view_image_routes_to_selected_remote_environment`

All five pass.
2026-05-14 12:40:01 -07:00
Anton Panasenko
ac466c0dbd feat(exec-server): use protobuf relay frames (#22343)
## Why

Remote exec-server now needs one executor websocket to serve multiple
harness JSON-RPC sessions. Rendezvous routes by `stream_id`, and the
exec-server side needs to use the same stable relay frame contract
instead of a hand-rolled JSON shape.

The relay protocol also needs to make ownership boundaries clear:
harness and executor endpoints own sequencing, acks, retries, duplicate
suppression, segmentation, and reassembly; rendezvous only routes
frames.

## What Changed

- Add the checked-in `codex.exec_server.relay.v1.RelayMessageFrame`
proto plus generated prost bindings for `codex-exec-server`.
- Encode remote harness/executor relay traffic as binary protobuf
websocket frames while keeping local websocket JSON-RPC unchanged.
- Demux executor-side relay streams into independent
`ConnectionProcessor` sessions keyed by `stream_id`.
- Add a programmatic `RemoteExecutorConfig::with_bearer_token(...)`
constructor for non-CLI callers and integration tests.
- Add an integration test that starts the remote executor against a fake
registry/rendezvous websocket and verifies two virtual streams share one
executor websocket without cross-talk, including per-stream reset
behavior.
- Document the remote relay envelope, sequence ranges, `ack`/`ack_bits`,
and endpoint responsibilities in `exec-server/README.md`.

## Verification

- `cargo test -p codex-exec-server --test relay
multiplexed_remote_executor_routes_independent_virtual_streams --
--exact`
- `cargo test -p codex-exec-server --test relay`
- `cargo test -p codex-exec-server` passed outside the sandbox. The
sandboxed run hit macOS `sandbox-exec: sandbox_apply: Operation not
permitted` in filesystem sandbox tests.
2026-05-12 16:50:45 -07:00