Run Bazel CI locally without BuildBuddy credentials (#59055)

## What changed

- Remove automatic BuildBuddy configuration and credential forwarding from Bazel CI, release, SDK, and voice builds. Strip remote execution CI configs in the shared wrapper while retaining explicit remote configurations for local use.
- Use native Windows host tools and audio dependencies for Windows Bazel actions, forwarding the required toolchain and environment settings.
- Initialize message-board MCP servers through a per-session `OnceCell` so server startup does not hold the shared registry lock across an await.
- Expose the message-board test helper to the core Bazel package and update the runtime-auth test call for the `reauth` argument.

GitOrigin-RevId: 5d5c1f4d71fa841090dea8eec8006380981fb65b
This commit is contained in:
Dmitriy Kovalenko
2026-11-03 04:08:59 +00:00
committed by copyberry
parent fbf5f0d3a7
commit 3f958ac2fe
25 changed files with 136 additions and 550 deletions
@@ -5,10 +5,6 @@ inputs:
target:
description: Runner target passed to prepare-bazel-ci.
required: true
buildbuddy-api-key:
description: BuildBuddy API key used by Bazel CI.
required: false
default: ""
runs:
using: composite
@@ -19,19 +15,19 @@ runs:
cache-scope: bazel-argument-comment-lint
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
if: ${{ runner.os == 'Windows' && inputs.buildbuddy-api-key == '' }}
if: ${{ runner.os == 'Windows' }}
with:
python-version: "3.12"
architecture: x64
- name: Prepare native Windows audio dependencies
if: ${{ runner.os == 'Windows' && inputs.buildbuddy-api-key == '' }}
if: ${{ runner.os == 'Windows' }}
uses: ./third_party/voice/cargo-ci/windows-setup
with:
target: x86_64-pc-windows-msvc
- name: Verify native Windows audio dependencies
if: ${{ runner.os == 'Windows' && inputs.buildbuddy-api-key == '' }}
if: ${{ runner.os == 'Windows' }}
shell: bash
run: |
: "${VOICE_WINDOWS_BAZEL_REPOSITORY:?Windows voice tools are required}"
@@ -56,8 +52,6 @@ runs:
- name: Run argument comment lint on codex-rs via Bazel
if: ${{ runner.os != 'Windows' }}
env:
BUILDBUDDY_API_KEY: ${{ inputs.buildbuddy-api-key }}
shell: bash
run: |
bazel_targets="$(./tools/argument-comment-lint/list-bazel-targets.sh)"
@@ -72,8 +66,6 @@ runs:
- name: Run argument comment lint on codex-rs via Bazel
if: ${{ runner.os == 'Windows' }}
env:
BUILDBUDDY_API_KEY: ${{ inputs.buildbuddy-api-key }}
shell: bash
run: |
./.github/scripts/run-argument-comment-lint-bazel.sh \
@@ -9,7 +9,7 @@ if [[ "${RUNNER_OS:-}" == "Windows" ]]; then
# lint aspect can run across the compatible crate graph instead of failing the
# whole build after analysis.
bazel_lint_args+=("--skip_incompatible_explicit_targets")
if [[ -z "${BUILDBUDDY_API_KEY:-}" && -n "${VOICE_WINDOWS_BAZEL_REPOSITORY:-}" ]]; then
if [[ -n "${VOICE_WINDOWS_BAZEL_REPOSITORY:-}" ]]; then
: "${VOICE_WINDOWS_BAZEL_REPOSITORY:?Windows voice tools are required}"
: "${VOICE_WINDOWS_SYSTEM_ROOT:?Windows SystemRoot is required}"
: "${VOICE_WINDOWS_HOST_ARCH:?Windows host architecture is required}"
+52 -90
View File
@@ -69,32 +69,11 @@ run_bazel_with_startup_args() {
run_bazel "$@"
}
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
print_bazel_test_log_tails() {
local console_log="$1"
local testlogs_dir
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}")
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
@@ -255,10 +234,8 @@ if [[ ${#bazel_args[@]} -eq 0 || ${#bazel_targets[@]} -eq 0 ]]; then
exit 1
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
if [[ "${RUNNER_OS:-}" == "Windows" && $windows_cross_compile -eq 1 ]]; then
# Run Windows actions with the native MSVC host platform.
windows_msvc_host_platform=1
fi
@@ -287,22 +264,9 @@ if [[ $remote_download_toplevel -eq 1 ]]; then
post_config_bazel_args+=(--remote_download_toplevel)
fi
if [[ "${RUNNER_OS:-}" == "Windows" && $windows_cross_compile -eq 1 && -n "${BUILDBUDDY_API_KEY:-}" ]]; then
# `--enable_platform_specific_config` expands `common:windows` on Windows
# hosts after ordinary rc configs, which can override `ci-windows-cross`'s
# RBE host platform. Repeat the host platform on the command line so V8 and
# other genrules execute on Linux RBE workers instead of Git Bash locally.
#
# Bazel also derives the default genrule shell from the client host. Without
# an explicit shell executable, remote Linux actions can be asked to run
# `C:\Program Files\Git\usr\bin\bash.exe`.
post_config_bazel_args+=(--host_platform=//:rbe --shell_executable=/bin/bash)
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.
if [[ "${RUNNER_OS:-}" == "Windows" && $windows_cross_compile -eq 1 ]]; then
# Bound concurrent native Windows actions to fit the runner.
post_config_bazel_args+=(--jobs=8)
fi
@@ -324,35 +288,49 @@ if [[ -n "${CODEX_BAZEL_EXECUTION_LOG_COMPACT_DIR:-}" ]]; then
fi
if [[ "${RUNNER_OS:-}" == "Windows" ]]; then
pass_windows_build_env=1
if [[ $windows_cross_compile -eq 1 && -n "${BUILDBUDDY_API_KEY:-}" ]]; then
# Remote build actions execute on Linux RBE workers. Passing the Windows
# runner's build environment there makes Bazel genrules try to execute
# C:\Program Files\Git\usr\bin\bash.exe on Linux.
pass_windows_build_env=0
fi
if [[ $pass_windows_build_env -eq 1 ]]; then
windows_action_env_vars=(
INCLUDE
LIB
LIBPATH
UCRTVersion
UniversalCRTSdkDir
VCINSTALLDIR
VCToolsInstallDir
WindowsLibPath
WindowsSdkBinPath
WindowsSdkDir
WindowsSDKLibVersion
WindowsSDKVersion
windows_action_env_vars=(
INCLUDE
LIB
LIBPATH
UCRTVersion
UniversalCRTSdkDir
VCINSTALLDIR
VCToolsInstallDir
WindowsLibPath
WindowsSdkBinPath
WindowsSdkDir
WindowsSDKLibVersion
WindowsSDKVersion
)
for env_var in "${windows_action_env_vars[@]}"; do
if [[ -n "${!env_var:-}" ]]; then
post_config_bazel_args+=("--action_env=${env_var}" "--host_action_env=${env_var}")
fi
done
if [[ -n "${VOICE_WINDOWS_BAZEL_REPOSITORY:-}" ]]; then
# Reuse the verified native audio inputs for local CI's voice targets.
# These fixed values must be visible during Starlark analysis as well
# as action execution; inherited --action_env values are insufficient.
: "${VOICE_WINDOWS_SYSTEM_ROOT:?Windows SystemRoot is required}"
: "${VOICE_WINDOWS_HOST_ARCH:?Windows host architecture is required}"
# GNU-host lint jobs still need the LLVM linker for their build tools.
if [[ $windows_msvc_host_platform -eq 1 ]]; then
post_config_bazel_args+=("--config=ci-windows-msvc")
fi
post_config_bazel_args+=(
"--workspace_status_command=./scripts/workspace-status.cmd"
"--extra_toolchains=//third_party/voice:windows_pkg_config_toolchain,//third_party/voice:windows_cmake_toolchain"
"--repo_env=BAZEL_MSVC_RUNTIME_VISUAL_STUDIO_EULA=1"
"--inject_repository=voice_windows_tools=${VOICE_WINDOWS_BAZEL_REPOSITORY}"
"--//third_party/voice:windows_installed_tools=@voice_windows_tools//:tools"
"--action_env=SystemRoot=${VOICE_WINDOWS_SYSTEM_ROOT}"
"--host_action_env=SystemRoot=${VOICE_WINDOWS_SYSTEM_ROOT}"
"--action_env=PROCESSOR_ARCHITECTURE=${VOICE_WINDOWS_HOST_ARCH}"
"--host_action_env=PROCESSOR_ARCHITECTURE=${VOICE_WINDOWS_HOST_ARCH}"
)
for env_var in "${windows_action_env_vars[@]}"; do
if [[ -n "${!env_var:-}" ]]; then
post_config_bazel_args+=("--action_env=${env_var}" "--host_action_env=${env_var}")
fi
done
fi
if [[ -z "${CODEX_BAZEL_WINDOWS_PATH:-}" ]]; then
@@ -360,20 +338,10 @@ if [[ "${RUNNER_OS:-}" == "Windows" ]]; then
exit 1
fi
if [[ $pass_windows_build_env -eq 1 ]]; then
post_config_bazel_args+=(
"--action_env=PATH=${CODEX_BAZEL_WINDOWS_PATH}"
"--host_action_env=PATH=${CODEX_BAZEL_WINDOWS_PATH}"
)
elif [[ $windows_cross_compile -eq 1 ]]; then
# Remote build actions run on Linux RBE workers. Give their shell snippets
# a Linux PATH while preserving CODEX_BAZEL_WINDOWS_PATH below for local
# Windows test execution.
post_config_bazel_args+=(
"--action_env=PATH=/usr/bin:/bin"
"--host_action_env=PATH=/usr/bin:/bin"
)
fi
post_config_bazel_args+=(
"--action_env=PATH=${CODEX_BAZEL_WINDOWS_PATH}"
"--host_action_env=PATH=${CODEX_BAZEL_WINDOWS_PATH}"
)
post_config_bazel_args+=("--test_env=PATH=${CODEX_BAZEL_WINDOWS_PATH}")
fi
@@ -383,14 +351,8 @@ trap 'rm -f "$bazel_console_log"' EXIT
bazel_run_args=(
"${bazel_args[@]}"
)
if [[ -n "${BUILDBUDDY_API_KEY:-}" ]]; then
echo "BuildBuddy API key is available; using remote Bazel configuration."
bazel_run_args+=("--config=${ci_config}")
else
echo "BuildBuddy API key is not available; using local Bazel configuration."
if [[ "${bazel_args[0]}" == "test" ]]; then
bazel_run_args+=("--test_env=INSTA_UPDATE=no")
fi
if [[ "${bazel_args[0]}" == "test" ]]; then
bazel_run_args+=("--test_env=INSTA_UPDATE=no")
fi
if (( ${#post_config_bazel_args[@]} > 0 )); then
bazel_run_args+=("${post_config_bazel_args[@]}")
@@ -399,7 +361,7 @@ 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.
# the startup-level repo contents cache.
run_bazel_with_startup_args \
--noexperimental_remote_repo_contents_cache \
"${bazel_run_args[@]}" \
+3 -100
View File
@@ -1,21 +1,13 @@
#!/usr/bin/env python3
import json
import os
import subprocess
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.
# These configs select remote execution, which is unavailable on CI runners.
REMOTE_EXECUTION_CONFIGS = {
"--config=ci-linux",
"--config=ci-macos",
@@ -56,68 +48,13 @@ def startup_args(args: Sequence[str], env: Mapping[str, str]) -> list[str]:
for arg in configured_startup_args
):
# Work around Bazel 9 overlay materialization failures seen in CI. This
# disables only the startup-level repo contents cache; keyed runs still
# use BuildBuddy.
# disables only the startup-level repo contents cache.
injected_args.append("--noexperimental_remote_repo_contents_cache")
return injected_args
# 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:
@@ -138,26 +75,7 @@ def bazel_args_with_remote_config(
if command_idx is None:
raise ValueError("expected a Bazel command")
config = remote_config(args, env)
if config is None:
configured_args = bazel_args_without_remote_execution(args)
else:
# `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.
configured_args = [
*args[: command_idx + 1],
*remote_args,
*args[command_idx + 1 :],
]
configured_args = bazel_args_without_remote_execution(args)
try:
separator_idx = configured_args.index("--")
except ValueError:
@@ -188,21 +106,6 @@ def bazel_command(*args: str, env: Mapping[str, str] | None = None) -> list[str]
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:])
if os.name == "nt":
# Windows CRT exec can split arguments containing spaces and lose the
@@ -1,177 +1,15 @@
#!/usr/bin/env python3
import json
import os
import subprocess
import sys
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(
@@ -266,7 +104,6 @@ class RunBazelWithBuildBuddyTest(unittest.TestCase):
env = os.environ.copy()
env["CODEX_BAZEL_BIN"] = sys.executable
env.pop("BAZEL_OUTPUT_USER_ROOT", None)
env.pop("BUILDBUDDY_API_KEY", None)
env.pop("GITHUB_ACTIONS", None)
result = subprocess.run(
+1 -17
View File
@@ -72,7 +72,7 @@ class RustyV8BazelTest(unittest.TestCase):
with patch.object(rusty_v8_bazel, "MODULE_BAZEL", module_bazel):
self.assertEqual("146.4.0", rusty_v8_bazel.command_version(None))
def test_bazel_commands_use_shared_buildbuddy_remote_config_library(self) -> None:
def test_bazel_commands_use_shared_ci_options(self) -> None:
with patch.dict(environ, {}, clear=True):
self.assertEqual(
[
@@ -86,22 +86,6 @@ class RustyV8BazelTest(unittest.TestCase):
"//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",
),
)
def test_release_pair_labels_and_staged_names_distinguish_sandbox_artifacts(
self,
+29 -36
View File
@@ -17,9 +17,7 @@ jobs:
test:
# PRs use the sharded Windows cross-compiled test jobs below. Post-merge
# pushes to main also run the native Windows test job for broader Windows
# signal without putting PR latency back on the critical path. When
# Code-mode unit tests run on every Bazel target. When authenticated RBE
# is available, the Windows-cross shards exercise the source-built V8 path.
# signal without putting PR latency back on the critical path.
timeout-minutes: 30
strategy:
fail-fast: false
@@ -72,8 +70,6 @@ jobs:
- name: Check MODULE.bazel.lock is up to date
if: matrix.os == 'ubuntu-24.04' && matrix.target == 'x86_64-unknown-linux-gnu'
env:
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
shell: bash
run: ./scripts/check-module-bazel-lock.sh
@@ -86,8 +82,6 @@ jobs:
echo "CODEX_COMPAT_RELEASES=$releases" >> "$GITHUB_ENV"
- name: bazel test //...
env:
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
shell: bash
run: |
bazel_targets=(
@@ -139,9 +133,7 @@ jobs:
uses: ./.github/actions/check-clean-worktree
test-windows-shard:
# Split the Windows Bazel test leg across separate Windows hosts. Jobs with
# BuildBuddy credentials use Linux RBE for build actions; test execution
# remains on a Windows runner.
# Build and test Windows shards on separate Windows runners.
timeout-minutes: 30
strategy:
fail-fast: false
@@ -164,7 +156,7 @@ jobs:
with:
persist-credentials: false
- name: Test BuildBuddy Bazel wrapper
- name: Test Bazel command wrapper
if: matrix.shard == 1
shell: pwsh
run: python .github/scripts/test_run_bazel_with_buildbuddy.py
@@ -184,11 +176,16 @@ jobs:
# the PR-blocking critical path after the useful test work is done.
cache-scope: bazel-test
- name: Prepare native Windows audio dependencies
if: runner.os == 'Windows'
uses: ./third_party/voice/cargo-ci/windows-setup
with:
target: x86_64-pc-windows-msvc
- name: bazel test shard
env:
BAZEL_TEST_SHARD: ${{ matrix.shard }}
BAZEL_TEST_SHARD_COUNT: 4
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
shell: bash
run: |
set -euo pipefail
@@ -283,9 +280,13 @@ jobs:
target: x86_64-pc-windows-msvc
cache-scope: bazel-${{ github.job }}
- name: Prepare native Windows audio dependencies
if: runner.os == 'Windows'
uses: ./third_party/voice/cargo-ci/windows-setup
with:
target: x86_64-pc-windows-msvc
- name: bazel test //...
env:
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
shell: bash
run: |
bazel_targets=(
@@ -376,6 +377,12 @@ jobs:
target: ${{ matrix.target }}
cache-scope: bazel-${{ github.job }}
- name: Prepare native Windows audio dependencies
if: runner.os == 'Windows'
uses: ./third_party/voice/cargo-ci/windows-setup
with:
target: x86_64-pc-windows-msvc
- name: Resolve app-server/exec-server compatibility releases
if: runner.os == 'Linux'
env:
@@ -385,8 +392,6 @@ jobs:
echo "CODEX_COMPAT_RELEASES=$releases" >> "$GITHUB_ENV"
- name: bazel build --config=clippy lint targets
env:
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
shell: bash
run: |
bazel_clippy_args=(
@@ -397,19 +402,9 @@ jobs:
bazel_wrapper_args=()
bazel_target_list_args=()
if [[ "${RUNNER_OS}" == "Windows" ]]; then
# Keep this aligned with the fast Windows Bazel test job: use
# Linux RBE for clippy build actions while targeting Windows
# gnullvm. Fork/community PRs without the BuildBuddy secret fall
# back inside `run-bazel-ci.sh` to the previous local Windows MSVC
# host-platform shape.
bazel_wrapper_args+=(--windows-cross-compile)
bazel_target_list_args+=(--windows-cross-compile)
if [[ -z "${BUILDBUDDY_API_KEY:-}" ]]; then
# The fork fallback can see incompatible explicit Windows-cross
# internal test binaries in the generated target list. Preserve
# the old local-fallback behavior there.
bazel_clippy_args+=(--skip_incompatible_explicit_targets)
fi
bazel_clippy_args+=(--skip_incompatible_explicit_targets)
fi
bazel_target_lines="$(./scripts/list-bazel-clippy-targets.sh "${bazel_target_list_args[@]}")"
@@ -483,6 +478,12 @@ jobs:
target: ${{ matrix.target }}
cache-scope: bazel-${{ github.job }}
- name: Prepare native Windows audio dependencies
if: runner.os == 'Windows'
uses: ./third_party/voice/cargo-ci/windows-setup
with:
target: x86_64-pc-windows-msvc
- name: Resolve app-server/exec-server compatibility releases
if: runner.os == 'Linux'
env:
@@ -492,8 +493,6 @@ jobs:
echo "CODEX_COMPAT_RELEASES=$releases" >> "$GITHUB_ENV"
- name: bazel build verify-release-build targets
env:
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
shell: bash
run: |
# This job exists to compile Rust code behind
@@ -504,11 +503,7 @@ jobs:
# Rust debug assertions explicitly.
bazel_wrapper_args=()
if [[ "${RUNNER_OS}" == "Windows" ]]; then
# This is build-only signal, so use the same Linux-RBE
# cross-compile path as the fast Windows test and clippy jobs.
# Fork/community PRs without the BuildBuddy secret fall back
# inside `run-bazel-ci.sh` to the previous local Windows MSVC
# host-platform shape.
# Use the same native host toolchain as the Windows test shards.
bazel_wrapper_args+=(--windows-cross-compile)
fi
@@ -537,8 +532,6 @@ jobs:
- name: Verify Bazel builds bwrap
if: runner.os == 'Linux'
env:
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
shell: bash
run: |
./.github/scripts/run-bazel-ci.sh \
-1
View File
@@ -212,7 +212,6 @@ jobs:
uses: ./.github/actions/run-argument-comment-lint
with:
target: ${{ runner.os }}
buildbuddy-api-key: ${{ secrets.BUILDBUDDY_API_KEY }}
- name: Check for a clean worktree
if: always() && !cancelled() && steps.argument_comment_lint_gate.outputs.run == 'true'
@@ -124,8 +124,6 @@ jobs:
- name: Build Bazel release binaries and symbols
if: ${{ matrix.build-system == 'bazel' }}
uses: ./.github/actions/build-bazel-release
env:
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
with:
target: ${{ matrix.target }}
bundle: ${{ matrix.bundle }}
@@ -253,7 +251,6 @@ jobs:
ARCH: ${{ matrix.arch }}
VOICE_ARCH: ${{ matrix.arch }}
# Use local Bazel while BuildBuddy credentials are unavailable.
BUILDBUDDY_API_KEY: ""
run: |
(cd codex-rs && cargo update --workspace)
python .github/scripts/watch_voice_bazel.py "$(cygpath -w "$BASH")" ./.github/scripts/run-bazel-ci.sh \
-4
View File
@@ -277,8 +277,6 @@ jobs:
- name: Build Bazel release binaries and symbols
if: ${{ matrix.build-system == 'bazel' }}
uses: ./.github/actions/build-bazel-release
env:
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
with:
target: ${{ matrix.target }}
bundle: ${{ matrix.bundle }}
@@ -327,7 +325,6 @@ jobs:
env:
APP_TARGET: ${{ matrix.target }}
# Use local Bazel while BuildBuddy credentials are unavailable.
BUILDBUDDY_API_KEY: ""
run: |
set -euo pipefail
cd "$GITHUB_WORKSPACE"
@@ -607,7 +604,6 @@ jobs:
TARGET: ${{ matrix.target }}
PREFIX: ${{ matrix.prefix }}
# Use local Bazel while BuildBuddy credentials are unavailable.
BUILDBUDDY_API_KEY: ""
run: |
set -euo pipefail
# Release tags bump Cargo.toml without rewriting the workspace lockfile.
-2
View File
@@ -168,7 +168,6 @@ jobs:
- name: Build Bazel V8 release pair
env:
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
PLATFORM: ${{ matrix.platform }}
SANDBOX: ${{ matrix.sandbox }}
TARGET: ${{ matrix.target }}
@@ -200,7 +199,6 @@ jobs:
- name: Stage release pair
env:
BAZEL_CONFIG: ${{ matrix.bazel_config }}
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
PLATFORM: ${{ matrix.platform }}
SANDBOX: ${{ matrix.sandbox }}
TARGET: ${{ matrix.target }}
-2
View File
@@ -79,8 +79,6 @@ jobs:
- name: Build codex and the code-mode host with Bazel
id: build_bazel
env:
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
shell: bash
run: |
set -euo pipefail
-2
View File
@@ -181,7 +181,6 @@ jobs:
- name: Build Bazel V8 release pair
env:
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
PLATFORM: ${{ matrix.platform }}
SANDBOX: ${{ matrix.sandbox }}
TARGET: ${{ matrix.target }}
@@ -211,7 +210,6 @@ jobs:
- name: Stage release pair
env:
BAZEL_CONFIG: ${{ matrix.bazel_config }}
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
PLATFORM: ${{ matrix.platform }}
SANDBOX: ${{ matrix.sandbox }}
TARGET: ${{ matrix.target }}
@@ -118,6 +118,7 @@ async fn selected_runtime_transport_stays_revoked(mode: RuntimeAuthMode) {
RequestRouteTelemetry::for_endpoint("/responses"),
&http::HeaderMap::new(),
/*parent_routing_affinity*/ None,
/*reauth*/ false,
)
.await;
assert!(matches!(
+1 -50
View File
@@ -76,20 +76,7 @@ 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. It also normalizes GitHub Actions
startup options so all Bazel launches in a job reuse the same server and
in-memory analysis cache. Target-discovery and lockfile helpers delegate to the
same wrapper so their callers do not need to select CI-specific startup options.
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.
CI executes Bazel actions on its runners. The following configurations are available for explicit local use.
| Invocation/config | Key Required | Cache/BES | Build exec | Test exec |
| ------------------------------------------- | ------------ | ---------------------- | ---------- | --------- |
@@ -99,42 +86,6 @@ The `Cache/BES` host is also used for remote downloads.
| `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.
+27 -20
View File
@@ -23,15 +23,16 @@ use codex_protocol::error::CodexErr;
use codex_protocol::error::Result;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::Weak;
use tokio::sync::Mutex;
use tokio::sync::OnceCell;
/// Shared launchers opened by the existing board factory when the MCP flag is enabled.
#[derive(Default)]
pub struct McpBoards {
servers: Mutex<HashMap<SessionId, Weak<LocalServer>>>,
servers: Mutex<HashMap<SessionId, Weak<OnceCell<LocalServer>>>>,
}
struct Binding(Arc<LocalServer>);
struct Binding(Arc<OnceCell<LocalServer>>);
struct PendingMcpRefresh;
impl McpBoards {
@@ -42,25 +43,28 @@ impl McpBoards {
caller: ThreadId,
host: Arc<dyn MessageBoardHost>,
) -> Result<()> {
let mut servers = self.servers.lock().await;
servers.retain(|_, server| server.strong_count() > 0);
let server = match servers.get(&tree).and_then(Weak::upgrade) {
Some(server) => server,
None => {
let server = Arc::new(
launch_in_memory()
.await
.map_err(|error| CodexErr::Io(std::io::Error::other(error)))?,
);
servers.insert(tree, Arc::downgrade(&server));
server
}
let slot = {
let mut servers = self
.servers
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
servers.retain(|_, server| server.strong_count() > 0);
let entry = servers.entry(tree).or_default();
entry.upgrade().unwrap_or_else(|| {
let slot = Arc::new(OnceCell::new());
*entry = Arc::downgrade(&slot);
slot
})
};
let server = slot
.get_or_try_init(launch_in_memory)
.await
.map_err(|error| CodexErr::Io(std::io::Error::other(error)))?;
server
.bind_agent(tree, caller, host)
.await
.map_err(|error| CodexErr::Io(std::io::Error::other(error)))?;
store.insert(Binding(server));
store.insert(Binding(slot));
store.insert(PendingMcpRefresh);
Ok(())
}
@@ -96,18 +100,21 @@ impl<C: Sync> McpServerContributor<C> for McpBoards {
Box::pin(async move {
let Some(binding) = context
.thread_store()
.and_then(|store| store.get::<Binding>())
.and_then(ExtensionData::get::<Binding>)
else {
return Vec::new();
};
let Ok(authorization) = binding.0.authorization().to_str() else {
let Some(server) = binding.0.get() else {
return Vec::new();
};
let Ok(authorization) = server.authorization().to_str() else {
return Vec::new();
};
vec![McpServerContribution::Set {
name: "codex_message_board".into(),
config: Box::new(McpServerConfig {
transport: McpServerTransportConfig::StreamableHttp {
url: binding.0.url().into(),
url: server.url().into(),
bearer_token_env_var: None,
http_headers: Some([("Authorization".into(), authorization.into())].into()),
env_http_headers: None,
+5
View File
@@ -1,5 +1,10 @@
load("//:defs.bzl", "codex_rust_crate")
exports_files(
["tests/common/mod.rs"],
visibility = ["//codex-rs/core:__pkg__"],
)
codex_rust_crate(
name = "message-board",
crate_name = "codex_message_board",
+2 -2
View File
@@ -78,7 +78,7 @@ async fn empty_thread_backend_chord_preserves_draft_until_first_submission() ->
.apply_external_edit("first draft".to_string());
app.chat_widget
.set_new_backend_control(Some("Cloud".to_string()));
let screen = render_bottom_popup(&app.chat_widget, 80);
let screen = render_bottom_popup(&app.chat_widget, /*width*/ 80);
insta::assert_snapshot!(screen.lines().find(|line| line.trim_start().starts_with("Cloud")).unwrap().trim(), @"Cloud ⌃x b to cycle");
// Cursor movement remains available while the backend selector is visible.
@@ -110,7 +110,7 @@ async fn empty_thread_backend_chord_preserves_draft_until_first_submission() ->
app.chat_widget.handle_key_event(KeyCode::Enter.into());
assert!(
!render_bottom_popup(&app.chat_widget, 80)
!render_bottom_popup(&app.chat_widget, /*width*/ 80)
.lines()
.any(|line| line.trim_start().starts_with("Cloud"))
);
@@ -4789,7 +4789,7 @@ fn fresh_session_keeps_its_name_and_draft_when_cycling_backends() -> Result<()>
backends.session_at_mut(backends.displayed),
AppEvent::ChooseNewSessionCheckout { name: None },
).await?;
assert!(render_bottom_popup(&app.chat_widget, 80).contains("Use current Git worktree"));
assert!(render_bottom_popup(&app.chat_widget, /*width*/ 80).contains("Use current Git worktree"));
app.chat_widget.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
assert_eq!(app.current_displayed_thread_id(), Some(thread_id));
assert_eq!(recorded_params(&requests, "thread/start").len(), 1);
+1 -1
View File
@@ -577,7 +577,7 @@ impl ChatWidget {
}
pub(super) fn restore_composer_state(&mut self, composer: ThreadComposerState) {
self.restore_composer_state_with_shell_mode(composer, None);
self.restore_composer_state_with_shell_mode(composer, /*shell_mode*/ None);
}
fn restore_composer_state_with_shell_mode(
@@ -1184,7 +1184,7 @@ async fn startup_draft_handoff_preserves_literal_and_shell_bang() {
] {
let (mut source, _rx, _ops) = make_chatwidget_manual(/*model_override*/ None).await;
source.bottom_pane.insert_str(&format!("x{text}"));
source.bottom_pane.set_composer_cursor(0);
source.bottom_pane.set_composer_cursor(/*cursor*/ 0);
source.handle_key_event(KeyEvent::from(KeyCode::Delete));
let draft = source.composer_draft_snapshot();
assert_eq!(draft.text, text);
+4 -2
View File
@@ -183,8 +183,10 @@ pub(crate) fn quiet_workspace_style() -> Style {
let Some(bg) = default_bg() else {
return base.fg(Color::Reset).bg(Color::Reset).dim();
};
let fill = best_color(blend(user_message_bg_rgb(bg), bg, 0.40));
let foreground = default_fg().map_or(Color::Reset, |fg| best_color(blend(fg, bg, 0.66)));
let fill = best_color(blend(user_message_bg_rgb(bg), bg, /*alpha*/ 0.40));
let foreground = default_fg().map_or(Color::Reset, |fg| {
best_color(blend(fg, bg, /*alpha*/ 0.66))
});
base.bg(fill).fg(readable_color_on(foreground, Some(fill)))
}
+3 -3
View File
@@ -365,11 +365,11 @@ impl TranscriptView {
lines.extend_from_slice(auxiliary);
(!lines.is_empty()).then_some(lines)
};
self.sync_search_live_tail(width, None, lines);
self.sync_search_live_tail(width, /*key*/ None, lines);
if self.detailed {
self.sync_live_tail(width, None, lines);
self.sync_live_tail(width, /*key*/ None, lines);
} else {
self.sync_live_activity_tail(width, None, expanded && disclose, |width| {
self.sync_live_activity_tail(width, /*key*/ None, expanded && disclose, |width| {
let activity = active.map_or_else(Vec::new, |cell| {
if disclose && expanded {
cell.expanded_hyperlink_lines(width)
+1 -5
View File
@@ -7,9 +7,6 @@ inputs:
windows-tools:
description: Directory containing the public Windows voice bootstrap
default: .github/scripts
buildbuddy-api-key:
description: Optional BuildBuddy credential for trusted CI builds
default: ""
runs:
using: composite
steps:
@@ -23,14 +20,13 @@ runs:
cache-scope: cargo-voice-native
- name: Set up Windows voice build tools
if: runner.os == 'Windows'
uses: ./third_party/voice/cargo-ci/windows-setup
uses: ./.github/actions/setup-bazel-windows
with:
target: ${{ inputs.target }}
windows-tools: ${{ inputs.windows-tools }}
- name: Build and stage native dependencies
shell: bash
env:
BUILDBUDDY_API_KEY: ${{ inputs.buildbuddy-api-key }}
TARGET: ${{ inputs.target }}
run: |
set -euo pipefail
-33
View File
@@ -1,33 +0,0 @@
name: Set up Windows voice build tools
description: Prepare verified native Windows voice tools for Bazel.
inputs:
target:
description: Native Windows target triple
required: true
windows-tools:
description: Directory containing the public Windows voice bootstrap
default: .github/scripts
runs:
using: composite
steps:
- uses: ./.github/actions/setup-msvc-env
with:
target: ${{ inputs.target }}
- name: Install verified public Windows tools
shell: pwsh
env:
WINDOWS_TOOLS: ${{ inputs.windows-tools }}
TARGET: ${{ inputs.target }}
run: |
./.github/scripts/compute-bazel-windows-path.ps1
$manifest = Get-Content -Raw "$env:WINDOWS_TOOLS/voice-cygwin-snapshot.json" | ConvertFrom-Json
$pin = $manifest.archive
$archive = Join-Path $env:RUNNER_TEMP $pin.name
Invoke-WebRequest "https://github.com/openai/codex/releases/download/$($pin.tag)/$($pin.name)" -OutFile $archive
& "$env:WINDOWS_TOOLS/setup-voice-windows.ps1" -Target $env:TARGET -SnapshotArchive $archive
if (-not $env:SystemRoot) { throw "Windows SystemRoot is required" }
"VOICE_WINDOWS_SYSTEM_ROOT=$env:SystemRoot" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append
$hostArch = $env:PROCESSOR_ARCHITEW6432
if (-not $hostArch) { $hostArch = $env:PROCESSOR_ARCHITECTURE }
if (-not $hostArch) { throw "Windows host architecture is required" }
"VOICE_WINDOWS_HOST_ARCH=$hostArch" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append