Skip to content

Merge pull request #218 from cbusillo/fix/skill-resource-path-instruc… #108

Merge pull request #218 from cbusillo/fix/skill-resource-path-instruc…

Merge pull request #218 from cbusillo/fix/skill-resource-path-instruc… #108

Workflow file for this run

name: Release
on:
push:
branches: [ main ]
# Ignore common non-release paths, but DO NOT ignore release.yml so that
# edits to this workflow can intentionally trigger a new release run.
paths-ignore:
- '.github/workflows/issue-triage.yml'
- '.github/workflows/preview-build.yml'
- '.github/workflows/upstream-merge.yml'
- 'examples/**'
- '**/*.test.ts'
- 'test/**'
- '*.md'
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: write
id-token: write
issues: write
pull-requests: write
statuses: write
jobs:
preflight-tests:
name: Preflight Tests (Linux fast E2E)
needs: [determine-version]
if: needs.determine-version.outputs.metadata_required != 'true'
runs-on: ubuntu-24.04
env:
CARGO_HOME: ${{ github.workspace }}/.cargo-home
RUSTUP_HOME: ${{ github.workspace }}/.rustup-home
steps:
- name: Prepare cargo target dir on data disk
shell: bash
run: |
set -euo pipefail
cargo_target_dir="${{ github.workspace }}/.cargo-target"
if [ -d /mnt ] && [ -w /mnt ]; then
cargo_target_dir="/mnt/code-release-preflight-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
fi
echo "CARGO_TARGET_DIR=$cargo_target_dir" >> "$GITHUB_ENV"
echo "CI_CLI_BIN=$cargo_target_dir/dev-fast/code" >> "$GITHUB_ENV"
mkdir -p "$cargo_target_dir"
df -h "$cargo_target_dir"
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Read Rust toolchain channel
id: rust_toolchain
shell: bash
run: |
set -euo pipefail
TOOLCHAIN=$(python3 -c "import sys, pathlib; p=pathlib.Path('code-rs/rust-toolchain.toml').read_text();
try:
import tomllib as tl
except ModuleNotFoundError:
import tomli as tl
print(tl.loads(p)['toolchain']['channel'])")
echo "channel=$TOOLCHAIN" >> "$GITHUB_OUTPUT"
echo "RUST_TOOLCHAIN=$TOOLCHAIN" >> "$GITHUB_ENV"
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ steps.rust_toolchain.outputs.channel }}
- name: Setup Rust Cache
uses: Swatinem/rust-cache@v2
with:
prefix-key: v5-rust
shared-key: code-preflight-${{ steps.rust_toolchain.outputs.channel }}
workspaces: |
code-rs -> target
cache-targets: false
cache-workspace-crates: true
cache-on-failure: false
- name: Build CLI (dev-fast)
shell: bash
run: |
set -euo pipefail
cd code-rs
cargo build --locked --profile dev-fast --bin code
- name: CLI smokes (skip duplicated cargo tests)
shell: bash
env:
SKIP_CARGO_TESTS: "1"
run: bash scripts/ci-tests.sh
- name: Drop dev-fast artifacts before workspace tests
shell: bash
run: |
set -euo pipefail
rm -rf "$CARGO_TARGET_DIR"/dev-fast || true
df -h
- name: Install cargo-nextest
uses: taiki-e/install-action@v2
with:
tool: cargo-nextest
- name: Workspace tests (nextest)
shell: bash
run: |
cd code-rs
cargo nextest run --no-fail-fast --locked
- name: Free disk after tests
if: always()
shell: bash
run: |
echo "Disk usage before cleanup" && df -h
rm -rf ~/.cache/sccache || true
rm -rf "$CARGO_TARGET_DIR" || true
echo "Disk usage after cleanup" && df -h
determine-version:
name: Determine Version
runs-on: [self-hosted, Linux, X64, chris-testing]
outputs:
version: ${{ steps.version.outputs.version }}
metadata_required: ${{ steps.version.outputs.metadata_required }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with: { fetch-depth: 0 }
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Determine next GitHub Release version
id: version
working-directory: codex-cli
shell: bash
run: |
set -euo pipefail
CURRENT_VERSION=$(node -p "require('./package.json').version")
latest_tag=$(git tag --list 'v*' --sort=-v:refname | head -n 1 | sed 's/^v//' || true)
latest_tag="${latest_tag:-0.0.0}"
CANDIDATE=$(printf '%s\n%s\n' "$CURRENT_VERSION" "$latest_tag" | sort -V | tail -n1)
if git rev-parse "v${CANDIDATE}" >/dev/null 2>&1; then
IFS='.' read -ra V <<< "$CANDIDATE"
CANDIDATE="${V[0]}.${V[1]}.$((V[2] + 1))"
fi
while git rev-parse "v${CANDIDATE}" >/dev/null 2>&1; do
IFS='.' read -ra V <<< "$CANDIDATE"
CANDIDATE="${V[0]}.${V[1]}.$((V[2] + 1))"
done
NEW_VERSION="$CANDIDATE"
if [[ "$CURRENT_VERSION" == "$NEW_VERSION" ]]; then
METADATA_REQUIRED=false
else
METADATA_REQUIRED=true
fi
echo "version=${NEW_VERSION}" >> "$GITHUB_OUTPUT"
echo "metadata_required=${METADATA_REQUIRED}" >> "$GITHUB_OUTPUT"
build-binaries:
name: Build ${{ matrix.target }}
needs: [determine-version]
if: needs.determine-version.outputs.metadata_required != 'true'
runs-on: ${{ fromJson(matrix.runs_on) }}
env:
CARGO_HOME: ${{ github.workspace }}/.cargo-home
RUSTUP_HOME: ${{ github.workspace }}/.rustup-home
strategy:
matrix:
include:
# Linux builds
- os: ubuntu-24.04
runs_on: '["ubuntu-24.04"]'
target: x86_64-unknown-linux-musl
artifact: code-x86_64-unknown-linux-musl
- os: ubuntu-24.04-arm
runs_on: '["ubuntu-24.04-arm"]'
target: aarch64-unknown-linux-musl
artifact: code-aarch64-unknown-linux-musl
# GNU variants omitted to reduce asset duplication.
# macOS builds
- os: macos-14
runs_on: '["macos-14"]'
target: x86_64-apple-darwin
artifact: code-x86_64-apple-darwin
- os: macos-14
runs_on: '["macos-14"]'
target: aarch64-apple-darwin
artifact: code-aarch64-apple-darwin
# Windows build
- os: windows-latest
runs_on: '["windows-latest"]'
target: x86_64-pc-windows-msvc
artifact: code-x86_64-pc-windows-msvc.exe
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Read Rust toolchain channel
id: rust_toolchain
shell: bash
run: |
set -euo pipefail
TOOLCHAIN=$(python3 -c "import sys, pathlib; p=pathlib.Path('code-rs/rust-toolchain.toml').read_text();
try:
import tomllib as tl
except ModuleNotFoundError:
import tomli as tl
print(tl.loads(p)['toolchain']['channel'])")
echo "channel=$TOOLCHAIN" >> "$GITHUB_OUTPUT"
echo "RUST_TOOLCHAIN=$TOOLCHAIN" >> "$GITHUB_ENV"
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ steps.rust_toolchain.outputs.channel }}
targets: ${{ matrix.target }}
# Keep target/ across runs so Cargo can no-op when code hasn't changed
- id: rust_cache
name: Setup Rust Cache (target + registries)
uses: Swatinem/rust-cache@v2
with:
prefix-key: v5-rust
shared-key: code-${{ matrix.target }}-toolchain-${{ steps.rust_toolchain.outputs.channel }}
workspaces: |
code-rs -> target
cache-targets: true
cache-workspace-crates: true
cache-on-failure: true
# sccache: skip compiles when possible (doesn't skip linking)
- name: Setup sccache (GHA backend)
uses: mozilla-actions/sccache-action@v0.0.9
with:
version: v0.10.0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Enable sccache
shell: bash
run: |
{
echo "SCCACHE_GHA_ENABLED=true"
echo "RUSTC_WRAPPER=sccache"
echo "SCCACHE_IDLE_TIMEOUT=1800"
echo "SCCACHE_CACHE_SIZE=10G"
} >> "$GITHUB_ENV"
# -------- Platform tuning (minimal, proven) --------
# Linux GNU: use mold if available; prefer system OpenSSL
- name: Linux (gnu) tuning
if: runner.os == 'Linux' && contains(matrix.target, 'gnu')
shell: bash
run: |
set -euo pipefail
if command -v apt-get >/dev/null 2>&1; then
if command -v sudo >/dev/null 2>&1; then
SUDO=(sudo)
elif [[ ${EUID:-$(id -u)} -eq 0 ]]; then
SUDO=()
else
echo "apt-get is available but neither sudo nor root is available; using preinstalled GNU build packages." >&2
SUDO=(false)
fi
if [[ ${SUDO[0]:-} != false ]]; then
"${SUDO[@]}" apt-get update || true
"${SUDO[@]}" apt-get install -y libssl-dev pkg-config mold || true
fi
fi
if command -v clang >/dev/null 2>&1; then
echo 'CC=sccache clang' >> "$GITHUB_ENV"
echo 'CXX=sccache clang++' >> "$GITHUB_ENV"
else
echo 'CC=sccache gcc' >> "$GITHUB_ENV"
echo 'CXX=sccache g++' >> "$GITHUB_ENV"
fi
echo 'OPENSSL_NO_VENDOR=1' >> "$GITHUB_ENV"
echo 'RUSTFLAGS=-Awarnings -C link-arg=-fuse-ld=mold -C debuginfo=0 -C strip=symbols -C panic=abort' >> "$GITHUB_ENV"
# Linux MUSL: reliable static build via musl-gcc (no glibc symbol leaks)
- name: Linux (musl) tuning
if: runner.os == 'Linux' && contains(matrix.target, 'musl')
shell: bash
run: |
set -euo pipefail
if command -v apt-get >/dev/null 2>&1 && ! command -v musl-gcc >/dev/null 2>&1; then
if command -v sudo >/dev/null 2>&1; then
SUDO=(sudo)
elif [[ ${EUID:-$(id -u)} -eq 0 ]]; then
SUDO=()
else
echo "musl-gcc is required but this runner cannot install packages without sudo/root." >&2
exit 1
fi
"${SUDO[@]}" apt-get update
"${SUDO[@]}" apt-get install -y musl-tools pkg-config
fi
{
echo 'CC=musl-gcc'
case "${{ matrix.target }}" in
x86_64-unknown-linux-musl) echo 'CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=musl-gcc' ;;
aarch64-unknown-linux-musl) echo 'CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=musl-gcc' ;;
esac
echo 'PKG_CONFIG_ALLOW_CROSS=1'
echo 'OPENSSL_STATIC=1'
echo 'RUSTFLAGS=-Awarnings -C debuginfo=0 -C strip=symbols -C panic=abort'
} >> "$GITHUB_ENV"
# macOS: stick with Apple toolchain to avoid brew overhead; still cache C via sccache
- name: macOS tuning
if: startsWith(matrix.os, 'macos-')
shell: bash
run: |
{
echo 'CC=sccache clang'
echo 'CXX=sccache clang++'
echo 'RUSTFLAGS=-Awarnings -C debuginfo=0 -C strip=symbols -C panic=abort'
} >> "$GITHUB_ENV"
# Windows: vcpkg not needed (rustls + pure Rust deps)
# Windows: use SChannel (no OpenSSL) + fast linker flags
- name: Windows TLS backend (SChannel) + linker flags
if: matrix.os == 'windows-latest'
shell: pwsh
run: |
# Force libgit2 to use the native Windows TLS stack
"LIBGIT2_SYS_USE_SCHANNEL=1" >> $env:GITHUB_ENV
# If anything in your graph uses curl-sys, prefer SChannel there too
"CURL_SSL_BACKEND=schannel" >> $env:GITHUB_ENV
# Prefer lld-link if present; otherwise MSVC link with good opts
if (Get-Command lld-link -ErrorAction SilentlyContinue) {
"RUSTFLAGS=-Awarnings -Clinker=lld-link -C codegen-units=16 -C debuginfo=0 -C strip=symbols -C panic=abort -C link-arg=/OPT:REF -C link-arg=/OPT:ICF -C link-arg=/DEBUG:NONE" >> $env:GITHUB_ENV
} else {
"RUSTFLAGS=-Awarnings -C codegen-units=16 -C debuginfo=0 -C strip=symbols -C panic=abort -C link-arg=/OPT:REF -C link-arg=/OPT:ICF -C link-arg=/DEBUG:NONE" >> $env:GITHUB_ENV
}
# Prefetch deps so --frozen works even with git deps
- name: Prefetch dependencies (git + registry)
working-directory: code-rs
env:
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
run: cargo fetch --locked
# Inject the display version without touching Cargo manifests
- name: Export CODE_VERSION for Rust build
shell: bash
run: echo "CODE_VERSION=${{ needs.determine-version.outputs.version }}" >> "$GITHUB_ENV"
- name: Build binaries (with timings)
shell: bash
env:
CARGO_INCREMENTAL: "0" # keep off in CI; release builds + sccache
RUST_BACKTRACE: "1"
run: |
cd code-rs
cargo build --release --frozen --locked --timings --target ${{ matrix.target }} --bin code
- name: Post-build smoke (run binary) [Unix]
if: |
(runner.os == 'Linux' && matrix.target == 'x86_64-unknown-linux-musl') ||
(matrix.os == 'ubuntu-24.04-arm' && matrix.target == 'aarch64-unknown-linux-musl') ||
(startsWith(matrix.os, 'macos-') && matrix.target == 'aarch64-apple-darwin')
shell: bash
run: |
set -euo pipefail
exe="code-rs/target/${{ matrix.target }}/release/code"
"$exe" --version
"$exe" completion bash > /dev/null
- name: Post-build smoke (run binary) [Windows]
if: matrix.os == 'windows-latest' && matrix.target == 'x86_64-pc-windows-msvc'
shell: pwsh
run: |
$exe = "code-rs/target/${{ matrix.target }}/release/code.exe"
& $exe --version | Out-Null
& $exe completion bash | Out-Null
- name: sccache stats
shell: bash
run: sccache --show-stats || true
- name: Prepare artifacts
shell: bash
run: |
mkdir -p artifacts
if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
cp code-rs/target/${{ matrix.target }}/release/code.exe artifacts/${{ matrix.artifact }}
else
cp code-rs/target/${{ matrix.target }}/release/code artifacts/${{ matrix.artifact }}
fi
- name: Compress artifacts (Windows)
if: matrix.os == 'windows-latest'
shell: pwsh
run: |
Get-ChildItem artifacts -File | ForEach-Object {
$src = $_.FullName
$dst = "$src.zip"
Compress-Archive -Path $src -DestinationPath $dst -Force
Remove-Item $src -Force
}
- name: Install zstd (Linux)
if: runner.os == 'Linux'
shell: bash
run: |
set -euo pipefail
if command -v zstd >/dev/null 2>&1; then
exit 0
fi
if command -v sudo >/dev/null 2>&1; then
SUDO=(sudo)
elif [[ ${EUID:-$(id -u)} -eq 0 ]]; then
SUDO=()
else
echo "zstd is required but this runner cannot install packages without sudo/root." >&2
exit 1
fi
"${SUDO[@]}" apt-get update -qq
"${SUDO[@]}" apt-get install -y zstd
- name: Compress artifacts (Linux dual-format)
if: runner.os == 'Linux'
shell: bash
run: |
shopt -s nullglob
for f in artifacts/*; do
# Only process regular files; skip any directories
[ -f "$f" ] || continue
base=$(basename "$f")
# .zst (size-optimized)
zstd -T0 -19 --force -o "artifacts/${base}.zst" "$f"
# .tar.gz fallback for users without zstd
tar -C artifacts -czf "artifacts/${base}.tar.gz" "$base"
rm -f "$f"
done
- name: Compress artifacts (macOS dual-format)
if: startsWith(matrix.os, 'macos-')
shell: bash
run: |
shopt -s nullglob
for f in artifacts/*; do
# Only process regular files; skip any directories
[ -f "$f" ] || continue
base=$(basename "$f")
# .zst (size-optimized)
zstd -T0 -19 --force -o "artifacts/${base}.zst" "$f"
# .tar.gz fallback for users without zstd
tar -C artifacts -czf "artifacts/${base}.tar.gz" "$base"
rm -f "$f"
done
- name: Upload binaries (compressed)
uses: actions/upload-artifact@v4
with:
name: binaries-${{ matrix.target }}
path: artifacts/
compression-level: 0
- name: Upload cargo timings
uses: actions/upload-artifact@v4
with:
name: cargo-timings-${{ matrix.target }}
path: code-rs/target/cargo-timings/*.html
if-no-files-found: ignore
compression-level: 0
release:
name: Publish GitHub Release
needs: [determine-version, build-binaries, preflight-tests]
runs-on: [self-hosted, Linux, X64, chris-testing]
if: "always() && !cancelled() && !failure() && !contains(github.event.head_commit.message, '[skip ci]')"
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GH_PAT || secrets.GITHUB_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Require local release metadata
if: needs.determine-version.outputs.metadata_required == 'true'
shell: bash
env:
NEW_VERSION: ${{ needs.determine-version.outputs.version }}
run: |
set -euo pipefail
echo "::error::Release metadata for v${NEW_VERSION} must be prepared locally."
echo "Run 'just local-release-notes', review the generated package version,"
echo "CHANGELOG.md, and docs/release-notes/RELEASE_NOTES.md changes, then"
echo "merge that release metadata PR before publishing."
exit 1
- name: Update package.json version
id: version
working-directory: codex-cli
shell: bash
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
NEW_VERSION="${{ needs.determine-version.outputs.version }}"
npm version "$NEW_VERSION" --no-git-tag-version --allow-same-version
echo "version=${NEW_VERSION}" >> "$GITHUB_OUTPUT"
git add package.json
if git diff --staged --quiet; then
echo "skip_push=true" >> "$GITHUB_OUTPUT"
else
git commit -m "chore(release): ${NEW_VERSION}"
echo "skip_push=false" >> "$GITHUB_OUTPUT"
fi
if ! git rev-parse "v${NEW_VERSION}" >/dev/null 2>&1; then
git tag "v${NEW_VERSION}"
fi
echo "tag=v${NEW_VERSION}" >> "$GITHUB_OUTPUT"
- name: Download all artifacts
if: steps.version.outputs.skip_push == 'true'
uses: actions/download-artifact@v4
with:
path: artifacts/
- name: Prepare release assets
if: steps.version.outputs.skip_push == 'true'
shell: bash
run: |
set -euo pipefail
mkdir -p release-assets
shopt -s nullglob
# Gather all built "code-*" files (zst/tar.gz/zip or raw) recursively under artifacts/
while IFS= read -r -d '' f; do
cp "$f" release-assets/
done < <(find artifacts -type f -name 'code-*' -print0)
# Show what we collected
ls -la release-assets/ || true
- name: Verify local release notes metadata
if: steps.version.outputs.skip_push == 'true'
shell: bash
run: |
set -euo pipefail
scripts/check-release-notes-version.sh --version "${{ steps.version.outputs.version }}"
- name: Push tag
if: steps.version.outputs.skip_push == 'true'
shell: bash
run: git push origin "v${{ steps.version.outputs.version }}" || true
- name: Verify release notes header matches version
if: steps.version.outputs.skip_push == 'true'
shell: bash
run: |
set -euo pipefail
scripts/check-release-notes-version.sh
- name: Generate update manifest
if: steps.version.outputs.skip_push == 'true'
shell: bash
env:
NEW_VERSION: ${{ steps.version.outputs.version }}
run: |
set -euo pipefail
scripts/release/generate-update-manifest.sh \
--version "$NEW_VERSION" \
--commit "$GITHUB_SHA" \
--repository "$GITHUB_REPOSITORY" \
--assets-dir release-assets \
--output release-assets/update-manifest.json
- name: Create GitHub Release
if: steps.version.outputs.skip_push == 'true'
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.version.outputs.version }}
name: Release v${{ steps.version.outputs.version }}
body_path: docs/release-notes/RELEASE_NOTES.md
files: |
release-assets/*
env:
GITHUB_TOKEN: ${{ secrets.GH_PAT || secrets.GITHUB_TOKEN }}