From 9e3d5f29e2b6e2bcad3b0384e35e1faaa13e8272 Mon Sep 17 00:00:00 2001 From: "Adam Perry @ OpenAI" Date: Tue, 2 Jun 2026 11:06:01 -0700 Subject: [PATCH 1/3] [codex] Revert shared BuildBuddy Bazel wrapper (#25909) ## Why PR #25905 intentionally adds a failing `codex-core` unit test, but its [Bazel test on Windows check](https://github.com/openai/codex/actions/runs/26837526950/job/79135369259) passed. That shows the Bazel configuration introduced by #25156 is not behaving as expected, so revert it while the configuration can be investigated separately. ## What changed Revert #25156 in full, restoring the previous Bazel remote configuration, CI scripts, workflows, `rusty_v8` handling, and documentation. This removes the shared BuildBuddy wrapper and its tests. ## Validation Not run locally; this exact revert was prioritized for a fast rollback. --- .bazelrc | 46 ++--- .github/scripts/run-bazel-ci.sh | 126 +++++++----- .github/scripts/run-bazel-query-ci.sh | 56 ++++-- .github/scripts/run_bazel_with_buildbuddy.py | 142 -------------- .github/scripts/rusty_v8_bazel.py | 63 +++--- .../scripts/test_run_bazel_with_buildbuddy.py | 184 ------------------ .github/scripts/test_rusty_v8_bazel.py | 49 ++--- .github/workflows/bazel.yml | 8 +- .github/workflows/rusty-v8-release.yml | 5 +- .github/workflows/v8-canary.yml | 7 +- codex-rs/docs/bazel.md | 114 +---------- justfile | 11 +- scripts/list-bazel-clippy-targets.sh | 24 ++- 13 files changed, 218 insertions(+), 617 deletions(-) delete mode 100755 .github/scripts/run_bazel_with_buildbuddy.py delete mode 100644 .github/scripts/test_run_bazel_with_buildbuddy.py diff --git a/.bazelrc b/.bazelrc index e39a3aff22..6357eb83e4 100644 --- a/.bazelrc +++ b/.bazelrc @@ -38,50 +38,24 @@ common:windows --test_env=WINDIR common --test_env=RUST_MIN_STACK=8388608 # 8 MiB common --test_output=errors -common --nobuild_runfile_links -# These settings tune BuildBuddy/RBE behavior but do not contact a remote -# service unless a `buildbuddy-*` configuration below supplies an endpoint. +common --bes_results_url=https://app.buildbuddy.io/invocation/ +common --bes_backend=grpcs://remote.buildbuddy.io +common --remote_cache=grpcs://remote.buildbuddy.io common --remote_download_toplevel +common --nobuild_runfile_links common --remote_timeout=3600 common --noexperimental_throttle_remote_action_building common --experimental_remote_execution_keepalive common --grpc_keepalive_time=30s - -# Opt-in remote configurations selected by -# `.github/scripts/run_bazel_with_buildbuddy.py`. Plain Bazel commands do not -# contact BuildBuddy unless a user selects one of these configurations. -# Use the generic host for cache, BES, and downloads without remote execution. -common:buildbuddy-generic --bes_backend=grpcs://remote.buildbuddy.io -common:buildbuddy-generic --bes_results_url=https://app.buildbuddy.io/invocation/ -common:buildbuddy-generic --remote_cache=grpcs://remote.buildbuddy.io -common:buildbuddy-generic --experimental_remote_downloader=grpcs://remote.buildbuddy.io - -# Add remote execution on the generic host. -common:buildbuddy-generic-rbe --config=buildbuddy-generic -common:buildbuddy-generic-rbe --config=remote -common:buildbuddy-generic-rbe --remote_executor=grpcs://remote.buildbuddy.io - -# Use the OpenAI tenant for cache, BES, and downloads without remote execution. -common:buildbuddy-openai --bes_backend=grpcs://openai.buildbuddy.io -common:buildbuddy-openai --bes_results_url=https://openai.buildbuddy.io/invocation/ -common:buildbuddy-openai --remote_cache=grpcs://openai.buildbuddy.io -common:buildbuddy-openai --experimental_remote_downloader=grpcs://openai.buildbuddy.io - -# Add remote execution on the OpenAI tenant. -common:buildbuddy-openai-rbe --config=buildbuddy-openai -common:buildbuddy-openai-rbe --config=remote -common:buildbuddy-openai-rbe --remote_executor=grpcs://openai.buildbuddy.io +common --experimental_remote_downloader=grpcs://remote.buildbuddy.io # This limits both in-flight executions and concurrent downloads. Even with high number # of jobs execution will still be limited by CPU cores, so this just pays a bit of # memory in exchange for higher download concurrency. common --jobs=30 -# Shared remote execution policy. The endpoint-bearing `buildbuddy-*-rbe` -# configurations include this group; CI configs override TestRunner below -# when tests must remain local on their runner. -common:remote --strategy=remote common:remote --extra_execution_platforms=//:rbe +common:remote --remote_executor=grpcs://remote.buildbuddy.io common:remote --jobs=800 # TODO(team): Evaluate if this actually helps, zbarsky is not sure, everything seems bottlenecked on `core` either way. # Enable pipelined compilation since we are not bound by local CPU count. @@ -172,11 +146,15 @@ common:ci-windows --repo_contents_cache=D:/a/.cache/bazel-repo-contents-cache # Linux crossbuilds don't work until we untangle the libc constraint mess. common:ci-linux --config=ci-bazel common:ci-linux --build_metadata=TAG_os=linux +common:ci-linux --config=remote +common:ci-linux --strategy=remote common:ci-linux --platforms=//:rbe # On mac, we can run all the build actions remotely but test actions locally. common:ci-macos --config=ci-bazel common:ci-macos --build_metadata=TAG_os=macos +common:ci-macos --config=remote +common:ci-macos --strategy=remote common:ci-macos --strategy=TestRunner=darwin-sandbox,local # On Windows, use Linux remote execution for build actions but keep test actions @@ -184,7 +162,9 @@ common:ci-macos --strategy=TestRunner=darwin-sandbox,local # still run against Windows binaries. common:ci-windows-cross --config=ci-windows common:ci-windows-cross --build_metadata=TAG_windows_cross_compile=true +common:ci-windows-cross --config=remote common:ci-windows-cross --host_platform=//:rbe +common:ci-windows-cross --strategy=remote common:ci-windows-cross --strategy=TestRunner=local common:ci-windows-cross --local_test_jobs=4 common:ci-windows-cross --test_env=RUST_TEST_THREADS=1 @@ -200,6 +180,8 @@ common:ci-windows-cross --extra_toolchains=//:windows_gnullvm_tests_on_msvc_host common:ci-v8 --config=ci common:ci-v8 --build_metadata=TAG_workflow=v8 common:ci-v8 --build_metadata=TAG_os=linux +common:ci-v8 --config=remote +common:ci-v8 --strategy=remote # Source-built Bazel V8 artifacts use the in-process sandbox by default. This # does not affect Cargo's default prebuilt rusty_v8 path. diff --git a/.github/scripts/run-bazel-ci.sh b/.github/scripts/run-bazel-ci.sh index 89f937a998..f98e4d8cb9 100755 --- a/.github/scripts/run-bazel-ci.sh +++ b/.github/scripts/run-bazel-ci.sh @@ -53,20 +53,11 @@ fi run_bazel() { if [[ "${RUNNER_OS:-}" == "Windows" ]]; then - MSYS2_ARG_CONV_EXCL='*' "$(dirname "${BASH_SOURCE[0]}")/run_bazel_with_buildbuddy.py" "$@" + MSYS2_ARG_CONV_EXCL='*' bazel "$@" return fi - "$(dirname "${BASH_SOURCE[0]}")/run_bazel_with_buildbuddy.py" "$@" -} - -run_bazel_with_startup_args() { - if (( ${#bazel_startup_args[@]} > 0 )); then - run_bazel "${bazel_startup_args[@]}" "$@" - return - fi - - run_bazel "$@" + bazel "$@" } ci_config=ci-linux @@ -86,16 +77,23 @@ esac print_bazel_test_log_tails() { local console_log="$1" local testlogs_dir - + local -a bazel_info_cmd=(bazel) local -a bazel_info_args=(info) - if [[ -n "${BUILDBUDDY_API_KEY:-}" ]]; then - # `bazel info` needs the same CI config as the failed test invocation so - # platform-specific output roots match. On Windows, omitting `ci-windows` - # would point at `local_windows-fastbuild` even when the test ran with the - # MSVC host platform under `local_windows_msvc-fastbuild`. - bazel_info_args+=("--config=${ci_config}") + + if (( ${#bazel_startup_args[@]} > 0 )); then + bazel_info_cmd+=("${bazel_startup_args[@]}") fi + # `bazel info` needs the same CI config as the failed test invocation so + # platform-specific output roots match. On Windows, omitting `ci-windows` + # would point at `local_windows-fastbuild` even when the test ran with the + # MSVC host platform under `local_windows_msvc-fastbuild`. + if [[ -n "${BUILDBUDDY_API_KEY:-}" ]]; then + bazel_info_args+=( + "--config=${ci_config}" + "--remote_header=x-buildbuddy-api-key=${BUILDBUDDY_API_KEY}" + ) + fi # Only pass flags that affect Bazel's output-root selection or repository # lookup. Test/build-only flags such as execution logs or remote download # mode can make `bazel info` fail, which would hide the real test log path. @@ -107,7 +105,7 @@ print_bazel_test_log_tails() { esac done - testlogs_dir="$(run_bazel_with_startup_args \ + testlogs_dir="$(run_bazel "${bazel_info_cmd[@]:1}" \ --noexperimental_remote_repo_contents_cache \ "${bazel_info_args[@]}" \ bazel-testlogs 2>/dev/null || echo bazel-testlogs)" @@ -256,9 +254,8 @@ if [[ ${#bazel_args[@]} -eq 0 || ${#bazel_targets[@]} -eq 0 ]]; then fi if [[ "${RUNNER_OS:-}" == "Windows" && $windows_cross_compile -eq 1 && -z "${BUILDBUDDY_API_KEY:-}" ]]; then - # Windows cross-compilation depends on authenticated RBE. Preserve the local - # Windows build shape when credentials are unavailable. - ci_config=ci-windows + # Fork PRs do not receive the BuildBuddy secret needed for the remote + # cross-compile config. Preserve the previous local Windows build shape. windows_msvc_host_platform=1 fi @@ -300,9 +297,9 @@ if [[ "${RUNNER_OS:-}" == "Windows" && $windows_cross_compile -eq 1 && -n "${BUI fi if [[ "${RUNNER_OS:-}" == "Windows" && $windows_cross_compile -eq 1 && -z "${BUILDBUDDY_API_KEY:-}" ]]; then - # The Windows cross-compile config depends on authenticated remote - # execution. When credentials are unavailable, keep the local build shape - # and its lower concurrency cap. + # The Windows cross-compile config depends on remote execution. Fork PRs do + # not receive the BuildBuddy secret, so fall back to the existing local build + # shape and keep its lower concurrency cap. post_config_bazel_args+=(--jobs=8) fi @@ -380,31 +377,70 @@ fi bazel_console_log="$(mktemp)" trap 'rm -f "$bazel_console_log"' EXIT -bazel_run_args=( - "${bazel_args[@]}" -) +bazel_cmd=(bazel) +if (( ${#bazel_startup_args[@]} > 0 )); then + bazel_cmd+=("${bazel_startup_args[@]}") +fi + if [[ -n "${BUILDBUDDY_API_KEY:-}" ]]; then echo "BuildBuddy API key is available; using remote Bazel configuration." - bazel_run_args+=("--config=${ci_config}") + # Work around Bazel 9 remote repo contents cache / overlay materialization failures + # seen in CI (for example "is not a symlink" or permission errors while + # materializing external repos such as rules_perl). We still use BuildBuddy for + # remote execution/cache; this only disables the startup-level repo contents cache. + bazel_run_args=( + "${bazel_args[@]}" + "--config=${ci_config}" + "--remote_header=x-buildbuddy-api-key=${BUILDBUDDY_API_KEY}" + ) + if (( ${#post_config_bazel_args[@]} > 0 )); then + bazel_run_args+=("${post_config_bazel_args[@]}") + fi + set +e + run_bazel "${bazel_cmd[@]:1}" \ + --noexperimental_remote_repo_contents_cache \ + "${bazel_run_args[@]}" \ + -- \ + "${bazel_targets[@]}" \ + 2>&1 | tee "$bazel_console_log" + bazel_status=${PIPESTATUS[0]} + set -e else echo "BuildBuddy API key is not available; using local Bazel configuration." + # Keep fork/community PRs on Bazel but disable remote services that are + # configured in .bazelrc and require auth. + # + # Flag docs: + # - Command-line reference: https://bazel.build/reference/command-line-reference + # - Remote caching overview: https://bazel.build/remote/caching + # - Remote execution overview: https://bazel.build/remote/rbe + # - Build Event Protocol overview: https://bazel.build/remote/bep + # + # --noexperimental_remote_repo_contents_cache: + # disable remote repo contents cache enabled in .bazelrc startup options. + # https://bazel.build/reference/command-line-reference#startup_options-flag--experimental_remote_repo_contents_cache + # --remote_cache= and --remote_executor=: + # clear remote cache/execution endpoints configured in .bazelrc. + # https://bazel.build/reference/command-line-reference#common_options-flag--remote_cache + # https://bazel.build/reference/command-line-reference#common_options-flag--remote_executor + bazel_run_args=( + "${bazel_args[@]}" + --remote_cache= + --remote_executor= + ) + if (( ${#post_config_bazel_args[@]} > 0 )); then + bazel_run_args+=("${post_config_bazel_args[@]}") + fi + set +e + run_bazel "${bazel_cmd[@]:1}" \ + --noexperimental_remote_repo_contents_cache \ + "${bazel_run_args[@]}" \ + -- \ + "${bazel_targets[@]}" \ + 2>&1 | tee "$bazel_console_log" + bazel_status=${PIPESTATUS[0]} + set -e fi -if (( ${#post_config_bazel_args[@]} > 0 )); then - bazel_run_args+=("${post_config_bazel_args[@]}") -fi -set +e -# Work around Bazel 9 remote repo contents cache / overlay materialization -# failures seen in CI (for example "is not a symlink" or permission errors -# while materializing external repos such as rules_perl). This only disables -# the startup-level repo contents cache; keyed runs still use BuildBuddy. -run_bazel_with_startup_args \ - --noexperimental_remote_repo_contents_cache \ - "${bazel_run_args[@]}" \ - -- \ - "${bazel_targets[@]}" \ - 2>&1 | tee "$bazel_console_log" -bazel_status=${PIPESTATUS[0]} -set -e if [[ ${bazel_status:-0} -ne 0 ]]; then if [[ $print_failed_bazel_action_summary -eq 1 ]]; then diff --git a/.github/scripts/run-bazel-query-ci.sh b/.github/scripts/run-bazel-query-ci.sh index f5d4f56f49..dd03b67169 100755 --- a/.github/scripts/run-bazel-query-ci.sh +++ b/.github/scripts/run-bazel-query-ci.sh @@ -2,17 +2,48 @@ set -euo pipefail -# Run target-discovery queries with the same startup settings as the main -# build/test invocation so they can reuse the same Bazel server. Queries only -# enumerate labels, so they intentionally do not select CI or remote configs. +# Run Bazel queries with the same CI startup settings as the main build/test +# invocation so target-discovery queries can reuse the same Bazel server. -if [[ $# -lt 2 || "${@: -2:1}" != "--" ]]; then - echo "Usage: $0 [...] -- " >&2 +query_args=() +windows_cross_compile=0 +while [[ $# -gt 0 ]]; do + case "$1" in + --windows-cross-compile) + windows_cross_compile=1 + shift + ;; + --) + shift + break + ;; + *) + query_args+=("$1") + shift + ;; + esac +done + +if [[ $# -ne 1 ]]; then + echo "Usage: $0 [--windows-cross-compile] [...] -- " >&2 exit 1 fi -query_args=("${@:1:$#-2}") -query_expression="${@: -1}" +query_expression="$1" + +ci_config=ci-linux +case "${RUNNER_OS:-}" in + macOS) + ci_config=ci-macos + ;; + Windows) + if [[ $windows_cross_compile -eq 1 ]]; then + ci_config=ci-windows-cross + else + ci_config=ci-windows + fi + ;; +esac bazel_startup_args=() if [[ -n "${BAZEL_OUTPUT_USER_ROOT:-}" ]]; then @@ -29,6 +60,12 @@ run_bazel() { } bazel_query_args=(--noexperimental_remote_repo_contents_cache query) +if [[ -n "${BUILDBUDDY_API_KEY:-}" ]]; then + bazel_query_args+=( + "--config=${ci_config}" + "--remote_header=x-buildbuddy-api-key=${BUILDBUDDY_API_KEY}" + ) +fi if [[ -n "${BAZEL_REPO_CONTENTS_CACHE:-}" ]]; then bazel_query_args+=("--repo_contents_cache=${BAZEL_REPO_CONTENTS_CACHE}") @@ -38,10 +75,7 @@ if [[ -n "${BAZEL_REPOSITORY_CACHE:-}" ]]; then bazel_query_args+=("--repository_cache=${BAZEL_REPOSITORY_CACHE}") fi -if (( ${#query_args[@]} > 0 )); then - bazel_query_args+=("${query_args[@]}") -fi -bazel_query_args+=("$query_expression") +bazel_query_args+=("${query_args[@]}" "$query_expression") if (( ${#bazel_startup_args[@]} > 0 )); then run_bazel "${bazel_startup_args[@]}" "${bazel_query_args[@]}" diff --git a/.github/scripts/run_bazel_with_buildbuddy.py b/.github/scripts/run_bazel_with_buildbuddy.py deleted file mode 100755 index add95a99e5..0000000000 --- a/.github/scripts/run_bazel_with_buildbuddy.py +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/env python3 - -import json -import os -import sys -from collections.abc import Mapping -from collections.abc import Sequence -from pathlib import Path - - -OPENAI_REPOSITORY = "openai/codex" -# Remote configurations select cache/BES/download endpoints. Their -rbe forms -# also select the matching remote executor endpoint. -GENERIC_REMOTE_CONFIG = "buildbuddy-generic" -OPENAI_REMOTE_CONFIG = "buildbuddy-openai" -# These CI configurations require remote build execution. The wrapper supplies -# an RBE configuration, which also includes the common `remote` settings. -REMOTE_EXECUTION_CONFIGS = { - "--config=ci-linux", - "--config=ci-macos", - "--config=ci-v8", - "--config=ci-windows-cross", -} -# Only authenticated workflow runs executing trusted upstream code may use the -# OpenAI BuildBuddy host. A pull request event without proof that its head is -# in the upstream repository fails closed to the generic host. -def is_trusted_upstream_run(env: Mapping[str, str]) -> bool: - # `GITHUB_REPOSITORY` is easy to set locally. Requiring GitHub's workflow - # marker prevents a local command from opting itself into the OpenAI host. - if ( - env.get("GITHUB_ACTIONS") != "true" - or env.get("GITHUB_REPOSITORY") != OPENAI_REPOSITORY - ): - return False - # Non-PR workflow runs in `openai/codex` execute upstream refs, so they are - # trusted. Fork code reaches these workflows only through pull requests. - if env.get("GITHUB_EVENT_NAME") != "pull_request": - return True - - event_path = env.get("GITHUB_EVENT_PATH") - if not event_path: - return False - try: - event = json.loads(Path(event_path).read_text(encoding="utf-8")) - except (OSError, json.JSONDecodeError): - return False - - try: - return event["pull_request"]["head"]["repo"]["fork"] is False - except (KeyError, TypeError): - return False - - -def uses_openai_host(env: Mapping[str, str]) -> bool: - return bool(env.get("BUILDBUDDY_API_KEY")) and is_trusted_upstream_run(env) - - -def uses_remote_execution(args: Sequence[str]) -> bool: - try: - separator_idx = args.index("--") - except ValueError: - separator_idx = len(args) - return any(arg in REMOTE_EXECUTION_CONFIGS for arg in args[:separator_idx]) - - -def remote_config(args: Sequence[str], env: Mapping[str, str]) -> str | None: - if not env.get("BUILDBUDDY_API_KEY"): - return None - - config = OPENAI_REMOTE_CONFIG if uses_openai_host(env) else GENERIC_REMOTE_CONFIG - if uses_remote_execution(args): - config += "-rbe" - return config - - -def bazel_args_without_remote_execution(args: Sequence[str]) -> list[str]: - # Remote CI configs require BuildBuddy credentials. Removing them preserves - # the local fallback used for fork pull requests. - try: - separator_idx = args.index("--") - except ValueError: - separator_idx = len(args) - return [ - *(arg for arg in args[:separator_idx] if arg not in REMOTE_EXECUTION_CONFIGS), - *args[separator_idx:], - ] - - -def bazel_args_with_remote_config( - args: Sequence[str], env: Mapping[str, str] -) -> list[str]: - config = remote_config(args, env) - if config is None: - return bazel_args_without_remote_execution(args) - - # `remote_config()` returns a configuration only when this key is present. - api_key = env["BUILDBUDDY_API_KEY"] - remote_args = [ - f"--config={config}", - f"--remote_header=x-buildbuddy-api-key={api_key}", - ] - - # Insert immediately after the Bazel command. This keeps wrapper-added - # options out of positional payloads and lets later CI configs override - # shared RBE defaults such as the Windows cross-compilation exec platforms. - insertion_idx = next( - (idx + 1 for idx, arg in enumerate(args) if not arg.startswith("-")), - len(args), - ) - return [*args[:insertion_idx], *remote_args, *args[insertion_idx:]] - - -def bazel_command(*args: str, env: Mapping[str, str] | None = None) -> list[str]: - env = os.environ if env is None else env - bazel = env.get("CODEX_BAZEL_BIN", "bazel") - return [bazel, *bazel_args_with_remote_config(args, env)] - - -def main() -> None: - config = remote_config(sys.argv[1:], os.environ) - if config is None: - print( - "BuildBuddy key unavailable; using local Bazel configuration.", - file=sys.stderr, - ) - else: - host_description = ( - "OpenAI tenant" if uses_openai_host(os.environ) else "generic" - ) - print( - f"Using {host_description} BuildBuddy configuration: {config}.", - file=sys.stderr, - ) - - command = bazel_command(*sys.argv[1:]) - # Replace the wrapper so Bazel receives signals directly and supplies the - # command exit status; a subprocess parent would have no remaining work. - os.execvp(command[0], command) - - -if __name__ == "__main__": - main() diff --git a/.github/scripts/rusty_v8_bazel.py b/.github/scripts/rusty_v8_bazel.py index 329d3f6c54..2f46daf45e 100644 --- a/.github/scripts/rusty_v8_bazel.py +++ b/.github/scripts/rusty_v8_bazel.py @@ -5,6 +5,7 @@ from __future__ import annotations import argparse import gzip import hashlib +import os import re import shutil import subprocess @@ -12,7 +13,6 @@ import sys import tomllib from pathlib import Path -from run_bazel_with_buildbuddy import bazel_command from rusty_v8_module_bazel import ( RustyV8ChecksumError, check_module_bazel, @@ -29,22 +29,33 @@ SANDBOX_ARTIFACT_PROFILE = "ptrcomp_sandbox_release" ARTIFACT_BAZEL_CONFIGS = ["rusty-v8-upstream-libcxx"] +def bazel_remote_args() -> list[str]: + buildbuddy_api_key = os.environ.get("BUILDBUDDY_API_KEY") + if not buildbuddy_api_key: + return [] + return [f"--remote_header=x-buildbuddy-api-key={buildbuddy_api_key}"] + + def bazel_execroot() -> Path: - output = subprocess.check_output( - bazel_command("info", "execution_root"), + result = subprocess.run( + ["bazel", "info", "execution_root"], cwd=ROOT, + check=True, + capture_output=True, text=True, ) - return Path(output.strip()) + return Path(result.stdout.strip()) def bazel_output_base() -> Path: - output = subprocess.check_output( - bazel_command("info", "output_base"), + result = subprocess.run( + ["bazel", "info", "output_base"], cwd=ROOT, + check=True, + capture_output=True, text=True, ) - return Path(output.strip()) + return Path(result.stdout.strip()) def bazel_output_path(path: str) -> Path: @@ -61,22 +72,24 @@ def bazel_output_files( ) -> list[Path]: expression = "set(" + " ".join(labels) + ")" bazel_configs = bazel_configs or [] - output = subprocess.check_output( - bazel_command( + result = subprocess.run( + [ + "bazel", "cquery", "-c", compilation_mode, f"--platforms=@llvm//platforms:{platform}", *[f"--config={config}" for config in bazel_configs], + *bazel_remote_args(), "--output=files", expression, - ), + ], cwd=ROOT, + check=True, + capture_output=True, text=True, ) - return [ - bazel_output_path(line.strip()) for line in output.splitlines() if line.strip() - ] + return [bazel_output_path(line.strip()) for line in result.stdout.splitlines() if line.strip()] def bazel_build( @@ -89,15 +102,17 @@ def bazel_build( bazel_configs = bazel_configs or [] download_args = ["--remote_download_toplevel"] if download_toplevel else [] subprocess.run( - bazel_command( + [ + "bazel", "build", "-c", compilation_mode, f"--platforms=@llvm//platforms:{platform}", *[f"--config={config}" for config in bazel_configs], + *bazel_remote_args(), *download_args, *labels, - ), + ], cwd=ROOT, check=True, ) @@ -157,7 +172,7 @@ def resolved_v8_crate_version() -> str: matches = sorted( set( re.findall( - r"https://static\.crates\.io/crates/v8/v8-([0-9]+\.[0-9]+\.[0-9]+)\.crate", + r'https://static\.crates\.io/crates/v8/v8-([0-9]+\.[0-9]+\.[0-9]+)\.crate', module_bazel, ) ) @@ -219,17 +234,13 @@ def stage_artifacts( output_dir: Path, sandbox: bool, ) -> None: - missing_paths = [ - str(path) for path in [lib_path, binding_path] if not path.exists() - ] + missing_paths = [str(path) for path in [lib_path, binding_path] if not path.exists()] if missing_paths: raise SystemExit(f"missing release outputs for {target}: {missing_paths}") output_dir.mkdir(parents=True, exist_ok=True) artifact_profile = SANDBOX_ARTIFACT_PROFILE if sandbox else RELEASE_ARTIFACT_PROFILE - staged_library = output_dir / staged_archive_name( - target, lib_path, artifact_profile - ) + staged_library = output_dir / staged_archive_name(target, lib_path, artifact_profile) staged_binding = output_dir / staged_binding_name(target, artifact_profile) with lib_path.open("rb") as src, staged_library.open("wb") as dst: @@ -259,9 +270,7 @@ def stage_artifacts( def upstream_release_pair_paths(source_root: Path, target: str) -> tuple[Path, Path]: - lib_name = ( - "rusty_v8.lib" if target.endswith("-pc-windows-msvc") else "librusty_v8.a" - ) + lib_name = "rusty_v8.lib" if target.endswith("-pc-windows-msvc") else "librusty_v8.a" gn_out = source_root / "target" / target / "release" / "gn_out" return gn_out / "obj" / lib_name, gn_out / "src_binding.rs" @@ -329,9 +338,7 @@ def parse_args() -> argparse.Namespace: stage_upstream_release_pair_parser = subparsers.add_parser( "stage-upstream-release-pair" ) - stage_upstream_release_pair_parser.add_argument( - "--source-root", type=Path, required=True - ) + stage_upstream_release_pair_parser.add_argument("--source-root", type=Path, required=True) stage_upstream_release_pair_parser.add_argument("--target", required=True) stage_upstream_release_pair_parser.add_argument("--output-dir", required=True) stage_upstream_release_pair_parser.add_argument("--sandbox", action="store_true") diff --git a/.github/scripts/test_run_bazel_with_buildbuddy.py b/.github/scripts/test_run_bazel_with_buildbuddy.py deleted file mode 100644 index bab4ad5cce..0000000000 --- a/.github/scripts/test_run_bazel_with_buildbuddy.py +++ /dev/null @@ -1,184 +0,0 @@ -#!/usr/bin/env python3 - -import json -import unittest -from pathlib import Path -from tempfile import TemporaryDirectory - -import run_bazel_with_buildbuddy - - -class RunBazelWithBuildBuddyTest(unittest.TestCase): - def github_env( - self, - temp_dir: str, - *, - repository: str = "openai/codex", - fork: bool = False, - event_name: str = "pull_request", - ) -> dict[str, str]: - event_path = Path(temp_dir) / "event.json" - event_path.write_text( - json.dumps({"pull_request": {"head": {"repo": {"fork": fork}}}}), - encoding="utf-8", - ) - return { - "BUILDBUDDY_API_KEY": "token", - "GITHUB_ACTIONS": "true", - "GITHUB_EVENT_NAME": event_name, - "GITHUB_EVENT_PATH": str(event_path), - "GITHUB_REPOSITORY": repository, - } - - def test_keyless_invocation_drops_remote_ci_configuration(self) -> None: - self.assertIsNone( - run_bazel_with_buildbuddy.remote_config( - ["build", "--config=ci-linux", "//codex-rs/cli:codex"], - {}, - ) - ) - self.assertEqual( - run_bazel_with_buildbuddy.bazel_args_with_remote_config( - ["build", "--config=ci-linux", "--", "//codex-rs/cli:codex"], - {}, - ), - ["build", "--", "//codex-rs/cli:codex"], - ) - - def test_program_arguments_after_separator_do_not_select_or_lose_rbe(self) -> None: - args = ["run", "//codex-rs/cli:codex", "--", "--config=remote"] - - self.assertEqual( - run_bazel_with_buildbuddy.bazel_args_with_remote_config(args, {}), - args, - ) - self.assertEqual( - run_bazel_with_buildbuddy.remote_config( - args, {"BUILDBUDDY_API_KEY": "fork-token"} - ), - "buildbuddy-generic", - ) - - def test_upstream_push_selects_openai_rbe_before_target_separator(self) -> None: - with TemporaryDirectory() as temp_dir: - env = self.github_env(temp_dir, event_name="push") - - self.assertEqual( - run_bazel_with_buildbuddy.bazel_args_with_remote_config( - ["build", "--config=ci-linux", "--", "//codex-rs/cli:codex"], - env, - ), - [ - "build", - "--config=buildbuddy-openai-rbe", - "--remote_header=x-buildbuddy-api-key=token", - "--config=ci-linux", - "--", - "//codex-rs/cli:codex", - ], - ) - - def test_windows_cross_ci_configuration_follows_remote_configuration(self) -> None: - env = {"BUILDBUDDY_API_KEY": "fork-token"} - - self.assertEqual( - run_bazel_with_buildbuddy.bazel_args_with_remote_config( - ["build", "--config=ci-windows-cross", "//codex-rs/cli:codex"], - env, - ), - [ - "build", - "--config=buildbuddy-generic-rbe", - "--remote_header=x-buildbuddy-api-key=fork-token", - "--config=ci-windows-cross", - "//codex-rs/cli:codex", - ], - ) - - def test_query_remote_configuration_is_inserted_before_expression(self) -> None: - expression = 'kind("rust_library rule", //codex-rs/...)' - env = {"BUILDBUDDY_API_KEY": "fork-token"} - - for command in ("query", "cquery", "aquery"): - with self.subTest(command=command): - self.assertEqual( - run_bazel_with_buildbuddy.bazel_args_with_remote_config( - [ - command, - "--config=ci-windows-cross", - "--output=label", - expression, - ], - env, - ), - [ - command, - "--config=buildbuddy-generic-rbe", - "--remote_header=x-buildbuddy-api-key=fork-token", - "--config=ci-windows-cross", - "--output=label", - expression, - ], - ) - - def test_same_repository_pull_request_selects_openai_host(self) -> None: - with TemporaryDirectory() as temp_dir: - self.assertEqual( - run_bazel_with_buildbuddy.remote_config( - ["build", "--config=ci-v8"], self.github_env(temp_dir) - ), - "buildbuddy-openai-rbe", - ) - - def test_fork_pull_request_cannot_select_openai_host(self) -> None: - with TemporaryDirectory() as temp_dir: - env = self.github_env(temp_dir, fork=True) - - self.assertEqual( - run_bazel_with_buildbuddy.remote_config( - ["build", "--config=ci-v8"], env - ), - "buildbuddy-generic-rbe", - ) - - def test_run_in_fork_repository_cannot_select_openai_host(self) -> None: - with TemporaryDirectory() as temp_dir: - env = self.github_env(temp_dir, repository="contributor/codex") - - self.assertEqual( - run_bazel_with_buildbuddy.remote_config( - ["build", "--config=ci-v8"], env - ), - "buildbuddy-generic-rbe", - ) - - def test_pull_request_without_readable_event_payload_fails_closed(self) -> None: - for event_path in (None, "missing-event.json"): - env = { - "BUILDBUDDY_API_KEY": "token", - "GITHUB_ACTIONS": "true", - "GITHUB_EVENT_NAME": "pull_request", - "GITHUB_REPOSITORY": "openai/codex", - } - if event_path is not None: - env["GITHUB_EVENT_PATH"] = event_path - - with self.subTest(event_path=event_path): - self.assertEqual( - run_bazel_with_buildbuddy.remote_config(["build"], env), - "buildbuddy-generic", - ) - - def test_bazel_command_uses_configured_binary_locally(self) -> None: - self.assertEqual( - run_bazel_with_buildbuddy.bazel_command( - "info", - "execution_root", - env={"CODEX_BAZEL_BIN": "fake-bazel"}, - ), - ["fake-bazel", "info", "execution_root"], - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/.github/scripts/test_rusty_v8_bazel.py b/.github/scripts/test_rusty_v8_bazel.py index 0b5c03f436..19690dbece 100644 --- a/.github/scripts/test_rusty_v8_bazel.py +++ b/.github/scripts/test_rusty_v8_bazel.py @@ -88,49 +88,24 @@ class RustyV8BazelTest(unittest.TestCase): ), ) - def test_bazel_commands_use_shared_buildbuddy_remote_config_library(self) -> None: - with patch.dict(environ, {}, clear=True): + def test_bazel_remote_args_include_buildbuddy_header_when_present(self) -> None: + with patch.dict(environ, {"BUILDBUDDY_API_KEY": "token"}, clear=False): self.assertEqual( - [ - "bazel", - "build", - "//third_party/v8:release", - ], - rusty_v8_bazel.bazel_command( - "build", - "--config=ci-v8", - "//third_party/v8:release", - ), - ) - with patch.dict(environ, {"BUILDBUDDY_API_KEY": "token"}, clear=True): - self.assertEqual( - [ - "bazel", - "build", - "--config=buildbuddy-generic-rbe", - "--remote_header=x-buildbuddy-api-key=token", - "--config=ci-v8", - "//third_party/v8:release", - ], - rusty_v8_bazel.bazel_command( - "build", - "--config=ci-v8", - "//third_party/v8:release", - ), + ["--remote_header=x-buildbuddy-api-key=token"], + rusty_v8_bazel.bazel_remote_args(), ) - def test_release_pair_labels_and_staged_names_distinguish_sandbox_artifacts( - self, - ) -> None: + with patch.dict(environ, {}, clear=True): + self.assertEqual([], rusty_v8_bazel.bazel_remote_args()) + + def test_release_pair_labels_and_staged_names_distinguish_sandbox_artifacts(self) -> None: self.assertEqual( "//third_party/v8:rusty_v8_release_pair_x86_64_unknown_linux_musl", rusty_v8_bazel.release_pair_label("x86_64-unknown-linux-musl"), ) self.assertEqual( "//third_party/v8:rusty_v8_sandbox_release_pair_x86_64_unknown_linux_musl", - rusty_v8_bazel.release_pair_label( - "x86_64-unknown-linux-musl", sandbox=True - ), + rusty_v8_bazel.release_pair_label("x86_64-unknown-linux-musl", sandbox=True), ) self.assertEqual( "//third_party/v8:rusty_v8_sandbox_release_pair_x86_64_apple_darwin", @@ -230,7 +205,11 @@ class RustyV8BazelTest(unittest.TestCase): with TemporaryDirectory() as source_dir, TemporaryDirectory() as output_dir: source_root = Path(source_dir) gn_out = ( - source_root / "target" / "x86_64-pc-windows-msvc" / "release" / "gn_out" + source_root + / "target" + / "x86_64-pc-windows-msvc" + / "release" + / "gn_out" ) (gn_out / "obj").mkdir(parents=True) (gn_out / "obj" / "rusty_v8.lib").write_bytes(b"archive") diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml index e2782b4904..153ace0fc9 100644 --- a/.github/workflows/bazel.yml +++ b/.github/workflows/bazel.yml @@ -15,7 +15,6 @@ concurrency: # See https://docs.github.com/en/actions/using-jobs/using-concurrency and https://docs.github.com/en/actions/learn-github-actions/contexts for more info. group: concurrency-group::${{ github.workflow }}::${{ github.event.pull_request.number > 0 && format('pr-{0}', github.event.pull_request.number) || github.ref_name }}${{ github.ref_name == 'main' && format('::{0}', github.run_id) || ''}} cancel-in-progress: ${{ github.ref_name != 'main' }} - jobs: test: # PRs use the sharded Windows cross-compiled test jobs below. Post-merge @@ -56,17 +55,12 @@ jobs: ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} persist-credentials: false - - uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49 - if: matrix.os == 'ubuntu-24.04' && matrix.target == 'x86_64-unknown-linux-gnu' - with: - tool: just - - name: Check rusty_v8 MODULE.bazel checksums if: matrix.os == 'ubuntu-24.04' && matrix.target == 'x86_64-unknown-linux-gnu' shell: bash run: | python3 .github/scripts/rusty_v8_bazel.py check-module-bazel - just test-github-scripts + python3 -m unittest discover -s .github/scripts -p test_rusty_v8_bazel.py - name: Prepare Bazel CI id: prepare_bazel diff --git a/.github/workflows/rusty-v8-release.yml b/.github/workflows/rusty-v8-release.yml index d6fb73e96d..4b5320dcb9 100644 --- a/.github/workflows/rusty-v8-release.yml +++ b/.github/workflows/rusty-v8-release.yml @@ -191,10 +191,11 @@ jobs: bazel_args+=(--config=v8-release-compat) fi - ./.github/scripts/run_bazel_with_buildbuddy.py \ + bazel \ --noexperimental_remote_repo_contents_cache \ "${bazel_args[@]}" \ - "--config=${{ matrix.bazel_config }}" + "--config=${{ matrix.bazel_config }}" \ + "--remote_header=x-buildbuddy-api-key=${BUILDBUDDY_API_KEY}" - name: Stage release pair env: diff --git a/.github/workflows/v8-canary.yml b/.github/workflows/v8-canary.yml index 0ad9f850d1..71ce5d7865 100644 --- a/.github/workflows/v8-canary.yml +++ b/.github/workflows/v8-canary.yml @@ -5,7 +5,6 @@ on: paths: - ".bazelrc" - ".github/actions/setup-bazel-ci/**" - - ".github/scripts/run_bazel_with_buildbuddy.py" - ".github/scripts/rusty_v8_bazel.py" - ".github/scripts/rusty_v8_module_bazel.py" - ".github/workflows/rusty-v8-release.yml" @@ -24,7 +23,6 @@ on: paths: - ".bazelrc" - ".github/actions/setup-bazel-ci/**" - - ".github/scripts/run_bazel_with_buildbuddy.py" - ".github/scripts/rusty_v8_bazel.py" - ".github/scripts/rusty_v8_module_bazel.py" - ".github/workflows/rusty-v8-release.yml" @@ -205,10 +203,11 @@ jobs: bazel_args+=(--config=v8-release-compat) fi - ./.github/scripts/run_bazel_with_buildbuddy.py \ + bazel \ --noexperimental_remote_repo_contents_cache \ "${bazel_args[@]}" \ - "--config=${{ matrix.bazel_config }}" + "--config=${{ matrix.bazel_config }}" \ + "--remote_header=x-buildbuddy-api-key=${BUILDBUDDY_API_KEY}" - name: Stage release pair env: diff --git a/codex-rs/docs/bazel.md b/codex-rs/docs/bazel.md index 085c15992f..a124688a24 100644 --- a/codex-rs/docs/bazel.md +++ b/codex-rs/docs/bazel.md @@ -4,7 +4,7 @@ This repository uses Bazel to build the Rust workspace under `codex-rs`. Cargo remains the source of truth for crates and features, while Bazel provides hermetic builds, toolchains, and cross-platform artifacts. -As of 6/1/2026, this setup is still experimental as we stabilize it. +As of 1/9/2026, this setup is still experimental as we stabilize it. ## High-level layout @@ -20,118 +20,6 @@ As of 6/1/2026, this setup is still experimental as we stabilize it. makes some adjustments if the crate needs additional compile-time or runtime data, or other customizations. -## Running Bazel locally - -The repository root `justfile` exposes the common Bazel entry points: - -```bash -just bazel-test -just bazel-clippy -``` - -Ordinary local `bazel` and `just` invocations run locally. BuildBuddy cache, -build event upload, downloads, and remote execution are opt-in configurations. - -## BuildBuddy - -Codex uses BuildBuddy for a shared Bazel cache and remoted builds and tests. To use it -to speed up your builds and tests you'll need to provide an API key and select a -configuration. - -### BuildBuddy API key - -If you're an OpenAI employee, log in to https://openai.buildbuddy.io and use Google sign-in. - -Create a BuildBuddy API key as described in BuildBuddy's [Authentication Guide][bb-auth-guide], -then add it to `~/.bazelrc`: - -```bazelrc -# Local machine only; this file contains a BuildBuddy credential. -common --remote_header=x-buildbuddy-api-key= -``` - -Keeping the credential outside the workspace reduces the risk of accidentally -committing it. - -If you need different API keys for different projects, put the API key in -`%workspace%/user.bazelrc` instead. The checked-in `.bazelrc` optionally imports -that file, and `.gitignore` excludes it. Do not commit or share a file containing -the credential. - -[bb-auth-guide]: https://www.buildbuddy.io/docs/guide-auth/#managing-keys - -### Selecting a remote build configuration - -OpenAI employees should default to the OpenAI host with remote execution unless -they have a reason to choose another configuration. Add the following configuration -to `%workspace%/user.bazelrc`: - -```bazelrc -common --config=buildbuddy-openai-rbe -``` - -OpenAI employees who don't want remote execution can use `buildbuddy-openai`. External users -should use `buildbuddy-generic-rbe` or `buildbuddy-generic`. See below for details on these -configurations. - -### All remote configurations - -GitHub Actions routes Bazel build and output-resolution commands through -`.github/scripts/run_bazel_with_buildbuddy.py`. Higher-level helpers such as -`.github/scripts/run-bazel-ci.sh` and `.github/scripts/rusty_v8_bazel.py` -delegate remote configuration selection to that wrapper. The wrapper reads the -GitHub Actions repository and event payload rather than relying on workflow -files to duplicate tenant-selection logic. - -Loading-phase target-discovery `bazel query` commands run locally because they -only enumerate labels and do not need remote caches or execution. - -The `Cache/BES` host is also used for remote downloads. - -| Invocation/config | Key Required | Cache/BES | Build exec | Test exec | -| --- | --- | --- | --- | --- | -| `bazel ...` | No | None | Local | Local | -| `bazel ... --config=buildbuddy-generic` | Yes | `remote.buildbuddy.io` | Local | Local | -| `bazel ... --config=buildbuddy-generic-rbe` | Yes | `remote.buildbuddy.io` | Remote | Remote | -| `bazel ... --config=buildbuddy-openai` | Yes | `openai.buildbuddy.io` | Local | Local | -| `bazel ... --config=buildbuddy-openai-rbe` | Yes | `openai.buildbuddy.io` | Remote | Remote | - -Without an API key, the wrapper removes remote CI configurations and runs -locally. With a key, workflows choose the host as follows: - -| Run | Key | Uses OpenAI BuildBuddy Host | -| --- | --- | --- | -| Push to `main` in `openai/codex` | Yes | Yes | -| `workflow_dispatch` in `openai/codex` | Yes | Yes | -| Same-repository pull request in `openai/codex` | Yes | Yes | -| Fork pull request into `openai/codex` | No | No; local | -| Push or `workflow_dispatch` in a fork with a key | Yes | No; generic host | -| Pull request run in a fork repository with a key | Yes | No; generic host | - -CI configurations determine whether builds and tests execute remotely: - -| CI config | Remote config | Build exec | Test exec | -| --- | --- | --- | --- | -| `ci-linux` | `*-rbe` | Remote host | Remote host | -| `ci-v8` | `*-rbe` | Remote host | Remote host | -| `ci-macos` | `*-rbe` | Remote host | Local | -| `ci-windows-cross` | `*-rbe` | Remote host | Local | -| `ci-windows` | non-RBE | Local | Local | -| Keyless CI fallback | none | Local | Local | - -To exercise the generic remote configuration with your key: - -```bash -BUILDBUDDY_API_KEY=... GITHUB_REPOSITORY=my-fork/codex \ - ./.github/scripts/run_bazel_with_buildbuddy.py \ - build --config=ci-linux //codex-rs/cli:codex -``` - -The wrapper selects the OpenAI host only inside GitHub Actions for a trusted -run in `openai/codex`. A missing or malformed pull request event -payload fails closed to the generic host. For local OpenAI host access, use -the `user.bazelrc` configuration above. - ## Evolving the setup When you add or change Rust dependencies, update the Cargo.toml/Cargo.lock as normal. diff --git a/justfile b/justfile index fe7e7349b3..34b5115ee0 100644 --- a/justfile +++ b/justfile @@ -83,12 +83,6 @@ test *args: $env:RUST_MIN_STACK = "{{ rust_min_stack }}"; cargo nextest run --no-fail-fast @($args | Select-Object -Skip 1) just bench-smoke -# Run from the repository root so scripts that resolve paths from `cwd` see -# the same layout they use in GitHub Actions. -[no-cd] -test-github-scripts: - {{ python }} -m unittest discover -s {{ justfile_directory() }}/.github/scripts -p 'test_*.py' - # Run explicit workspace benchmark targets. bench *args: cargo bench --workspace --bench '*' {args} @@ -135,8 +129,11 @@ bazel-clippy: bazel-argument-comment-lint: bazel build --config=argument-comment-lint -- $({{ justfile_directory() }}/tools/argument-comment-lint/list-bazel-targets.sh) +bazel-remote-test: + bazel test --test_tag_filters=-argument-comment-lint //... --config=remote --platforms=//:rbe --keep_going + build-for-release: - bazel build //codex-rs/cli:release_binaries + bazel build //codex-rs/cli:release_binaries --config=remote # Run the MCP server mcp-server-run *args: diff --git a/scripts/list-bazel-clippy-targets.sh b/scripts/list-bazel-clippy-targets.sh index d12a256d00..b76fc2a830 100755 --- a/scripts/list-bazel-clippy-targets.sh +++ b/scripts/list-bazel-clippy-targets.sh @@ -20,13 +20,23 @@ while [[ $# -gt 0 ]]; do done # Resolve the dynamic targets before printing anything so callers do not -# continue with a partial list if `bazel query` fails. Target discovery is -# local on all platforms. -manual_rust_test_targets="$( - ./.github/scripts/run-bazel-query-ci.sh \ - --output=label \ - -- 'kind("rust_test rule", attr(tags, "manual", //codex-rs/... except //codex-rs/v8-poc/...))' -)" +# continue with a partial list if `bazel query` fails. Reuse the same CI Bazel +# server settings as the subsequent build so Windows jobs do not cold-start a +# second Bazel server just for target discovery. +if [[ $windows_cross_compile -eq 1 ]]; then + manual_rust_test_targets="$( + ./.github/scripts/run-bazel-query-ci.sh \ + --windows-cross-compile \ + --output=label \ + -- 'kind("rust_test rule", attr(tags, "manual", //codex-rs/... except //codex-rs/v8-poc/...))' + )" +else + manual_rust_test_targets="$( + ./.github/scripts/run-bazel-query-ci.sh \ + --output=label \ + -- 'kind("rust_test rule", attr(tags, "manual", //codex-rs/... except //codex-rs/v8-poc/...))' + )" +fi if [[ "${RUNNER_OS:-}" != "Windows" ]]; then # Non-Windows clippy jobs lint the native test binaries; the # Windows-cross binaries exist only for the fast Windows test leg. From cee9671646aa5456c34d398a2b6070e47d702184 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Tue, 2 Jun 2026 11:14:14 -0700 Subject: [PATCH 2/3] core: stop threading SandboxPolicy through exec Replace the exec-side legacy SandboxPolicy plumbing with PermissionProfile-based Windows sandbox override resolution. The runtime still needs a legacy SandboxPolicy projection for a few compatibility surfaces and for Windows override baseline comparisons, but that projection is now derived from a single PermissionProfile instead of accepting separately materialized FileSystemSandboxPolicy and NetworkSandboxPolicy inputs. This avoids mismatched permission arguments while keeping the migration scoped to the existing compatibility boundary. Keep the Windows exec tests aligned with that shape by constructing PermissionProfile values through the highest-level available helpers instead of routing test setup through SandboxPolicy compatibility conversions. Validation: - just test -p codex-sandboxing - just test -p codex-core windows_restricted_token - CI for the follow-up exec_tests cleanup --- .../src/request_processors/thread_summary.rs | 3 - codex-rs/core/src/codex_thread.rs | 3 - codex-rs/core/src/config/mod.rs | 8 +- codex-rs/core/src/exec.rs | 99 ++++---- codex-rs/core/src/exec_tests.rs | 238 ++++++++---------- codex-rs/core/src/sandboxing/mod.rs | 11 - codex-rs/core/src/session/session.rs | 16 +- codex-rs/core/src/session/turn_context.rs | 4 - codex-rs/core/src/spawn.rs | 2 +- codex-rs/sandboxing/src/manager.rs | 5 +- 10 files changed, 156 insertions(+), 233 deletions(-) diff --git a/codex-rs/app-server/src/request_processors/thread_summary.rs b/codex-rs/app-server/src/request_processors/thread_summary.rs index da7ae54804..a57f2fcf79 100644 --- a/codex-rs/app-server/src/request_processors/thread_summary.rs +++ b/codex-rs/app-server/src/request_processors/thread_summary.rs @@ -179,11 +179,8 @@ pub(crate) fn thread_response_sandbox_policy( permission_profile: &codex_protocol::models::PermissionProfile, cwd: &Path, ) -> codex_app_server_protocol::SandboxPolicy { - let file_system_policy = permission_profile.file_system_sandbox_policy(); let sandbox_policy = codex_sandboxing::compatibility_sandbox_policy_for_permission_profile( permission_profile, - &file_system_policy, - permission_profile.network_sandbox_policy(), cwd, ); sandbox_policy.into() diff --git a/codex-rs/core/src/codex_thread.rs b/codex-rs/core/src/codex_thread.rs index cd90829c0c..5035796767 100644 --- a/codex-rs/core/src/codex_thread.rs +++ b/codex-rs/core/src/codex_thread.rs @@ -76,11 +76,8 @@ pub struct ThreadConfigSnapshot { impl ThreadConfigSnapshot { pub fn sandbox_policy(&self) -> SandboxPolicy { - let file_system_sandbox_policy = self.permission_profile.file_system_sandbox_policy(); codex_sandboxing::compatibility_sandbox_policy_for_permission_profile( &self.permission_profile, - &file_system_sandbox_policy, - self.permission_profile.network_sandbox_policy(), self.cwd.as_path(), ) } diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 051064f279..e008328763 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -444,13 +444,7 @@ impl Permissions { /// Legacy compatibility projection derived from the canonical profile. pub fn legacy_sandbox_policy(&self, cwd: &Path) -> SandboxPolicy { let permission_profile = self.materialized_permission_profile(); - let file_system_sandbox_policy = permission_profile.file_system_sandbox_policy(); - compatibility_sandbox_policy_for_permission_profile( - &permission_profile, - &file_system_sandbox_policy, - permission_profile.network_sandbox_policy(), - cwd, - ) + compatibility_sandbox_policy_for_permission_profile(&permission_profile, cwd) } /// Check whether a legacy sandbox policy can be applied to this permission diff --git a/codex-rs/core/src/exec.rs b/codex-rs/core/src/exec.rs index e050552246..aa3edc3dc1 100644 --- a/codex-rs/core/src/exec.rs +++ b/codex-rs/core/src/exec.rs @@ -31,19 +31,18 @@ use codex_protocol::error::SandboxErr; use codex_protocol::exec_output::ExecToolCallOutput; use codex_protocol::exec_output::StreamOutput; use codex_protocol::models::PermissionProfile; -use codex_protocol::permissions::FileSystemSandboxKind; use codex_protocol::permissions::FileSystemSandboxPolicy; use codex_protocol::permissions::NetworkSandboxPolicy; use codex_protocol::protocol::Event; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::ExecCommandOutputDeltaEvent; use codex_protocol::protocol::ExecOutputStream; -use codex_protocol::protocol::SandboxPolicy; use codex_sandboxing::SandboxCommand; use codex_sandboxing::SandboxManager; use codex_sandboxing::SandboxTransformRequest; use codex_sandboxing::SandboxType; use codex_sandboxing::SandboxablePreference; +use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile; use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_pty::DEFAULT_OUTPUT_BYTES_CAP; use codex_utils_pty::process_group::kill_child_process_group; @@ -419,22 +418,17 @@ pub fn build_exec_request( exec_req.windows_sandbox_level, exec_req.network.is_some(), ); - let sandbox_policy = exec_req.compatibility_sandbox_policy(); exec_req.windows_sandbox_filesystem_overrides = if use_windows_elevated_backend { resolve_windows_elevated_filesystem_overrides( exec_req.sandbox, - &sandbox_policy, - &exec_req.file_system_sandbox_policy, - exec_req.network_sandbox_policy, + &exec_req.permission_profile, sandbox_cwd, use_windows_elevated_backend, ) } else { resolve_windows_restricted_token_filesystem_overrides( exec_req.sandbox, - &sandbox_policy, - &exec_req.file_system_sandbox_policy, - exec_req.network_sandbox_policy, + &exec_req.permission_profile, sandbox_cwd, exec_req.windows_sandbox_level, ) @@ -1004,34 +998,28 @@ async fn exec( } #[cfg_attr(not(target_os = "windows"), allow(dead_code))] -fn should_use_windows_restricted_token_sandbox( - sandbox: SandboxType, - sandbox_policy: &SandboxPolicy, - file_system_sandbox_policy: &FileSystemSandboxPolicy, +fn permission_profile_supports_windows_restricted_token_sandbox( + permission_profile: &PermissionProfile, ) -> bool { - sandbox == SandboxType::WindowsRestrictedToken - && file_system_sandbox_policy.kind == FileSystemSandboxKind::Restricted - && !matches!( - sandbox_policy, - SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } - ) + match permission_profile { + PermissionProfile::Managed { file_system, .. } => { + !file_system.to_sandbox_policy().has_full_disk_write_access() + } + PermissionProfile::Disabled | PermissionProfile::External { .. } => false, + } } #[cfg_attr(not(test), allow(dead_code))] pub(crate) fn unsupported_windows_restricted_token_sandbox_reason( sandbox: SandboxType, - sandbox_policy: &SandboxPolicy, - file_system_sandbox_policy: &FileSystemSandboxPolicy, - network_sandbox_policy: NetworkSandboxPolicy, + permission_profile: &PermissionProfile, sandbox_policy_cwd: &AbsolutePathBuf, windows_sandbox_level: WindowsSandboxLevel, ) -> Option { if windows_sandbox_level == WindowsSandboxLevel::Elevated { resolve_windows_elevated_filesystem_overrides( sandbox, - sandbox_policy, - file_system_sandbox_policy, - network_sandbox_policy, + permission_profile, sandbox_policy_cwd, windows_sandbox_level == WindowsSandboxLevel::Elevated, ) @@ -1039,9 +1027,7 @@ pub(crate) fn unsupported_windows_restricted_token_sandbox_reason( } else { resolve_windows_restricted_token_filesystem_overrides( sandbox, - sandbox_policy, - file_system_sandbox_policy, - network_sandbox_policy, + permission_profile, sandbox_policy_cwd, windows_sandbox_level, ) @@ -1051,9 +1037,7 @@ pub(crate) fn unsupported_windows_restricted_token_sandbox_reason( pub(crate) fn resolve_windows_restricted_token_filesystem_overrides( sandbox: SandboxType, - sandbox_policy: &SandboxPolicy, - file_system_sandbox_policy: &FileSystemSandboxPolicy, - network_sandbox_policy: NetworkSandboxPolicy, + permission_profile: &PermissionProfile, sandbox_policy_cwd: &AbsolutePathBuf, windows_sandbox_level: WindowsSandboxLevel, ) -> std::result::Result, String> { @@ -1063,25 +1047,21 @@ pub(crate) fn resolve_windows_restricted_token_filesystem_overrides( return Ok(None); } + let (file_system_sandbox_policy, network_sandbox_policy) = + permission_profile.to_runtime_permissions(); + let needs_direct_runtime_enforcement = file_system_sandbox_policy .needs_direct_runtime_enforcement(network_sandbox_policy, sandbox_policy_cwd); - if should_use_windows_restricted_token_sandbox( - sandbox, - sandbox_policy, - file_system_sandbox_policy, - ) && !needs_direct_runtime_enforcement + if permission_profile_supports_windows_restricted_token_sandbox(permission_profile) + && !needs_direct_runtime_enforcement { return Ok(None); } - if !should_use_windows_restricted_token_sandbox( - sandbox, - sandbox_policy, - file_system_sandbox_policy, - ) { + if !permission_profile_supports_windows_restricted_token_sandbox(permission_profile) { return Err(format!( - "windows sandbox backend cannot enforce file_system={:?}, network={network_sandbox_policy:?}, legacy_policy={sandbox_policy:?}; refusing to run unsandboxed", + "windows sandbox backend cannot enforce file_system={:?}, network={network_sandbox_policy:?}, permission_profile={permission_profile:?}; refusing to run unsandboxed", file_system_sandbox_policy.kind, )); } @@ -1090,7 +1070,7 @@ pub(crate) fn resolve_windows_restricted_token_filesystem_overrides( // but its WRITE_RESTRICTED token does not make capability SID deny-read ACEs // participate in read access checks. Read restrictions therefore require the // elevated backend, even when the filesystem root remains readable. - if !windows_policy_has_root_read_access(file_system_sandbox_policy, sandbox_policy_cwd) { + if !windows_policy_has_root_read_access(&file_system_sandbox_policy, sandbox_policy_cwd) { return Err( "windows unelevated restricted-token sandbox cannot enforce split filesystem read restrictions directly; refusing to run unsandboxed" .to_string(), @@ -1098,7 +1078,7 @@ pub(crate) fn resolve_windows_restricted_token_filesystem_overrides( } let additional_deny_read_paths = codex_windows_sandbox::resolve_windows_deny_read_paths( - file_system_sandbox_policy, + &file_system_sandbox_policy, sandbox_policy_cwd, )?; if !additional_deny_read_paths.is_empty() { @@ -1108,7 +1088,11 @@ pub(crate) fn resolve_windows_restricted_token_filesystem_overrides( ); } - let legacy_writable_roots = sandbox_policy.get_writable_roots_with_cwd(sandbox_policy_cwd); + let legacy_projection = compatibility_sandbox_policy_for_permission_profile( + permission_profile, + sandbox_policy_cwd.as_path(), + ); + let legacy_writable_roots = legacy_projection.get_writable_roots_with_cwd(sandbox_policy_cwd); let split_writable_roots = file_system_sandbox_policy.get_writable_roots_with_cwd(sandbox_policy_cwd); let legacy_root_paths: BTreeSet = legacy_writable_roots @@ -1204,9 +1188,7 @@ fn windows_policy_has_root_read_access( pub(crate) fn resolve_windows_elevated_filesystem_overrides( sandbox: SandboxType, - sandbox_policy: &SandboxPolicy, - file_system_sandbox_policy: &FileSystemSandboxPolicy, - network_sandbox_policy: NetworkSandboxPolicy, + permission_profile: &PermissionProfile, sandbox_policy_cwd: &AbsolutePathBuf, use_windows_elevated_backend: bool, ) -> std::result::Result, String> { @@ -1214,19 +1196,18 @@ pub(crate) fn resolve_windows_elevated_filesystem_overrides( return Ok(None); } - if !should_use_windows_restricted_token_sandbox( - sandbox, - sandbox_policy, - file_system_sandbox_policy, - ) { + let (file_system_sandbox_policy, network_sandbox_policy) = + permission_profile.to_runtime_permissions(); + + if !permission_profile_supports_windows_restricted_token_sandbox(permission_profile) { return Err(format!( - "windows sandbox backend cannot enforce file_system={:?}, network={network_sandbox_policy:?}, legacy_policy={sandbox_policy:?}; refusing to run unsandboxed", + "windows sandbox backend cannot enforce file_system={:?}, network={network_sandbox_policy:?}, permission_profile={permission_profile:?}; refusing to run unsandboxed", file_system_sandbox_policy.kind, )); } let additional_deny_read_paths = codex_windows_sandbox::resolve_windows_deny_read_paths( - file_system_sandbox_policy, + &file_system_sandbox_policy, sandbox_policy_cwd, )?; @@ -1242,7 +1223,11 @@ pub(crate) fn resolve_windows_elevated_filesystem_overrides( let needs_direct_runtime_enforcement = file_system_sandbox_policy .needs_direct_runtime_enforcement(network_sandbox_policy, sandbox_policy_cwd); let normalize_path = |path: PathBuf| dunce::canonicalize(&path).unwrap_or(path); - let legacy_writable_roots = sandbox_policy.get_writable_roots_with_cwd(sandbox_policy_cwd); + let legacy_projection = compatibility_sandbox_policy_for_permission_profile( + permission_profile, + sandbox_policy_cwd.as_path(), + ); + let legacy_writable_roots = legacy_projection.get_writable_roots_with_cwd(sandbox_policy_cwd); let legacy_root_paths: BTreeSet = legacy_writable_roots .iter() .map(|root| normalize_path(root.root.to_path_buf())) @@ -1264,7 +1249,7 @@ pub(crate) fn resolve_windows_elevated_filesystem_overrides( // whether the baseline still reads from the filesystem root and only needs // additional deny ACLs layered on top. let split_has_root_read_access = - windows_policy_has_root_read_access(file_system_sandbox_policy, sandbox_policy_cwd); + windows_policy_has_root_read_access(&file_system_sandbox_policy, sandbox_policy_cwd); let read_roots_override = if split_has_root_read_access { None } else { diff --git a/codex-rs/core/src/exec_tests.rs b/codex-rs/core/src/exec_tests.rs index 1c71d02651..b17a3f9c91 100644 --- a/codex-rs/core/src/exec_tests.rs +++ b/codex-rs/core/src/exec_tests.rs @@ -379,34 +379,18 @@ async fn process_exec_tool_call_preserves_full_buffer_capture_policy() -> Result #[test] fn windows_restricted_token_skips_external_sandbox_policies() { - let policy = SandboxPolicy::ExternalSandbox { - network_access: codex_protocol::protocol::NetworkAccess::Restricted, + let permission_profile = PermissionProfile::External { + network: NetworkSandboxPolicy::Restricted, }; - let file_system_policy = FileSystemSandboxPolicy::from(&policy); - assert_eq!( - should_use_windows_restricted_token_sandbox( - SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - ), - false - ); + assert!(!permission_profile_supports_windows_restricted_token_sandbox(&permission_profile)); } #[test] -fn windows_restricted_token_runs_for_legacy_restricted_policies() { - let policy = SandboxPolicy::new_read_only_policy(); - let file_system_policy = FileSystemSandboxPolicy::from(&policy); +fn windows_restricted_token_supports_read_only_profiles() { + let permission_profile = PermissionProfile::read_only(); - assert_eq!( - should_use_windows_restricted_token_sandbox( - SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - ), - true - ); + assert!(permission_profile_supports_windows_restricted_token_sandbox(&permission_profile)); } #[test] @@ -427,39 +411,63 @@ fn windows_proxy_enforcement_uses_elevated_backend() { #[test] fn windows_restricted_token_rejects_network_only_restrictions() { - let policy = SandboxPolicy::ExternalSandbox { - network_access: codex_protocol::protocol::NetworkAccess::Restricted, - }; - let file_system_policy = FileSystemSandboxPolicy::unrestricted(); + let permission_profile = PermissionProfile::from_runtime_permissions( + &FileSystemSandboxPolicy::unrestricted(), + NetworkSandboxPolicy::Restricted, + ); let sandbox_policy_cwd = AbsolutePathBuf::current_dir().expect("cwd"); assert_eq!( unsupported_windows_restricted_token_sandbox_reason( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, &sandbox_policy_cwd, WindowsSandboxLevel::RestrictedToken, ), Some( - "windows sandbox backend cannot enforce file_system=Unrestricted, network=Restricted, legacy_policy=ExternalSandbox { network_access: Restricted }; refusing to run unsandboxed".to_string() + "windows sandbox backend cannot enforce file_system=Unrestricted, network=Restricted, permission_profile=Managed { file_system: Unrestricted, network: Restricted }; refusing to run unsandboxed".to_string() ) ); } #[test] -fn windows_restricted_token_allows_legacy_restricted_policies() { - let policy = SandboxPolicy::new_read_only_policy(); - let file_system_policy = FileSystemSandboxPolicy::from(&policy); +fn windows_restricted_token_rejects_managed_root_write_profiles() { + let file_system_policy = FileSystemSandboxPolicy::restricted(vec![ + codex_protocol::permissions::FileSystemSandboxEntry { + path: codex_protocol::permissions::FileSystemPath::Special { + value: codex_protocol::permissions::FileSystemSpecialPath::Root, + }, + access: codex_protocol::permissions::FileSystemAccessMode::Write, + }, + ]); + let permission_profile = PermissionProfile::from_runtime_permissions( + &file_system_policy, + NetworkSandboxPolicy::Restricted, + ); let sandbox_policy_cwd = AbsolutePathBuf::current_dir().expect("cwd"); assert_eq!( unsupported_windows_restricted_token_sandbox_reason( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, + &sandbox_policy_cwd, + WindowsSandboxLevel::RestrictedToken, + ), + Some(format!( + "windows sandbox backend cannot enforce file_system=Restricted, network=Restricted, permission_profile={permission_profile:?}; refusing to run unsandboxed", + )) + ); +} + +#[test] +fn windows_restricted_token_allows_read_only_profiles() { + let permission_profile = PermissionProfile::read_only(); + let sandbox_policy_cwd = AbsolutePathBuf::current_dir().expect("cwd"); + + assert_eq!( + unsupported_windows_restricted_token_sandbox_reason( + SandboxType::WindowsRestrictedToken, + &permission_profile, &sandbox_policy_cwd, WindowsSandboxLevel::RestrictedToken, ), @@ -468,22 +476,19 @@ fn windows_restricted_token_allows_legacy_restricted_policies() { } #[test] -fn windows_restricted_token_allows_legacy_workspace_write_policies() { - let policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; - let file_system_policy = FileSystemSandboxPolicy::from(&policy); +fn windows_restricted_token_allows_workspace_write_profiles() { + let permission_profile = PermissionProfile::workspace_write_with( + &[], + NetworkSandboxPolicy::Restricted, + /*exclude_tmpdir_env_var*/ true, + /*exclude_slash_tmp*/ true, + ); let sandbox_policy_cwd = AbsolutePathBuf::current_dir().expect("cwd"); assert_eq!( unsupported_windows_restricted_token_sandbox_reason( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, &sandbox_policy_cwd, WindowsSandboxLevel::RestrictedToken, ), @@ -499,22 +504,21 @@ fn windows_elevated_allows_split_restricted_read_policies() { ) .expect("absolute docs"); std::fs::create_dir_all(docs.as_path()).expect("create docs"); - let policy = SandboxPolicy::ReadOnly { - network_access: false, - }; let file_system_policy = FileSystemSandboxPolicy::restricted(vec![ codex_protocol::permissions::FileSystemSandboxEntry { path: codex_protocol::permissions::FileSystemPath::Path { path: docs }, access: codex_protocol::permissions::FileSystemAccessMode::Read, }, ]); + let permission_profile = PermissionProfile::from_runtime_permissions( + &file_system_policy, + NetworkSandboxPolicy::Restricted, + ); assert_eq!( unsupported_windows_restricted_token_sandbox_reason( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, &temp_dir.path().abs(), WindowsSandboxLevel::Elevated, ), @@ -527,12 +531,6 @@ fn windows_restricted_token_rejects_split_only_filesystem_policies() { let temp_dir = tempfile::TempDir::new().expect("tempdir"); let docs = temp_dir.path().join("docs"); std::fs::create_dir_all(&docs).expect("create docs"); - let policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; let file_system_policy = FileSystemSandboxPolicy::restricted(vec![ codex_protocol::permissions::FileSystemSandboxEntry { path: codex_protocol::permissions::FileSystemPath::Special { @@ -550,13 +548,15 @@ fn windows_restricted_token_rejects_split_only_filesystem_policies() { access: codex_protocol::permissions::FileSystemAccessMode::Read, }, ]); + let permission_profile = PermissionProfile::from_runtime_permissions( + &file_system_policy, + NetworkSandboxPolicy::Restricted, + ); assert_eq!( unsupported_windows_restricted_token_sandbox_reason( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, &temp_dir.path().abs(), WindowsSandboxLevel::RestrictedToken, ), @@ -572,12 +572,6 @@ fn windows_restricted_token_rejects_root_write_read_only_carveouts() { let temp_dir = tempfile::TempDir::new().expect("tempdir"); let docs = temp_dir.path().join("docs"); std::fs::create_dir_all(&docs).expect("create docs"); - let policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; let file_system_policy = FileSystemSandboxPolicy::restricted(vec![ codex_protocol::permissions::FileSystemSandboxEntry { path: codex_protocol::permissions::FileSystemPath::Special { @@ -593,13 +587,15 @@ fn windows_restricted_token_rejects_root_write_read_only_carveouts() { access: codex_protocol::permissions::FileSystemAccessMode::Read, }, ]); + let permission_profile = PermissionProfile::from_runtime_permissions( + &file_system_policy, + NetworkSandboxPolicy::Restricted, + ); assert_eq!( unsupported_windows_restricted_token_sandbox_reason( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, &temp_dir.path().abs(), WindowsSandboxLevel::RestrictedToken, ), @@ -618,12 +614,6 @@ fn windows_restricted_token_supports_full_read_split_write_read_carveouts() { .abs(); let docs = cwd.join("docs"); std::fs::create_dir_all(docs.as_path()).expect("create docs"); - let policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; let file_system_policy = FileSystemSandboxPolicy::restricted(vec![ codex_protocol::permissions::FileSystemSandboxEntry { path: codex_protocol::permissions::FileSystemPath::Special { @@ -644,17 +634,20 @@ fn windows_restricted_token_supports_full_read_split_write_read_carveouts() { access: codex_protocol::permissions::FileSystemAccessMode::Read, }, ]); + let permission_profile = PermissionProfile::from_runtime_permissions( + &file_system_policy, + NetworkSandboxPolicy::Restricted, + ); - // The legacy workspace-write root already protects top-level `.codex`, so - // the restricted-token overlay only needs the extra read-only docs carveout. + // The workspace-write compatibility projection already protects top-level + // `.codex`, so the restricted-token overlay only needs the extra read-only + // docs carveout. let expected_deny_write_paths = vec![docs]; assert_eq!( resolve_windows_restricted_token_filesystem_overrides( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, &cwd, WindowsSandboxLevel::RestrictedToken, ), @@ -676,12 +669,6 @@ fn windows_restricted_token_rejects_unreadable_split_carveouts() { .abs(); let blocked = cwd.join("blocked"); std::fs::create_dir_all(blocked.as_path()).expect("create blocked"); - let policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; let file_system_policy = FileSystemSandboxPolicy::restricted(vec![ codex_protocol::permissions::FileSystemSandboxEntry { path: codex_protocol::permissions::FileSystemPath::Special { @@ -702,13 +689,15 @@ fn windows_restricted_token_rejects_unreadable_split_carveouts() { access: codex_protocol::permissions::FileSystemAccessMode::Deny, }, ]); + let permission_profile = PermissionProfile::from_runtime_permissions( + &file_system_policy, + NetworkSandboxPolicy::Restricted, + ); assert_eq!( resolve_windows_restricted_token_filesystem_overrides( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, &cwd, WindowsSandboxLevel::RestrictedToken, ), @@ -725,9 +714,6 @@ fn windows_elevated_supports_split_restricted_read_roots() { let docs = temp_dir.path().join("docs"); std::fs::create_dir_all(&docs).expect("create docs"); let expected_docs = dunce::canonicalize(&docs).expect("canonical docs"); - let policy = SandboxPolicy::ReadOnly { - network_access: false, - }; let file_system_policy = FileSystemSandboxPolicy::restricted(vec![ codex_protocol::permissions::FileSystemSandboxEntry { path: codex_protocol::permissions::FileSystemPath::Path { @@ -737,13 +723,15 @@ fn windows_elevated_supports_split_restricted_read_roots() { access: codex_protocol::permissions::FileSystemAccessMode::Read, }, ]); + let permission_profile = PermissionProfile::from_runtime_permissions( + &file_system_policy, + NetworkSandboxPolicy::Restricted, + ); assert_eq!( resolve_windows_elevated_filesystem_overrides( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, &temp_dir.path().abs(), /*use_windows_elevated_backend*/ true, ), @@ -763,12 +751,6 @@ fn windows_elevated_supports_split_write_read_carveouts() { let docs = temp_dir.path().join("docs"); std::fs::create_dir_all(&docs).expect("create docs"); let expected_docs = dunce::canonicalize(&docs).expect("canonical docs"); - let policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; let file_system_policy = FileSystemSandboxPolicy::restricted(vec![ codex_protocol::permissions::FileSystemSandboxEntry { path: codex_protocol::permissions::FileSystemPath::Special { @@ -792,13 +774,15 @@ fn windows_elevated_supports_split_write_read_carveouts() { access: codex_protocol::permissions::FileSystemAccessMode::Read, }, ]); + let permission_profile = PermissionProfile::from_runtime_permissions( + &file_system_policy, + NetworkSandboxPolicy::Restricted, + ); assert_eq!( resolve_windows_elevated_filesystem_overrides( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, &temp_dir.path().abs(), /*use_windows_elevated_backend*/ true, ), @@ -821,12 +805,6 @@ fn windows_elevated_supports_unreadable_split_carveouts() { let blocked = temp_dir.path().join("blocked"); std::fs::create_dir_all(&blocked).expect("create blocked"); let expected_blocked = dunce::canonicalize(&blocked).expect("canonical blocked"); - let policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; let file_system_policy = FileSystemSandboxPolicy::restricted(vec![ codex_protocol::permissions::FileSystemSandboxEntry { path: codex_protocol::permissions::FileSystemPath::Special { @@ -850,13 +828,15 @@ fn windows_elevated_supports_unreadable_split_carveouts() { access: codex_protocol::permissions::FileSystemAccessMode::Deny, }, ]); + let permission_profile = PermissionProfile::from_runtime_permissions( + &file_system_policy, + NetworkSandboxPolicy::Restricted, + ); assert_eq!( resolve_windows_elevated_filesystem_overrides( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, &temp_dir.path().abs(), /*use_windows_elevated_backend*/ true, ), @@ -884,12 +864,6 @@ fn windows_elevated_supports_unreadable_globs() { let secret = temp_dir.path().join("app").join(".env"); std::fs::create_dir_all(secret.parent().expect("parent")).expect("create parent"); std::fs::write(&secret, "secret").expect("write secret"); - let policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; let file_system_policy = FileSystemSandboxPolicy::restricted(vec![ codex_protocol::permissions::FileSystemSandboxEntry { path: codex_protocol::permissions::FileSystemPath::Special { @@ -912,13 +886,15 @@ fn windows_elevated_supports_unreadable_globs() { access: codex_protocol::permissions::FileSystemAccessMode::Deny, }, ]); + let permission_profile = PermissionProfile::from_runtime_permissions( + &file_system_policy, + NetworkSandboxPolicy::Restricted, + ); assert_eq!( resolve_windows_elevated_filesystem_overrides( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, &temp_dir.path().abs(), /*use_windows_elevated_backend*/ true, ), @@ -941,12 +917,6 @@ fn windows_elevated_rejects_reopened_writable_descendants() { let docs = temp_dir.path().join("docs"); let nested = docs.join("nested"); std::fs::create_dir_all(&nested).expect("create nested"); - let policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }; let file_system_policy = FileSystemSandboxPolicy::restricted(vec![ codex_protocol::permissions::FileSystemSandboxEntry { path: codex_protocol::permissions::FileSystemPath::Special { @@ -977,13 +947,15 @@ fn windows_elevated_rejects_reopened_writable_descendants() { access: codex_protocol::permissions::FileSystemAccessMode::Write, }, ]); + let permission_profile = PermissionProfile::from_runtime_permissions( + &file_system_policy, + NetworkSandboxPolicy::Restricted, + ); assert_eq!( unsupported_windows_restricted_token_sandbox_reason( SandboxType::WindowsRestrictedToken, - &policy, - &file_system_policy, - NetworkSandboxPolicy::Restricted, + &permission_profile, &temp_dir.path().abs(), WindowsSandboxLevel::Elevated, ), diff --git a/codex-rs/core/src/sandboxing/mod.rs b/codex-rs/core/src/sandboxing/mod.rs index f853ea3ba6..8a2a2849fd 100644 --- a/codex-rs/core/src/sandboxing/mod.rs +++ b/codex-rs/core/src/sandboxing/mod.rs @@ -22,10 +22,8 @@ use codex_protocol::models::PermissionProfile; pub use codex_protocol::models::SandboxPermissions; use codex_protocol::permissions::FileSystemSandboxPolicy; use codex_protocol::permissions::NetworkSandboxPolicy; -use codex_protocol::protocol::SandboxPolicy; use codex_sandboxing::SandboxExecRequest; use codex_sandboxing::SandboxType; -use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile; use codex_utils_absolute_path::AbsolutePathBuf; use std::collections::HashMap; @@ -102,15 +100,6 @@ impl ExecRequest { } } - pub(crate) fn compatibility_sandbox_policy(&self) -> SandboxPolicy { - compatibility_sandbox_policy_for_permission_profile( - &self.permission_profile, - &self.file_system_sandbox_policy, - self.network_sandbox_policy, - self.windows_sandbox_policy_cwd.as_path(), - ) - } - pub(crate) fn from_sandbox_exec_request( request: SandboxExecRequest, options: ExecOptions, diff --git a/codex-rs/core/src/session/session.rs b/codex-rs/core/src/session/session.rs index 05773abb8c..3c576baaa6 100644 --- a/codex-rs/core/src/session/session.rs +++ b/codex-rs/core/src/session/session.rs @@ -150,17 +150,11 @@ impl SessionConfiguration { } pub(super) fn sandbox_policy(&self) -> SandboxPolicy { - self.permission_profile() - .to_legacy_sandbox_policy(&self.cwd) - .unwrap_or_else(|_| { - let file_system_sandbox_policy = self.file_system_sandbox_policy(); - codex_sandboxing::compatibility_sandbox_policy_for_permission_profile( - self.permission_profile_state.permission_profile(), - &file_system_sandbox_policy, - self.network_sandbox_policy(), - &self.cwd, - ) - }) + let permission_profile = self.permission_profile(); + codex_sandboxing::compatibility_sandbox_policy_for_permission_profile( + &permission_profile, + &self.cwd, + ) } pub(super) fn file_system_sandbox_policy(&self) -> FileSystemSandboxPolicy { diff --git a/codex-rs/core/src/session/turn_context.rs b/codex-rs/core/src/session/turn_context.rs index d600368137..ff36796348 100644 --- a/codex-rs/core/src/session/turn_context.rs +++ b/codex-rs/core/src/session/turn_context.rs @@ -124,12 +124,8 @@ impl TurnContext { } pub(crate) fn sandbox_policy(&self) -> SandboxPolicy { - let file_system_sandbox_policy = self.file_system_sandbox_policy(); - let network_sandbox_policy = self.network_sandbox_policy(); compatibility_sandbox_policy_for_permission_profile( &self.permission_profile, - &file_system_sandbox_policy, - network_sandbox_policy, #[allow(deprecated)] &self.cwd, ) diff --git a/codex-rs/core/src/spawn.rs b/codex-rs/core/src/spawn.rs index a2c4ebe597..a23a1d749e 100644 --- a/codex-rs/core/src/spawn.rs +++ b/codex-rs/core/src/spawn.rs @@ -30,7 +30,7 @@ pub enum StdioPolicy { Inherit, } -/// Spawns the appropriate child process for the ExecParams and SandboxPolicy, +/// Spawns the appropriate child process for the exec params and sandbox settings, /// ensuring the args and environment variables used to create the `Command` /// (and `Child`) honor the configuration. /// diff --git a/codex-rs/sandboxing/src/manager.rs b/codex-rs/sandboxing/src/manager.rs index bd8f6d9b98..39ef358f8c 100644 --- a/codex-rs/sandboxing/src/manager.rs +++ b/codex-rs/sandboxing/src/manager.rs @@ -290,19 +290,18 @@ impl SandboxManager { pub fn compatibility_sandbox_policy_for_permission_profile( permissions: &PermissionProfile, - file_system_policy: &FileSystemSandboxPolicy, - network_policy: NetworkSandboxPolicy, cwd: &Path, ) -> SandboxPolicy { permissions .to_legacy_sandbox_policy(cwd) .unwrap_or_else(|_| { + let (file_system_policy, network_policy) = permissions.to_runtime_permissions(); compatibility_workspace_write_policy(file_system_policy, network_policy, cwd) }) } fn compatibility_workspace_write_policy( - file_system_policy: &FileSystemSandboxPolicy, + file_system_policy: FileSystemSandboxPolicy, network_policy: NetworkSandboxPolicy, cwd: &Path, ) -> SandboxPolicy { From 1be50d9996dc22e4696f15142797398837bc18f0 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Tue, 2 Jun 2026 11:14:14 -0700 Subject: [PATCH 3/3] chore: stop passing SandboxPolicy for ThreadSettingsOverrides with guardian --- codex-rs/core/src/guardian/review_session.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/codex-rs/core/src/guardian/review_session.rs b/codex-rs/core/src/guardian/review_session.rs index fed87cd75f..b8c5c98075 100644 --- a/codex-rs/core/src/guardian/review_session.rs +++ b/codex-rs/core/src/guardian/review_session.rs @@ -20,7 +20,6 @@ use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::InitialHistory; use codex_protocol::protocol::Op; use codex_protocol::protocol::RolloutItem; -use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::SubAgentSource; use codex_protocol::protocol::TokenUsage; @@ -719,9 +718,7 @@ async fn run_review_on_session( .total_token_usage() .await .unwrap_or_default(); - // The legacy SandboxPolicy should match the PermissionProfile. let guardian_permission_profile = PermissionProfile::read_only(); - let legacy_sandbox_policy = SandboxPolicy::new_read_only_policy(); let submit_result = run_before_review_deadline( deadline, @@ -736,7 +733,7 @@ async fn run_review_on_session( #[allow(deprecated)] cwd: Some(params.parent_turn.cwd.to_path_buf()), approval_policy: Some(AskForApproval::Never), - sandbox_policy: Some(legacy_sandbox_policy), + sandbox_policy: None, permission_profile: Some(guardian_permission_profile), summary: Some(params.reasoning_summary), personality: params.personality,