Commit Graph

10184 Commits

Author SHA1 Message Date
Michael Bolin
5763e48246 merge commit for archive created by Sapling 2026-03-26 15:41:55 -07:00
Michael Bolin
ab7ce315fb permissions: remove macOS seatbelt extension profiles 2026-03-26 15:40:36 -07:00
Michael Bolin
4a82ab2446 Merge 9df4851671 into sapling-pr-archive-bolinfest 2026-03-26 15:21:59 -07:00
Michael Bolin
9df4851671 permissions: start using PermissionProfile as the canonical runtime model 2026-03-26 15:21:46 -07:00
Michael Bolin
cfd3a90ed4 Merge 1dd6b0d6a7 into sapling-pr-archive-bolinfest 2026-03-26 14:48:12 -07:00
Michael Bolin
1dd6b0d6a7 docs: update AGENTS.md to discourage adding code to codex-core 2026-03-26 14:48:06 -07:00
Michael Bolin
4f2f8f2ef1 Merge fcad39156d into sapling-pr-archive-bolinfest 2026-03-26 13:46:17 -07:00
Michael Bolin
fcad39156d chore: remove skill_metadata field 2026-03-26 13:46:09 -07:00
Michael Bolin
dfb36573cd sandboxing: use OsString for SandboxCommand.program (#15897)
## Why

`SandboxCommand.program` represents an executable path, but keeping it
as `String` forced path-backed callers to run `to_string_lossy()` before
the sandbox layer ever touched the command. That loses fidelity earlier
than necessary and adds avoidable conversions in runtimes that already
have a `PathBuf`.

## What changed

- Changed `SandboxCommand.program` to `OsString`.
- Updated `SandboxManager::transform` to keep the program and argv in
`OsString` form until the `SandboxExecRequest` conversion boundary.
- Switched the path-backed `apply_patch` and `js_repl` runtimes to pass
`into_os_string()` instead of `to_string_lossy()`.
- Updated the remaining string-backed builders and tests to match the
new type while preserving the existing Linux helper `arg0` behavior.

## Verification

- `cargo test -p codex-sandboxing`
- `just argument-comment-lint -p codex-core -p codex-sandboxing`
- `cargo test -p codex-core` currently fails in unrelated existing
config tests: `config::tests::approvals_reviewer_*` and
`config::tests::smart_approvals_alias_*`
2026-03-26 20:38:33 +00:00
Michael Bolin
b23789b770 [codex] import token_data from codex-login directly (#15903)
## Why
`token_data` is owned by `codex-login`, but `codex-core` was still
re-exporting it. That let callers pull auth token types through
`codex-core`, which keeps otherwise unrelated crates coupled to
`codex-core` and makes `codex-core` more of a build-graph bottleneck.

## What changed
- remove the `codex-core` re-export of `codex_login::token_data`
- update the remaining `codex-core` internals that used
`crate::token_data` to import `codex_login::token_data` directly
- update downstream callers in `codex-rs/chatgpt`,
`codex-rs/tui_app_server`, `codex-rs/app-server/tests/common`, and
`codex-rs/core/tests` to import `codex_login::token_data` directly
- add explicit `codex-login` workspace dependencies and refresh lock
metadata for crates that now depend on it directly

## Validation
- `cargo test -p codex-chatgpt --locked`
- `just argument-comment-lint`
- `just bazel-lock-update`
- `just bazel-lock-check`

## Notes
- attempted `cargo test -p codex-core --locked` and `cargo test -p
codex-core auth_refresh --locked`, but both ran out of disk while
linking `codex-core` test binaries in the local environment
2026-03-26 13:34:02 -07:00
rreichel3-oai
86764af684 Protect first-time project .codex creation across Linux and macOS sandboxes (#15067)
## Problem

Codex already treated an existing top-level project `./.codex` directory
as protected, but there was a gap on first creation.

If `./.codex` did not exist yet, a turn could create files under it,
such as `./.codex/config.toml`, without going through the same approval
path as later modifications. That meant the initial write could bypass
the intended protection for project-local Codex state.

## What this changes

This PR closes that first-creation gap in the Unix enforcement layers:

- `codex-protocol`
- treat the top-level project `./.codex` path as a protected carveout
even when it does not exist yet
- avoid injecting the default carveout when the user already has an
explicit rule for that exact path
- macOS Seatbelt
- deny writes to both the exact protected path and anything beneath it,
so creating `./.codex` itself is blocked in addition to writes inside it
- Linux bubblewrap
- preserve the same protected-path behavior for first-time creation
under `./.codex`
- tests
- add protocol regressions for missing `./.codex` and explicit-rule
collisions
- add Unix sandbox coverage for blocking first-time `./.codex` creation
  - tighten Seatbelt policy assertions around excluded subpaths

## Scope

This change is intentionally scoped to protecting the top-level project
`.codex` subtree from agent writes.

It does not make `.codex` unreadable, and it does not change the product
behavior around loading project skills from `.codex` when project config
is untrusted.

## Why this shape

The fix is pointed rather than broad:
- it preserves the current model of “project `.codex` is protected from
writes”
- it closes the security-relevant first-write hole
- it avoids folding a larger permissions-model redesign into this PR

## Validation

- `cargo test -p codex-protocol`
- `cargo test -p codex-sandboxing seatbelt`
- `cargo test -p codex-exec --test all
sandbox_blocks_first_time_dot_codex_creation -- --nocapture`

---------

Co-authored-by: Michael Bolin <mbolin@openai.com>
2026-03-26 16:06:53 -04:00
Ruslan Nigmatullin
9736fa5e3d app-server: Split transport module (#15811)
`transport.rs` is getting pretty big, split individual transport
implementations into separate files.
2026-03-26 13:01:35 -07:00
Michael Bolin
73e5be3ca8 Merge bc3f1320ba into sapling-pr-archive-bolinfest 2026-03-26 12:37:09 -07:00
Michael Bolin
bc3f1320ba refactor: stop re-exporting token_data from codex-core 2026-03-26 12:36:56 -07:00
Michael Bolin
b3e069e8cb skills: remove unused skill permission metadata (#15900)
## Why

Skill metadata accepted a `permissions` block and stored the result on
`SkillMetadata`, but that data was never consumed by runtime behavior.
Leaving the dead parsing path in place makes it look like skills can
widen or otherwise influence execution permissions when, in practice,
declared skill permissions are ignored.

This change removes that misleading surface area so the skill metadata
model matches what the system actually uses.

## What changed

- removed `permission_profile` and `managed_network_override` from
`core-skills::SkillMetadata`
- stopped parsing `permissions` from skill metadata in
`core-skills/src/loader.rs`
- deleted the loader tests that only exercised the removed permissions
parsing path
- cleaned up dependent `SkillMetadata` constructors in tests and TUI
code that were only carrying `None` for those fields

## Testing

- `cargo test -p codex-core-skills`
- `cargo test -p codex-tui
submission_prefers_selected_duplicate_skill_path`
- `just argument-comment-lint`
2026-03-26 19:33:23 +00:00
viyatb-oai
b6050b42ae fix: resolve bwrap from trusted PATH entry (#15791)
## Summary
- resolve system bwrap from PATH instead of hardcoding /usr/bin/bwrap
- skip PATH entries that resolve inside the current workspace before
launching the sandbox helper
- keep the vendored bubblewrap fallback when no trusted system bwrap is
found

## Validation
- cargo test -p codex-core bwrap --lib
- cargo test -p codex-linux-sandbox
- just fix -p codex-core
- just fix -p codex-linux-sandbox
- just fmt
- just argument-comment-lint
- cargo clean
2026-03-26 12:13:51 -07:00
Michael Bolin
b497384556 Merge 224eb8987f into sapling-pr-archive-bolinfest 2026-03-26 11:59:10 -07:00
Michael Bolin
224eb8987f skills: remove unused skill permission metadata 2026-03-26 11:58:56 -07:00
Matthew Zeng
3360f128f4 [plugins] Polish tool suggest prompts. (#15891)
- [x] Polish tool suggest prompts to distinguish between missing
connectors and discoverable plugins, and be very precise about the
triggering conditions.
2026-03-26 18:52:59 +00:00
Michael Bolin
3dff35f9d4 Merge 43d4a56601 into sapling-pr-archive-bolinfest 2026-03-26 11:40:30 -07:00
Michael Bolin
43d4a56601 fix: use OsString for SandboxCommand.program 2026-03-26 11:37:58 -07:00
Matthew Zeng
25134b592c [mcp] Fix legacy_tools (#15885)
- [x] Fix legacy_tools
2026-03-26 11:08:49 -07:00
Felipe Coury
2c54d4b160 feat(tui): add terminal title support to tui app server (#15860)
## TR;DR

Replicates the `/title` command from `tui` to `tui_app_server`.

## Problem

The classic `tui` crate supports customizing the terminal window/tab
title via `/title`, but the `tui_app_server` crate does not. Users on
the app-server path have no way to configure what their terminal title
shows (project name, status, spinner, thread, etc.), making it harder to
identify Codex sessions across tabs or windows.

## Mental model

The terminal title is a *status surface* -- conceptually parallel to the
footer status line. Both surfaces are configurable lists of items, both
share expensive inputs (git branch lookup, project root discovery), and
both must be refreshed at the same lifecycle points. This change ports
the classic `tui`'s design verbatim:

1. **`terminal_title.rs`** owns the low-level OSC write path and input
sanitization. It strips control characters and bidi/invisible codepoints
before placing untrusted text (model output, thread names, project
paths) inside an escape sequence.

2. **`title_setup.rs`** defines `TerminalTitleItem` (the 8 configurable
items) and `TerminalTitleSetupView` (the interactive picker that wraps
`MultiSelectPicker`).

3. **`status_surfaces.rs`** is the shared refresh pipeline. It parses
both surface configs once per refresh, warns about invalid items once
per session, synchronizes the git-branch cache, then renders each
surface from the same `StatusSurfaceSelections` snapshot.

4. **`chatwidget.rs`** sets `TerminalTitleStatusKind` at each state
transition (Working, Thinking, Undoing, WaitingForBackgroundTerminal)
and calls `refresh_terminal_title()` whenever relevant state changes.

5. **`app.rs`** handles the three setup events (confirm/preview/cancel),
persists config via `ConfigEditsBuilder`, and clears the managed title
on `Drop`.

## Non-goals

- **Restoring the previous terminal title on exit.** There is no
portable way to read the terminal's current title, so `Drop` clears the
managed title rather than restoring it.
- **Sharing code between `tui` and `tui_app_server`.** The
implementation is a parallel copy, matching the existing pattern for the
status-line feature. Extracting a shared crate is future work.

## Tradeoffs

- **Duplicate code across crates.** The three core files
(`terminal_title.rs`, `title_setup.rs`, `status_surfaces.rs`) are
byte-for-byte copies from the classic `tui`. This was chosen for
consistency with the existing status-line port and to avoid coupling the
two crates at the dependency level. Future changes must be applied in
both places.

- **`status_surfaces.rs` is large (~660 lines).** It absorbs logic that
previously lived inline in `chatwidget.rs` (status-line refresh, git
branch management, project root discovery) plus all new terminal-title
logic. This consolidation trades file size for a single place where both
surfaces are coordinated.

- **Spinner scheduling on every refresh.** The terminal title spinner
(when active) schedules a frame every 100ms. This is the same pattern
the status-indicator spinner already uses; the overhead is a timer
registration, not a redraw.

## Architecture

```
/title command
  -> SlashCommand::Title
  -> open_terminal_title_setup()
  -> TerminalTitleSetupView (MultiSelectPicker)
  -> on_change:  AppEvent::TerminalTitleSetupPreview  -> preview_terminal_title()
  -> on_confirm: AppEvent::TerminalTitleSetup         -> ConfigEditsBuilder + setup_terminal_title()
  -> on_cancel:  AppEvent::TerminalTitleSetupCancelled -> cancel_terminal_title_setup()

Runtime title refresh:
  state change (turn start, reasoning, undo, plan update, thread rename, ...)
  -> set terminal_title_status_kind
  -> refresh_terminal_title()
  -> status_surface_selections()  (parse configs, collect invalids)
  -> refresh_terminal_title_from_selections()
     -> terminal_title_value_for_item() for each configured item
     -> assemble title string with separators
     -> skip if identical to last_terminal_title (dedup OSC writes)
     -> set_terminal_title() (sanitize + OSC 0 write)
     -> schedule spinner frame if animating

Widget replacement:
  replace_chat_widget_with_app_server_thread()
  -> transfer last_terminal_title from old widget to new
  -> avoids redundant OSC clear+rewrite on session switch
```

## Observability

- Invalid terminal-title item IDs in config emit a one-per-session
warning via `on_warning()` (gated by
`terminal_title_invalid_items_warned` `AtomicBool`).
- OSC write failures are logged at `tracing::debug` level.
- Config persistence failures are logged at `tracing::error` and
surfaced to the user via `add_error_message()`.

## Tests

- `terminal_title.rs`: 4 unit tests covering sanitization (control
chars, bidi codepoints, truncation) and OSC output format.
- `title_setup.rs`: 3 tests covering setup view snapshot rendering,
parse order preservation, and invalid-ID rejection.
- `chatwidget/tests.rs`: Updated test helpers with new fields; existing
tests continue to pass.

---------

Co-authored-by: Eric Traut <etraut@openai.com>
2026-03-26 11:59:12 -06:00
jif-oai
970386e8b2 fix: root as std agent (#15881) 2026-03-26 18:57:34 +01:00
evawong-oai
0bd34c28c7 Add wildcard in the middle test coverage (#15813)
## Summary
Add a focused codex network proxy unit test for the denylist pattern
with wildcard in the middle `region*.some.malicious.tunnel.com`. This
does not change how existing code works, just ensure that behavior stays
the same and we got CI guards to guard existin behavior.

## Why
The managed Codex denylist update relies on this mid label glob form,
and the existing tests only covered exact hosts, `*.` subdomains, and
`**.` apex plus subdomains.

## Validation
`cargo test -p codex-network-proxy
compile_globset_supports_mid_label_wildcards`
`cargo test -p codex-network-proxy`
`./tools/argument-comment-lint/run-prebuilt-linter.sh -p
codex-network-proxy`
2026-03-26 17:53:31 +00:00
Adrian
af04273778 [codex] Block unsafe git global options from safe allowlist (#15796)
## Summary
- block git global options that can redirect config, repository, or
helper lookup from being auto-approved as safe
- share the unsafe global-option predicate across the Unix and Windows
git safety checks
- add regression coverage for inline and split forms, including `bash
-lc` and PowerShell wrappers

## Root cause
The Unix safe-command gate only rejected `-c` and `--config-env`, even
though the shared git parser already knew how to skip additional
pre-subcommand globals such as `--git-dir`, `--work-tree`,
`--exec-path`, `--namespace`, and `--super-prefix`. That let those
arguments slip through safe-command classification on otherwise
read-only git invocations and bypass approval. The Windows-specific
safe-command path had the same trust-boundary gap for git global
options.
2026-03-26 10:46:04 -07:00
Michael Bolin
e36ebaa3da fix: box apply_patch test harness futures (#15835)
## Why

`#[large_stack_test]` made the `apply_patch_cli` tests pass by giving
them more stack, but it did not address why those tests needed the extra
stack in the first place.

The real problem is the async state built by the `apply_patch_cli`
harness path. Those tests await three helper boundaries directly:
harness construction, turn submission, and apply-patch output
collection. If those helpers inline their full child futures, the test
future grows to include the whole harness startup and request/response
path.

This change replaces the workaround from #12768 with the same basic
approach used in #13429, but keeps the fix narrower: only the helper
boundaries awaited directly by `apply_patch_cli` stay boxed.

## What Changed

- removed `#[large_stack_test]` from
`core/tests/suite/apply_patch_cli.rs`
- restored ordinary `#[tokio::test(flavor = "multi_thread",
worker_threads = 2)]` annotations in that suite
- deleted the now-unused `codex-test-macros` crate and removed its
workspace wiring
- boxed only the three helper boundaries that the suite awaits directly:
  - `apply_patch_harness_with(...)`
  - `TestCodexHarness::submit(...)`
  - `TestCodexHarness::apply_patch_output(...)`
- added comments at those boxed boundaries explaining why they remain
boxed

## Testing

- `cargo test -p codex-core --test all suite::apply_patch_cli --
--nocapture`

## References

- #12768
- #13429
2026-03-26 17:32:04 +00:00
Eric Traut
e7139e14a2 Enable tui_app_server feature by default (#15661) 2026-03-26 11:28:25 -06:00
nicholasclark-openai
8d479f741c Add MCP connector metrics (#15805)
## Summary
- enrich `codex.mcp.call` with `tool`, `connector_id`, and sanitized
`connector_name` for actual MCP executions
- record `codex.mcp.call.duration_ms` for actual MCP executions so
connector-level latency is visible in metrics
- keep skipped, blocked, declined, and cancelled paths on the plain
status-only `codex.mcp.call` counter

## Included Changes
- `codex-rs/core/src/mcp_tool_call.rs`: add connector-sliced MCP count
and duration metrics only for executed tool calls, while leaving
non-executed outcomes as status-only counts
- `codex-rs/core/src/mcp_tool_call_tests.rs`: cover metric tag shaping,
connector-name sanitization, and the new duration metric tags

## Testing
- `cargo test -p codex-core`
- `just fix -p codex-core`
- `just fmt`

## Notes
- `cargo test -p codex-core` still hits existing unrelated failures in
approvals-reviewer config tests and the sandboxed JS REPL `mktemp` test
- full workspace `cargo test` was not run

---------

Co-authored-by: Codex <noreply@openai.com>
2026-03-26 17:08:02 +00:00
Eric Traut
0d44bd708e Fix duplicate /review messages in app-server TUI (#15839)
## Symptoms
When `/review` ran through `tui_app_server`, the TUI could show
duplicate review content:
- the `>> Code review started: ... <<` banner appeared twice
- the final review body could also appear twice

## Problem
`tui_app_server` was treating review lifecycle items as renderable
content on more than one delivery path.

Specifically:
- `EnteredReviewMode` was rendered both when the item started and again
when it completed
- `ExitedReviewMode` rendered the review text itself, even though the
same review text was also delivered later as the assistant message item

That meant the same logical review event was committed into history
multiple times.

## Solution
Make review lifecycle items control state transitions only once, and
keep the final review body sourced from the assistant message item:
- render the review-start banner from the live `ItemStarted` path, while
still allowing replay to restore it once
- treat `ExitedReviewMode` as a mode-exit/finish-banner event instead of
rendering the review body from it
- preserve the existing assistant-message rendering path as the single
source of final review text
2026-03-26 10:55:18 -06:00
jif-oai
352f37db03 fix: max depth agent still has v2 tools (#15880) 2026-03-26 17:36:12 +01:00
Matthew Zeng
c9214192c5 [plugins] Update the suggestable plugins list. (#15829)
- [x] Update the suggestable plugins list to be featured plugins.
2026-03-26 15:53:22 +00:00
jif-oai
6d2f4aaafc feat: use ProcessId in exec-server (#15866)
Use a full struct for the ProcessId to increase readability and make it
easier in the future to make it evolve if needed
2026-03-26 16:45:36 +01:00
jif-oai
a5824e37db chore: ask agents md not to play with PIDs (#15877)
Ask Codex to be patient with Rust
2026-03-26 15:43:19 +00:00
Michael Bolin
f8f045d130 merge commit for archive created by Sapling 2026-03-26 08:25:17 -07:00
Michael Bolin
d7e76aebc1 fix: box apply_patch test harness futures 2026-03-26 08:25:11 -07:00
jif-oai
26c66f3ee1 fix: flaky (#15869) 2026-03-26 16:07:32 +01:00
Michael Bolin
01fa4f0212 core: remove special execve handling for skill scripts (#15812) 2026-03-26 07:46:04 -07:00
jif-oai
6dcac41d53 chore: drop artifacts lib (#15864) 2026-03-26 15:28:59 +01:00
jif-oai
7dac332c93 feat: exec-server prep for unified exec (#15691)
This PR partially rebase `unified_exec` on the `exec-server` and adapt
the `exec-server` accordingly.

## What changed in `exec-server`

1. Replaced the old "broadcast-driven; process-global" event model with
process-scoped session events. The goal is to be able to have dedicated
handler for each process.
2. Add to protocol contract to support explicit lifecycle status and
stream ordering:
- `WriteResponse` now returns `WriteStatus` (Accepted, UnknownProcess,
StdinClosed, Starting) instead of a bool.
  - Added seq fields to output/exited notifications.
  - Added terminal process/closed notification.
3. Demultiplexed remote notifications into per-process channels. Same as
for the event sys
4. Local and remote backends now both implement ExecBackend.
5. Local backend wraps internal process ID/operations into per-process
ExecProcess objects.
6. Remote backend registers a session channel before launch and
unregisters on failed launch.

## What changed in `unified_exec`

1. Added unified process-state model and backend-neutral process
wrapper. This will probably disappear in the future, but it makes it
easier to keep the work flowing on both side.
- `UnifiedExecProcess` now handles both local PTY sessions and remote
exec-server processes through a shared `ProcessHandle`.
- Added `ProcessState` to track has_exited, exit_code, and terminal
failure message consistently across backends.
2. Routed write and lifecycle handling through process-level methods.

## Some rationals

1. The change centralizes execution transport in exec-server while
preserving policy and orchestration ownership in core, avoiding
duplicated launch approval logic. This comes from internal discussion.
2. Session-scoped events remove coupling/cross-talk between processes
and make stream ordering and terminal state explicit (seq, closed,
failed).
3. The failure-path surfacing (remote launch failures, write failures,
transport disconnects) makes command tool output and cleanup behavior
deterministic

## Follow-ups:
* Unify the concept of thread ID behind an obfuscated struct
* FD handling
* Full zsh-fork compatibility
* Full network sandboxing compatibility
* Handle ws disconnection
2026-03-26 15:22:34 +01:00
jif-oai
4a5635b5a0 feat: clean spawn v1 (#15861)
Avoid the usage of path in the v1 spawn
2026-03-26 15:01:00 +01:00
jif-oai
b00a05c785 feat: drop artifact tool and feature (#15851) 2026-03-26 13:21:24 +01:00
jif-oai
7ef3cfe63e feat: replace askama by custom lib (#15784)
Finalise the drop of `askama` to use our internal lib instead
2026-03-26 10:33:25 +01:00
viyatb-oai
937cb5081d fix: fix old system bubblewrap compatibility without falling back to vendored bwrap (#15693)
Fixes #15283.

## Summary
Older system bubblewrap builds reject `--argv0`, which makes our Linux
sandbox fail before the helper can re-exec. This PR keeps using system
`/usr/bin/bwrap` whenever it exists and only falls back to vendored
bwrap when the system binary is missing. That matters on stricter
AppArmor hosts, where the distro bwrap package also provides the policy
setup needed for user namespaces.

For old system bwrap, we avoid `--argv0` instead of switching binaries:
- pass the sandbox helper a full-path `argv0`,
- keep the existing `current_exe() + --argv0` path when the selected
launcher supports it,
- otherwise omit `--argv0` and re-exec through the helper's own
`argv[0]` path, whose basename still dispatches as
`codex-linux-sandbox`.

Also updates the launcher/warning tests and docs so they match the new
behavior: present-but-old system bwrap uses the compatibility path, and
only absent system bwrap falls back to vendored.

### Validation

1. Install Ubuntu 20.04 in a VM
2. Compile codex and run without bubblewrap installed - see a warning
about falling back to the vendored bwrap
3. Install bwrap and verify version is 0.4.0 without `argv0` support
4. run codex and use apply_patch tool without errors

<img width="802" height="631" alt="Screenshot 2026-03-25 at 11 48 36 PM"
src="https://github.com/user-attachments/assets/77248a29-aa38-4d7c-9833-496ec6a458b8"
/>
<img width="807" height="634" alt="Screenshot 2026-03-25 at 11 47 32 PM"
src="https://github.com/user-attachments/assets/5af8b850-a466-489b-95a6-455b76b5050f"
/>
<img width="812" height="635" alt="Screenshot 2026-03-25 at 11 45 45 PM"
src="https://github.com/user-attachments/assets/438074f0-8435-4274-a667-332efdd5cb57"
/>
<img width="801" height="623" alt="Screenshot 2026-03-25 at 11 43 56 PM"
src="https://github.com/user-attachments/assets/0dc8d3f5-e8cf-4218-b4b4-a4f7d9bf02e3"
/>

---------

Co-authored-by: Michael Bolin <mbolin@openai.com>
2026-03-25 23:51:39 -07:00
Michael Bolin
023902a47b merge commit for archive created by Sapling 2026-03-25 22:54:45 -07:00
Michael Bolin
e679a8e14b fix: box apply_patch test harness futures 2026-03-25 22:54:33 -07:00
Michael Bolin
d798a9247b merge commit for archive created by Sapling 2026-03-25 22:52:01 -07:00
Michael Bolin
6ec7a2d269 fix: box apply_patch test harness futures 2026-03-25 22:51:12 -07:00
Michael Bolin
75f4cc5cdf Merge 9e6350c16b into sapling-pr-archive-bolinfest 2026-03-25 22:45:47 -07:00
Michael Bolin
9e6350c16b fix: box apply_patch test harness futures 2026-03-25 22:45:38 -07:00