Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 38 additions & 30 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,42 @@
# Workflow Strategy
# Codex Lab Workflow Strategy

The workflows in this directory are split so that pull requests get fast, review-friendly signal while `main` still gets the full cross-platform verification pass.
This fork keeps upstream workflows available, but the automatic PR signal is
owned by Codex Lab. Upstream's full CI and release workflows assume OpenAI
runner groups, secrets, and release infrastructure that this fork does not own.

## Pull Requests

- `bazel.yml` is the main pre-merge verification path for Rust code.
It runs Bazel `test` and Bazel `clippy` on the supported Bazel targets,
including the generated Rust test binaries needed to lint inline `#[cfg(test)]`
code.
- `rust-ci.yml` keeps the Cargo-native PR checks intentionally small:
- `cargo fmt --check`
- `cargo shear`
- `argument-comment-lint` on Linux, macOS, and Windows
- `tools/argument-comment-lint` package tests when the lint or its workflow wiring changes

## Post-Merge On `main`

- `bazel.yml` also runs on pushes to `main`.
This re-verifies the merged Bazel path and helps keep the BuildBuddy caches warm.
- `rust-ci-full.yml` is the full Cargo-native verification workflow.
It keeps the heavier checks off the PR path while still validating them after merge:
- the full Cargo `clippy` matrix
- the full Cargo `nextest` matrix via per-platform archive-backed shards
- Windows ARM64 nextest archives cross-compiled on Windows x64, then replayed on native Windows ARM64 shards
- release-profile Cargo builds
- cross-platform `argument-comment-lint`
- Linux remote-env tests

## Rule Of Thumb

- If a build/test/clippy check can be expressed in Bazel, prefer putting the PR-time version in `bazel.yml`.
- Keep `rust-ci.yml` fast enough that it usually does not dominate PR latency.
- Reserve `rust-ci-full.yml` for heavyweight Cargo-native coverage that Bazel does not replace yet.
- `ci.yml` runs cheap repository sanity checks plus Codex Lab package-builder
unit and smoke tests.
- `codex-lab-app.yml` builds the macOS ARM64 `Codex Lab.app` artifact on the
self-hosted macOS runner when packaging files, Rust CLI code, or the workflow
change. The self-hosted job is guarded so it runs automatically only for
branches in this repository or manual dispatches.
- `blob-size-policy.yml`, `codespell.yml`, and `cargo-deny.yml` are retained as
lightweight inherited checks while they remain fork-safe.

## Manual Upstream Parity Checks

The inherited heavyweight workflows are `workflow_dispatch` only in this fork:

- `bazel.yml`
- `rust-ci.yml`
- `sdk.yml`
- `v8-canary.yml`

Run these manually when a change needs upstream-style validation or touches the
areas those workflows own. Keep them out of the default PR path until this fork
has matching runner capacity, secrets, and branch-protection expectations.

## Local Runner Contract

`codex-lab-app.yml` expects a self-hosted macOS ARM64 runner with these labels:

- `self-hosted`
- `macOS`
- `ARM64`
- `codex-lab-app`

The current local runner is `chris-mac-codex-release-1`. It must have Rust,
Python 3, Xcode command line tools, and macOS `ditto` available. The generated
Codex Lab app artifact is currently unsigned.
4 changes: 0 additions & 4 deletions .github/workflows/bazel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ name: Bazel
# https://github.com/cerisier/toolchains_llvm_bootstrapped/blob/main/.github/workflows/ci.yaml

on:
pull_request: {}
push:
branches:
- main
workflow_dispatch:

concurrency:
Expand Down
17 changes: 17 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,23 @@ jobs:
- name: Test Codex package builder
run: python3 -m unittest discover -s scripts/codex_package -p 'test_*.py'

- name: Test Codex Lab app package builder
run: python3 -m unittest discover -s scripts/codex_lab_package -p 'test_*.py'

- name: Smoke test Codex Lab app package layout
shell: bash
run: |
set -euo pipefail
output_dir="${RUNNER_TEMP}/codex-lab-smoke"
python3 scripts/build_codex_lab_app.py \
--codex-bin /bin/sh \
--app-dir "${output_dir}/Codex Lab.app" \
--shim-dir "${output_dir}/bin" \
--force
python3 scripts/codex_lab_package/smoke.py \
"${output_dir}/Codex Lab.app" \
--shim-path "${output_dir}/bin/codex-lab"

- name: Setup pnpm
uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5
with:
Expand Down
81 changes: 81 additions & 0 deletions .github/workflows/codex-lab-app.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: codex-lab-app

on:
workflow_dispatch:
pull_request:
paths:
- ".github/workflows/codex-lab-app.yml"
- "scripts/build_codex_lab_app.py"
- "scripts/codex_lab_package/**"
- "codex-rs/**"

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build-macos-aarch64:
name: Build macOS ARM64 Codex Lab app
if: ${{ github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository }}
runs-on:
- self-hosted
- macOS
- ARM64
- codex-lab-app
timeout-minutes: 60
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
persist-credentials: false

- uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0

- name: Build Codex Lab CLI
working-directory: codex-rs
shell: bash
run: cargo build --release -p codex-cli --bin codex

- name: Build Codex Lab app bundle
id: package
shell: bash
run: |
set -euo pipefail
output_root="${RUNNER_TEMP}/codex-lab-app"
app_dir="${output_root}/Codex Lab.app"
shim_dir="${output_root}/bin"
mkdir -p "$output_root"
python3 scripts/build_codex_lab_app.py \
--codex-bin codex-rs/target/release/codex \
--app-dir "$app_dir" \
--shim-dir "$shim_dir" \
--force
python3 scripts/codex_lab_package/smoke.py \
"$app_dir" \
--shim-path "${shim_dir}/codex-lab"
echo "output_root=$output_root" >> "$GITHUB_OUTPUT"

- name: Archive Codex Lab app artifact
id: archive
shell: bash
run: |
set -euo pipefail
output_root="${{ steps.package.outputs.output_root }}"
dist_dir="${RUNNER_TEMP}/codex-lab-dist"
mkdir -p "$dist_dir"
app_zip="${dist_dir}/codex-lab-app-aarch64-apple-darwin.zip"
shim_zip="${dist_dir}/codex-lab-shim-aarch64-apple-darwin.zip"
ditto -c -k --norsrc --keepParent "${output_root}/Codex Lab.app" "$app_zip"
ditto -c -k --norsrc --keepParent "${output_root}/bin/codex-lab" "$shim_zip"
(cd "$dist_dir" && shasum -a 256 -- *.zip > SHA256SUMS)
cat "${dist_dir}/SHA256SUMS"
echo "dist_dir=$dist_dir" >> "$GITHUB_OUTPUT"

- name: Upload Codex Lab app artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: codex-lab-app-aarch64-apple-darwin
path: ${{ steps.archive.outputs.dist_dir }}/*
if-no-files-found: error
1 change: 0 additions & 1 deletion .github/workflows/rust-ci.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
name: rust-ci
on:
pull_request: {}
workflow_dispatch:

# Cargo's libgit2 transport has been flaky when fetching git dependencies with
Expand Down
4 changes: 1 addition & 3 deletions .github/workflows/sdk.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
name: sdk

on:
push:
branches: [main]
pull_request: {}
workflow_dispatch:

jobs:
python-sdk:
Expand Down
36 changes: 0 additions & 36 deletions .github/workflows/v8-canary.yml
Original file line number Diff line number Diff line change
@@ -1,42 +1,6 @@
name: v8-canary

on:
pull_request:
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"
- ".github/workflows/v8-canary.yml"
- "MODULE.bazel"
- "MODULE.bazel.lock"
- "codex-rs/Cargo.toml"
- "patches/BUILD.bazel"
- "patches/llvm_*.patch"
- "patches/rules_cc_*.patch"
- "patches/v8_*.patch"
- "third_party/v8/**"
push:
branches:
- main
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"
- ".github/workflows/v8-canary.yml"
- "MODULE.bazel"
- "MODULE.bazel.lock"
- "codex-rs/Cargo.toml"
- "patches/BUILD.bazel"
- "patches/llvm_*.patch"
- "patches/rules_cc_*.patch"
- "patches/v8_*.patch"
- "third_party/v8/**"
workflow_dispatch:

# Cargo's libgit2 transport has been flaky when fetching git dependencies with
Expand Down
17 changes: 17 additions & 0 deletions scripts/build_codex_lab_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env python3
"""Build a Codex Lab macOS launcher app bundle."""

from pathlib import Path
import sys


# Some developer environments set PYTHONSAFEPATH=1, which prevents Python from
# adding the script directory to sys.path. Add it explicitly so the local helper
# package remains importable when this executable is launched from any cwd.
sys.path.insert(0, str(Path(__file__).resolve().parent))

from codex_lab_package.cli import main


if __name__ == "__main__":
raise SystemExit(main())
19 changes: 19 additions & 0 deletions scripts/codex_lab_package/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Codex Lab Desktop Launcher Packaging

This helper builds a macOS `Codex Lab.app` launcher bundle. The bundle does not
contain or modify OpenAI's Codex Desktop app. Instead, it embeds a Codex Lab CLI
binary, sets `CODEX_CLI_PATH` to that binary, and launches the official
`/Applications/Codex.app` through LaunchServices.

Example:

```shell
scripts/build_codex_lab_app.py \
--codex-bin codex-rs/target/release/codex \
--app-dir /tmp/Codex\ Lab.app \
--shim-dir /tmp/codex-lab-bin \
--force
```

The optional shim directory receives a `codex-lab` wrapper that executes the
same embedded binary used by Desktop mode.
1 change: 1 addition & 0 deletions scripts/codex_lab_package/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Helpers for packaging the Codex Lab desktop launcher."""
99 changes: 99 additions & 0 deletions scripts/codex_lab_package/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
"""Command-line interface for building Codex Lab launcher bundles."""

import argparse
import tempfile
from pathlib import Path

from .layout import DEFAULT_BUNDLE_IDENTIFIER
from .layout import DEFAULT_CODEX_APP_PATH
from .layout import DEFAULT_DISPLAY_NAME
from .layout import CodexLabAppOptions
from .layout import build_codex_lab_app


def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Build a Codex Lab macOS launcher app bundle.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument(
"--codex-bin",
type=Path,
required=True,
help="Prebuilt Codex Lab CLI executable to embed in the app bundle.",
)
parser.add_argument(
"--app-dir",
type=Path,
default=argparse.SUPPRESS,
help=(
"Output .app directory. Defaults to a new temporary "
"directory named Codex Lab.app."
),
)
parser.add_argument(
"--shim-dir",
type=Path,
help="Optional directory where a codex-lab wrapper should be installed.",
)
parser.add_argument(
"--codex-app-path",
type=Path,
default=DEFAULT_CODEX_APP_PATH,
help="Official Codex Desktop app path to launch.",
)
parser.add_argument(
"--bundle-id",
default=DEFAULT_BUNDLE_IDENTIFIER,
help="CFBundleIdentifier for the launcher bundle.",
)
parser.add_argument(
"--display-name",
default=DEFAULT_DISPLAY_NAME,
help="Display name for the launcher bundle.",
)
parser.add_argument(
"--short-version",
default="0.0.0",
help="CFBundleShortVersionString for the launcher bundle.",
)
parser.add_argument(
"--bundle-version",
default="1",
help="CFBundleVersion for the launcher bundle.",
)
parser.add_argument(
"--force",
action="store_true",
help="Replace an existing app bundle or shim.",
)
return parser.parse_args()


def main() -> int:
args = parse_args()
app_dir_arg = getattr(args, "app_dir", None)
app_dir = (
app_dir_arg.resolve()
if app_dir_arg is not None
else Path(tempfile.mkdtemp(prefix="codex-lab-app-")) / "Codex Lab.app"
)

result = build_codex_lab_app(
CodexLabAppOptions(
app_dir=app_dir,
codex_bin=args.codex_bin.resolve(),
codex_app_path=args.codex_app_path,
shim_dir=args.shim_dir.resolve() if args.shim_dir else None,
bundle_identifier=args.bundle_id,
display_name=args.display_name,
short_version=args.short_version,
bundle_version=args.bundle_version,
force=args.force,
)
)

print(f"Built Codex Lab app bundle at {result.app_dir}")
if result.shim_path is not None:
print(f"Installed codex-lab shim at {result.shim_path}")
return 0
Loading
Loading