From 9b7652c87e369dafb6a50e97d24670ccd512f1bc Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Thu, 6 Aug 2026 17:49:18 -0700 Subject: [PATCH 01/32] ci: scan approved Codex topics through pinned plans The default-branch trampoline must not start from pull_request_review: that event loads workflow code from the PR merge ref. Replace it with a trusted default-branch scanner. On a five-minute schedule, or an explicit scan dispatch, it pins meta, checks it out read-only, preflights approved candidates with propose-plan --no-push, and calls the App-backed producer only for a valid exact-head review. Stale or policy-invalid approvals are skipped instead of blocking later topics. Plan admission stays pull_request_target with trusted meta code; remove and reorder remain explicit dispatch-only policy operations. --- .github/workflows/codex.yml | 243 ++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 .github/workflows/codex.yml diff --git a/.github/workflows/codex.yml b/.github/workflows/codex.yml new file mode 100644 index 00000000000000..650cfd55baef14 --- /dev/null +++ b/.github/workflows/codex.yml @@ -0,0 +1,243 @@ +name: Refresh codex + +on: + schedule: + - cron: '*/5 * * * *' + workflow_dispatch: + inputs: + operation: + description: Refresh, scan, remove, or reorder a pinned topic + type: choice + options: + - refresh + - scan + - remove + - reorder + default: refresh + lane: + description: codex or codex-unstable for a plan operation + required: false + type: string + topic: + description: Exact topic branch for a plan operation + required: false + type: string + after: + description: Existing topic or root for reorder + required: false + type: string + plan_branch: + description: Optional codex-plan/* branch name + required: false + type: string + pull_request_target: + branches: + - meta + types: + - opened + - reopened + - synchronize + - ready_for_review + +permissions: + actions: read + contents: read + pull-requests: read + +jobs: + refresh: + if: >- + github.event_name == 'workflow_dispatch' && + github.ref == 'refs/heads/codex' && + inputs.operation == 'refresh' + uses: openai/git/.github/workflows/codex.yml@meta + topic_plan_scan: + name: Find one approved topic plan + if: >- + github.event_name == 'schedule' || + (github.event_name == 'workflow_dispatch' && + github.ref == 'refs/heads/codex' && + inputs.operation == 'scan') + runs-on: ubuntu-24.04 + permissions: + contents: read + pull-requests: read + concurrency: + group: codex-topic-plan-scan + cancel-in-progress: false + outputs: + lane: ${{ steps.reviewed.outputs.lane }} + topic: ${{ steps.reviewed.outputs.topic }} + source_tip: ${{ steps.reviewed.outputs.source_tip }} + review_pr: ${{ steps.reviewed.outputs.review_pr }} + env: + GH_TOKEN: ${{ github.token }} + steps: + - name: Pin trusted meta + id: meta + run: | + set -euo pipefail + test "$GITHUB_REPOSITORY" = openai/git + test "$GITHUB_REF" = refs/heads/codex + sha=$(gh api "repos/$GITHUB_REPOSITORY/git/ref/heads/meta" \ + --jq .object.sha) + case "$sha" in + ''|*[!0-9a-f]*) exit 1 ;; + esac + test "${#sha}" = 40 + printf 'sha=%s\n' "$sha" >>"$GITHUB_OUTPUT" + + - name: Check out trusted meta + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + repository: ${{ github.repository }} + ref: ${{ steps.meta.outputs.sha }} + fetch-depth: 0 + persist-credentials: false + + - name: Find one exact approved topic PR + id: reviewed + env: + META_SHA: ${{ steps.meta.outputs.sha }} + run: | + set -euo pipefail + + die () { + printf '%s\n' "$*" >&2 + exit 1 + } + + test "$GITHUB_REPOSITORY" = openai/git + test "$GITHUB_REF" = refs/heads/codex || + die "topic scan must run from the trusted default branch" + test "$(git rev-parse HEAD)" = "$META_SHA" || + die "trusted checkout does not match pinned meta" + gh auth setup-git + mkdir -p "$RUNNER_TEMP/codex-plan-scan" + for lane in codex codex-unstable + do + case "$lane" in + codex) plan=codex.plan ;; + codex-unstable) plan=codex-unstable.plan ;; + esac + test -f "$plan" || + die "trusted meta has no $plan" + gh pr list --repo "$GITHUB_REPOSITORY" --state open \ + --base "$lane" --limit 1000 \ + --json number,isDraft,headRefName,headRefOid,headRepository,reviewDecision | + jq -r --arg lane "$lane" ' + .[] | + select(.isDraft | not) | + select(.reviewDecision == "APPROVED") | + select(.headRepository.nameWithOwner == "openai/git") | + [$lane, .headRefName, .headRefOid, + (.number | tostring)] | @tsv + ' + done | sort -k4,4n >"$RUNNER_TEMP/codex-plan-scan/candidates" + + while IFS=$'\t' read -r lane topic source_tip review_pr + do + test -n "$review_pr" || continue + case "$review_pr" in + *[!0-9]*) die "approved topic PR has invalid number '$review_pr'" ;; + esac + case "$source_tip" in + *[!0-9a-f]*|'') die "approved topic PR has invalid source SHA" ;; + esac + test "${#source_tip}" = 40 || + die "approved topic PR has invalid source SHA" + git check-ref-format "refs/heads/$topic" >/dev/null 2>&1 || + die "approved topic PR has invalid branch '$topic'" + case "$topic" in + ??/codex/*) ;; + *) continue ;; + esac + suffix=${topic#??/codex/} + case "$suffix" in + ''|*/*|*-wip|*-stale) continue ;; + esac + case "$lane" in + codex) + case "$topic" in + *-unstable) continue ;; + esac + plan=codex.plan + ;; + codex-unstable) + case "$topic" in + *-unstable) ;; + *) continue ;; + esac + plan=codex-unstable.plan + ;; + *) die "approved topic PR has invalid lane '$lane'" ;; + esac + pinned=$(git config --no-includes \ + --file "$plan" \ + --get "branch.$topic.source-tip" || :) + test "$pinned" = "$source_tip" && continue + short=$(printf '%.12s' "$source_tip") + slug=${topic##*/} + plan_branch=codex-plan/$lane-$slug-$short + pending=$(gh pr list --repo "$GITHUB_REPOSITORY" \ + --state open --base meta --head "$plan_branch" \ + --json number --jq '.[0].number // empty') || + die "could not inspect pending Codex plan PR" + test -n "$pending" && continue + if ! sh .github/workflows/codex-branch.sh propose-plan \ + --remote origin --lane "$lane" --topic "$topic" \ + --action auto --source-tip "$source_tip" \ + --review-pr "$review_pr" --expected-meta "$META_SHA" \ + --no-push >/dev/null + then + printf 'skipping approved topic PR #%s: preflight failed\n' \ + "$review_pr" >&2 + continue + fi + { + printf 'lane=%s\n' "$lane" + printf 'topic=%s\n' "$topic" + printf 'source_tip=%s\n' "$source_tip" + printf 'review_pr=%s\n' "$review_pr" + } >>"$GITHUB_OUTPUT" + exit 0 + done <"$RUNNER_TEMP/codex-plan-scan/candidates" + topic_plan_propose: + name: Propose reviewed topic plan + needs: topic_plan_scan + if: needs.topic_plan_scan.outputs.review_pr != '' + permissions: + contents: read + pull-requests: read + uses: openai/git/.github/workflows/codex-plan-propose.yml@meta + with: + lane: ${{ needs.topic_plan_scan.outputs.lane }} + topic: ${{ needs.topic_plan_scan.outputs.topic }} + action: auto + source_tip: ${{ needs.topic_plan_scan.outputs.source_tip }} + review_pr: ${{ needs.topic_plan_scan.outputs.review_pr }} + policy_plan_propose: + name: Propose explicit plan policy + if: >- + github.event_name == 'workflow_dispatch' && + github.ref == 'refs/heads/codex' && + (inputs.operation == 'remove' || inputs.operation == 'reorder') + permissions: + contents: read + pull-requests: read + uses: openai/git/.github/workflows/codex-plan-propose.yml@meta + with: + lane: ${{ inputs.lane }} + topic: ${{ inputs.topic }} + action: ${{ inputs.operation }} + after: ${{ inputs.after }} + plan_branch: ${{ inputs.plan_branch }} + plan_admission: + name: Codex plan admission + if: >- + github.event_name == 'pull_request_target' && + github.event.pull_request.base.ref == 'meta' + permissions: + contents: read + pull-requests: write + uses: openai/git/.github/workflows/codex-plan-admission.yml@meta From 63b49fedd9ad54667e21814fe5466bb08a0e99cb Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Tue, 4 Aug 2026 09:44:11 -0700 Subject: [PATCH 02/32] maintenance: account for promisor pack geometry Commit 9bc151850c (builtin/maintenance: introduce "geometric-repack" task, 2025-10-24) added a new maintenance task to perform either a geometric repack, or an all-into-one repack if the geometric repack would itself produce a single pack. Some time later, commit dcc9c7ef47 (builtin/repack: handle promisor packs with geometric repacking, 2026-01-05) taught the geometric repacking machinery to separate promisor packs from ordinary ones, but did not update the maintenance task accordingly. As a consequence, the geometric-repack maintenance task only considers the non-promisor pack progression. It falls back to all-into-one whenever a geometric repack would roll up all non-promisor packs into a single pack, even if the promisor progression would keep a large pack and roll up only smaller ones. Check both progressions before choosing the repack mode. If either leaves a pack above its split, geometric repack still avoids rewriting that pack, whereas the all-into-one fallback would rewrite it. Use the fallback only when neither progression leaves a pack behind. That preserves the reason for the fallback: let the all-into-one repack handle unreachable objects when it is not rewriting more packs than the geometric repack. Signed-off-by: Taylor Blau --- odb/source-files.c | 3 ++- t/t7900-maintenance.sh | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/odb/source-files.c b/odb/source-files.c index b7b3a297bb79d7..693bd661b8899a 100644 --- a/odb/source-files.c +++ b/odb/source-files.c @@ -693,7 +693,8 @@ int odb_source_files_optimize(struct odb_source *source, pack_geometry_init(&geometry, &existing_packs, &po_args); pack_geometry_split(&geometry); - if (geometry.split < geometry.pack_nr) { + if (geometry.split < geometry.pack_nr || + geometry.promisor_split < geometry.promisor_pack_nr) { strvec_pushf(&repack_cmd.args, "--geometric=%d", geometry.split_factor); } else { diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh index 5fbb16f0f0e59c..3c655a9dba2cbb 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh @@ -617,6 +617,13 @@ run_and_verify_geometric_pack () { test_must_be_empty orphaned-idx } +pack_promisor () { + packdir=.git/objects/pack && + pack="$(echo "$@" | git pack-objects --revs "$packdir/pack")" && + touch "$packdir/pack-$pack.promisor" && + echo "$pack" +} + test_expect_success 'geometric repacking task' ' test_when_finished "rm -rf repo" && git init repo && @@ -693,6 +700,41 @@ test_expect_success 'geometric repacking task' ' ) ' +test_expect_success 'geometric repacking task handles promisor packs' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + git config set maintenance.auto false && + git remote add promisor garbage && + git config set remote.promisor.promisor true && + + for n in $(test_seq 6) + do + test_commit $n || return 1 + done && + + A="$(pack_promisor 1)" && + B="$(pack_promisor 1..2)" && + pack_promisor 2..6 >/dev/null && + git prune-packed && + + ls .git/objects/pack/*.promisor | sort >promisors.before && + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \ + git maintenance run --quiet --task=geometric-repack && + ls .git/objects/pack/*.promisor | sort >promisors.after && + + test_subcommand git repack -d -l -q --geometric=2 \ + --write-midx expect && + comm -23 promisors.before promisors.after >actual && + test_cmp expect actual + ) +' + test_geometric_repack_needed () { NEEDED="$1" GEOMETRIC_CONFIG="$2" && From bccc1fd882f27143f7331192a75c6281ba76e2f7 Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Tue, 4 Aug 2026 09:44:13 -0700 Subject: [PATCH 03/32] maintenance: trigger --auto for promisor rollups Commit 9bc151850c (builtin/maintenance: introduce "geometric-repack" task, 2025-10-24) added an auto condition for the geometric-repack task. It runs the task when ordinary packs need to be combined or when the number of loose objects crosses the configured threshold. Later on in commit dcc9c7ef47 (builtin/repack: handle promisor packs with geometric repacking, 2026-01-05), the geometric repack machinery started handling promisor packs separately, but did not correspondingly update the auto condition. As a result, a repository can have promisor packs ready to combine while its non-promisor packs and loose object count require no work. In that case, `--auto` skips the task even though a geometric repack would combine at least two promisor packs. Check `geometry.promisor_split` alongside `geometry.split`. There is some fallout in t5331: the new condition makes a filtered clone eligible for auto-maintenance before the test inspects its promisor packs. Disable auto-maintenance in that fixture so it continues to test `--stdin-packs`, not the maintenance task. Signed-off-by: Taylor Blau --- odb/source-files.c | 2 +- t/t5331-pack-objects-stdin.sh | 3 ++- t/t7900-maintenance.sh | 23 +++++++++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/odb/source-files.c b/odb/source-files.c index 693bd661b8899a..33192514aac46b 100644 --- a/odb/source-files.c +++ b/odb/source-files.c @@ -559,7 +559,7 @@ bool odb_source_files_optimize_required(struct odb_source *source, * When we'd merge at least two packs with one another we always * perform the repack. */ - if (geometry.split) { + if (geometry.split || geometry.promisor_split) { ret = true; goto out; } diff --git a/t/t5331-pack-objects-stdin.sh b/t/t5331-pack-objects-stdin.sh index c74b5861af322f..2a983e28ac43e7 100755 --- a/t/t5331-pack-objects-stdin.sh +++ b/t/t5331-pack-objects-stdin.sh @@ -368,7 +368,8 @@ test_expect_success '--stdin-packs does not perform backfill fetch' ' git -C remote config set --local uploadpack.allowfilter 1 && git -C remote config set --local uploadpack.allowanysha1inwant 1 && - git clone --filter=tree:0 "file://$(pwd)/remote" client && + git -c maintenance.auto=false clone --filter=tree:0 \ + "file://$(pwd)/remote" client && ( cd client && ls .git/objects/pack/*.promisor | sed "s|.*/||; s/\.promisor$/.pack/" >packs && diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh index 3c655a9dba2cbb..e1e12e6d57183d 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh @@ -790,6 +790,29 @@ test_expect_success 'geometric repacking with --auto' ' ) ' +test_expect_success 'geometric repacking with --auto handles promisor packs' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + git config set maintenance.auto false && + git remote add promisor garbage && + git config set remote.promisor.promisor true && + + for n in $(test_seq 6) + do + test_commit $n || return 1 + done && + + pack_promisor 1 >/dev/null && + pack_promisor 1..2 >/dev/null && + pack_promisor 2..6 >/dev/null && + git prune-packed && + + test_geometric_repack_needed true auto=9000 + ) +' + test_expect_success 'geometric repacking honors configured split factor' ' test_when_finished "rm -rf repo" && git init repo && From 92b468d862225fa8df193c81907910e292cf9111 Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Mon, 13 Jul 2026 18:45:12 -0700 Subject: [PATCH 04/32] ci: publish Codex Git release artifacts --- .github/workflows/codex-release.yml | 461 ++++++++++++++++++++++++++++ 1 file changed, 461 insertions(+) create mode 100644 .github/workflows/codex-release.yml diff --git a/.github/workflows/codex-release.yml b/.github/workflows/codex-release.yml new file mode 100644 index 00000000000000..025d3a1998d450 --- /dev/null +++ b/.github/workflows/codex-release.yml @@ -0,0 +1,461 @@ +name: Codex Git release + +on: + push: + branches: + - codex + +permissions: + contents: read + +concurrency: + group: codex-git-release-${{ github.sha }} + cancel-in-progress: false + +jobs: + version: + name: Determine version + runs-on: ubuntu-24.04 + outputs: + describe: ${{ steps.version.outputs.describe }} + upstream_tag: ${{ steps.version.outputs.upstream_tag }} + version: ${{ steps.version.outputs.version }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Derive OpenAI version from git describe + id: version + shell: bash + run: | + set -euo pipefail + describe="$(git describe \ + --match 'v[0-9]*' \ + --exclude 'v*-openai.*' \ + --long \ + --always \ + --abbrev=12 \ + "$GITHUB_SHA")" + + if [[ "$describe" =~ ^(.+)-([0-9]+)-g([0-9a-f]+)$ ]] + then + upstream_tag="${BASH_REMATCH[1]}" + version="$upstream_tag-openai.${BASH_REMATCH[2]}.g${BASH_REMATCH[3]}" + else + upstream_tag= + version="openai-$describe" + fi + git check-ref-format "refs/tags/$version" + printf 'describe=%s\n' "$describe" | tee -a "$GITHUB_OUTPUT" + printf 'upstream_tag=%s\n' "$upstream_tag" | tee -a "$GITHUB_OUTPUT" + printf 'version=%s\n' "$version" | tee -a "$GITHUB_OUTPUT" + + build: + name: ${{ matrix.name }} + needs: version + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - name: macOS arm64 + os: macos-15 + target_platform: macOS + asset_platform: macOS + arch: arm64 + binary: /tmp/build/git/bin/git + file_pattern: Mach-O 64-bit executable arm64 + has_gcm: true + - name: macOS x64 + os: macos-15-intel + target_platform: macOS + asset_platform: macOS + arch: x64 + binary: /tmp/build/git/bin/git + file_pattern: Mach-O 64-bit executable x86_64 + has_gcm: true + - name: Linux arm64 + os: ubuntu-22.04 + target_platform: ubuntu + asset_platform: ubuntu + arch: arm64 + binary: /tmp/build/git/bin/git + file_pattern: ELF 64-bit.*ARM aarch64 + has_gcm: false + - name: Linux x64 + os: ubuntu-22.04 + target_platform: ubuntu + asset_platform: ubuntu + arch: x64 + binary: /tmp/build/git/bin/git + file_pattern: ELF 64-bit.*x86-64 + has_gcm: true + - name: Windows arm64 + os: windows-2025 + target_platform: win32 + asset_platform: windows + arch: arm64 + binary: /tmp/build/git/clangarm64/bin/git.exe + file_pattern: PE32\+.*Aarch64 + has_gcm: true + sdk_arch: aarch64 + sdk_flavor: full + mingw_dir: clangarm64 + mingit_arch: arm64 + mingit_filename: MinGit-2.55.0.2-arm64.zip + mingit_url: https://github.com/git-for-windows/git/releases/download/v2.55.0.windows.2/MinGit-2.55.0.2-arm64.zip + mingit_sha256: 0b2b81fdce284efd174cbb51b886ccea2fd271679c4b5c21f07d9e03bae51413 + - name: Windows x64 + os: windows-2025 + target_platform: win32 + asset_platform: windows + arch: x64 + binary: /tmp/build/git/mingw64/bin/git.exe + file_pattern: PE32\+.*x86-64 + has_gcm: true + sdk_arch: x86_64 + sdk_flavor: minimal + mingw_dir: mingw64 + mingit_arch: amd64 + mingit_filename: MinGit-2.55.0.2-64-bit.zip + mingit_url: https://github.com/git-for-windows/git/releases/download/v2.55.0.windows.2/MinGit-2.55.0.2-64-bit.zip + mingit_sha256: e3ea2944cea4b3fabcd69c7c1669ef69b1b66c05ac7806d81224d0abad2dec31 + + steps: + # Keep the packaging contract, dependency pins, and platform build logic + # aligned with the artifacts already consumed by Codex and GitHub Desktop. + - name: Check out Dugite Native + uses: actions/checkout@v6 + with: + repository: desktop/dugite-native + ref: f97e50add48cdcff053a69d95aa343a4a4a258c2 + path: dugite-native + fetch-depth: 0 + persist-credentials: false + + - name: Check out this Git revision + uses: actions/checkout@v6 + with: + ref: ${{ github.sha }} + path: dugite-native/git + fetch-depth: 1 + persist-credentials: false + + - name: Give the source an immutable package version + shell: bash + working-directory: dugite-native/git + env: + VERSION: ${{ needs.version.outputs.version }} + run: | + git \ + -c 'user.name=github-actions[bot]' \ + -c 'user.email=41898282+github-actions[bot]@users.noreply.github.com' \ + tag -a "$VERSION" -m "$VERSION" + + # Match Dugite Native's compatibility choice for its macOS x64 build. + - name: Select Xcode 16.4 + if: matrix.target_platform == 'macOS' && matrix.arch == 'x64' + run: | + sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer/ + sudo rm -rf /Library/Developer/CommandLineTools + + - name: Install Linux build dependencies + if: matrix.target_platform == 'ubuntu' + run: | + sudo apt-get update + sudo apt-get install -y \ + autoconf \ + automake \ + build-essential \ + ca-certificates \ + curl \ + gettext \ + jq \ + lsb-release \ + pkg-config + + - name: Install Linux x64 build dependencies + if: matrix.target_platform == 'ubuntu' && matrix.arch == 'x64' + run: | + sudo apt-get install -y \ + libcurl4-gnutls-dev \ + libexpat1-dev \ + libssl-dev \ + zlib1g-dev + + - name: Install Linux arm64 build dependencies + if: matrix.target_platform == 'ubuntu' && matrix.arch == 'arm64' + run: | + sudo sed -i "s/^deb/deb [arch=amd64,i386]/g" /etc/apt/sources.list + release="$(lsb_release -s -c)" + echo "deb [arch=arm64,armhf] http://azure.ports.ubuntu.com/ ${release} main universe multiverse restricted" | sudo tee -a /etc/apt/sources.list + echo "deb [arch=arm64,armhf] http://azure.ports.ubuntu.com/ ${release}-updates main universe multiverse restricted" | sudo tee -a /etc/apt/sources.list + sudo dpkg --add-architecture arm64 + sudo apt-get update + sudo apt-get install -y \ + binutils-aarch64-linux-gnu \ + gcc-aarch64-linux-gnu \ + libcurl4-gnutls-dev:arm64 \ + libexpat1-dev:arm64 \ + libssl-dev:arm64 \ + zlib1g-dev:arm64 + + # Dugite Native currently pins MinGit 2.53. Keep its build script and + # dependency schema, but match the runtime to the Git series we compile. + - name: Select the matching MinGit runtime + if: matrix.target_platform == 'win32' + shell: bash + working-directory: dugite-native + env: + MINGIT_ARCH: ${{ matrix.mingit_arch }} + MINGIT_FILENAME: ${{ matrix.mingit_filename }} + MINGIT_SHA256: ${{ matrix.mingit_sha256 }} + MINGIT_URL: ${{ matrix.mingit_url }} + MINGIT_VERSION: v2.55.0 + SOURCE_UPSTREAM_TAG: ${{ needs.version.outputs.upstream_tag }} + run: | + set -euo pipefail + test "$SOURCE_UPSTREAM_TAG" = "$MINGIT_VERSION" + updated="$(mktemp)" + jq \ + --arg arch "$MINGIT_ARCH" \ + --arg checksum "$MINGIT_SHA256" \ + --arg filename "$MINGIT_FILENAME" \ + --arg url "$MINGIT_URL" \ + --arg version "$MINGIT_VERSION" \ + '.git.version = $version | + (.git.files[] | + select(.platform == "windows" and .arch == $arch)) |= + (.filename = $filename | + .url = $url | + .checksum = $checksum)' \ + dependencies.json >"$updated" + mv "$updated" dependencies.json + + - name: Set up Git for Windows SDK + if: matrix.target_platform == 'win32' + uses: git-for-windows/setup-git-for-windows-sdk@v2 + with: + architecture: ${{ matrix.sdk_arch }} + flavor: ${{ matrix.sdk_flavor }} + cache: false + + # Dugite cross-compiles several targets. Keep Git's optional Rust + # library disabled until its Makefile can direct Cargo at those targets. + - name: Build the Dugite Native distribution + shell: bash + working-directory: dugite-native + env: + NO_RUST: 1 + TARGET_PLATFORM: ${{ matrix.target_platform }} + TARGET_ARCH: ${{ matrix.arch }} + run: | + set -euo pipefail + if test "$TARGET_PLATFORM" = win32 + then + . /etc/profile + fi + script/build.sh + + # Dugite Native compiles its Git submodule on macOS and Linux. On + # Windows it starts from MinGit, so replace MinGit's Git programs with + # the build from this repository while retaining the portable runtime. + - name: Install this Git build into the Windows distribution + if: matrix.target_platform == 'win32' + shell: bash + working-directory: dugite-native/git + env: + MINGW_DIR: ${{ matrix.mingw_dir }} + run: | + set -euo pipefail + . /etc/profile + + make_args=( + "prefix=/$MINGW_DIR" + NO_PERL=YesPlease + NO_RUST=YesPlease + NO_TCLTK=YesPlease + NO_GETTEXT=YesPlease + NO_INSTALL_HARDLINKS=YesPlease + NO_CROSS_DIRECTORY_HARDLINKS=YesPlease + ) + jobs="$(nproc)" + make -j"$jobs" "${make_args[@]}" all + make "${make_args[@]}" DESTDIR=/tmp/build/git strip install + + - name: Verify distribution layout and provenance + shell: bash + env: + TARGET_PLATFORM: ${{ matrix.target_platform }} + MINGW_DIR: ${{ matrix.mingw_dir }} + GIT_BINARY: ${{ matrix.binary }} + FILE_PATTERN: ${{ matrix.file_pattern }} + HAS_GCM: ${{ matrix.has_gcm }} + run: | + set -euo pipefail + if test "$TARGET_PLATFORM" = win32 + then + . /etc/profile + test -f /tmp/build/git/cmd/git.exe + test -f "/tmp/build/git/$MINGW_DIR/libexec/git-core/git-lfs.exe" + test -d "/tmp/build/git/$MINGW_DIR/share/git-core/templates" + if test "$HAS_GCM" = true + then + test -f "/tmp/build/git/$MINGW_DIR/bin/git-credential-manager.exe" + fi + else + test -x /tmp/build/git/libexec/git-core/git-lfs + test -d /tmp/build/git/share/git-core/templates + if test "$HAS_GCM" = true + then + test -x /tmp/build/git/libexec/git-core/git-credential-manager + fi + fi + test -f /tmp/build/git/etc/gitconfig + + file "$GIT_BINARY" | tee /tmp/git-file-type + grep -E "$FILE_PATTERN" /tmp/git-file-type + strings "$GIT_BINARY" | grep -F "$GITHUB_SHA" + + - name: Smoke-test the native x64 distribution + if: matrix.arch == 'x64' + shell: bash + env: + TARGET_PLATFORM: ${{ matrix.target_platform }} + MINGW_DIR: ${{ matrix.mingw_dir }} + run: | + set -euo pipefail + smoke=/tmp/codex-git-smoke + mkdir -p "$smoke/home" + + if test "$TARGET_PLATFORM" = win32 + then + . /etc/profile + git_binary=/tmp/build/git/cmd/git.exe + git_env=( + "PATH=/tmp/build/git/cmd:/tmp/build/git/$MINGW_DIR/bin:/tmp/build/git/usr/bin:$PATH" + ) + else + git_binary=/tmp/build/git/bin/git + git_env=( + GIT_CONFIG_SYSTEM=/tmp/build/git/etc/gitconfig + GIT_EXEC_PATH=/tmp/build/git/libexec/git-core + GIT_TEMPLATE_DIR=/tmp/build/git/share/git-core/templates + ) + if test "$TARGET_PLATFORM" = ubuntu + then + git_env+=( + GIT_SSL_CAINFO=/tmp/build/git/ssl/cacert.pem + PREFIX=/tmp/build/git + ) + fi + fi + git_env+=("HOME=$smoke/home" GIT_TERMINAL_PROMPT=0) + + build_options="$(env "${git_env[@]}" "$git_binary" --version --build-options)" + printf '%s\n' "$build_options" + grep -F "built from commit: $GITHUB_SHA" <<<"$build_options" + env "${git_env[@]}" "$git_binary" lfs version + env "${git_env[@]}" "$git_binary" credential-manager --version + + env "${git_env[@]}" "$git_binary" init --quiet "$smoke/repo" + echo test >"$smoke/repo/file" + env "${git_env[@]}" "$git_binary" -C "$smoke/repo" add file + env "${git_env[@]}" "$git_binary" -C "$smoke/repo" \ + -c user.name='Codex Git CI' \ + -c user.email='codex-git-ci@openai.com' \ + commit --quiet -m initial + test -z "$(env "${git_env[@]}" "$git_binary" -C "$smoke/repo" status --porcelain)" + + - name: Package with Dugite Native + shell: bash + working-directory: dugite-native + env: + ASSET_PLATFORM: ${{ matrix.asset_platform }} + TARGET_PLATFORM: ${{ matrix.target_platform }} + TARGET_ARCH: ${{ matrix.arch }} + VERSION: ${{ needs.version.outputs.version }} + run: | + set -euo pipefail + if test "$TARGET_PLATFORM" = win32 + then + . /etc/profile + fi + script/package.sh + + for extension in tar.gz lzma + do + matches=( + output/dugite-native-"$VERSION"-*-"$ASSET_PLATFORM"-"$TARGET_ARCH.$extension" + ) + test "${#matches[@]}" -eq 1 + test -f "${matches[0]}" + + destination="output/git-$VERSION-$ASSET_PLATFORM-$TARGET_ARCH.$extension" + mv "${matches[0]}" "$destination" + mv "${matches[0]}.sha256" "$destination.sha256" + done + + for checksum in output/*.sha256 + do + archive="${checksum%.sha256}" + expected="$(tr -d '\r\n' <"$checksum")" + if command -v sha256sum >/dev/null 2>&1 + then + actual="$(sha256sum "$archive" | awk '{print $1}')" + else + actual="$(shasum -a 256 "$archive" | awk '{print $1}')" + fi + test "$actual" = "$expected" + done + + - name: Upload release assets + uses: actions/upload-artifact@v7 + with: + name: git-${{ matrix.asset_platform }}-${{ matrix.arch }} + path: dugite-native/output/git-* + if-no-files-found: error + retention-days: 7 + + release: + name: Publish GitHub prerelease + needs: + - version + - build + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: write + steps: + - name: Download release assets + uses: actions/download-artifact@v8 + with: + pattern: git-* + path: artifacts + merge-multiple: true + + - name: Publish immutable prerelease + env: + GH_TOKEN: ${{ github.token }} + SOURCE_DESCRIPTION: ${{ needs.version.outputs.describe }} + VERSION: ${{ needs.version.outputs.version }} + run: | + set -euo pipefail + assets=(artifacts/git-*) + + if gh release view "$VERSION" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1 + then + gh release upload "$VERSION" "${assets[@]}" \ + --repo "$GITHUB_REPOSITORY" \ + --clobber + else + gh release create "$VERSION" "${assets[@]}" \ + --repo "$GITHUB_REPOSITORY" \ + --target "$GITHUB_SHA" \ + --title "$VERSION" \ + --notes "OpenAI Git release artifacts for $SOURCE_DESCRIPTION, built from $GITHUB_SHA for Codex." \ + --prerelease + fi From 28389e3159bda051120620f7cccefabf5748de0b Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Mon, 13 Jul 2026 19:37:49 -0700 Subject: [PATCH 05/32] ci: prepare Windows package before SDK setup --- .github/workflows/codex-release.yml | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/.github/workflows/codex-release.yml b/.github/workflows/codex-release.yml index 025d3a1998d450..389877044edf6c 100644 --- a/.github/workflows/codex-release.yml +++ b/.github/workflows/codex-release.yml @@ -235,14 +235,6 @@ jobs: dependencies.json >"$updated" mv "$updated" dependencies.json - - name: Set up Git for Windows SDK - if: matrix.target_platform == 'win32' - uses: git-for-windows/setup-git-for-windows-sdk@v2 - with: - architecture: ${{ matrix.sdk_arch }} - flavor: ${{ matrix.sdk_flavor }} - cache: false - # Dugite cross-compiles several targets. Keep Git's optional Rust # library disabled until its Makefile can direct Cargo at those targets. - name: Build the Dugite Native distribution @@ -254,12 +246,16 @@ jobs: TARGET_ARCH: ${{ matrix.arch }} run: | set -euo pipefail - if test "$TARGET_PLATFORM" = win32 - then - . /etc/profile - fi script/build.sh + - name: Set up Git for Windows SDK + if: matrix.target_platform == 'win32' + uses: git-for-windows/setup-git-for-windows-sdk@v2 + with: + architecture: ${{ matrix.sdk_arch }} + flavor: ${{ matrix.sdk_flavor }} + cache: false + # Dugite Native compiles its Git submodule on macOS and Linux. On # Windows it starts from MinGit, so replace MinGit's Git programs with # the build from this repository while retaining the portable runtime. From 6e201030168f082a55e85ab6ad98dd4b4d1bff75 Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Mon, 13 Jul 2026 19:39:37 -0700 Subject: [PATCH 06/32] ci: use Windows runner processor count --- .github/workflows/codex-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codex-release.yml b/.github/workflows/codex-release.yml index 389877044edf6c..636bc9471099dc 100644 --- a/.github/workflows/codex-release.yml +++ b/.github/workflows/codex-release.yml @@ -278,7 +278,7 @@ jobs: NO_INSTALL_HARDLINKS=YesPlease NO_CROSS_DIRECTORY_HARDLINKS=YesPlease ) - jobs="$(nproc)" + jobs="${NUMBER_OF_PROCESSORS:-2}" make -j"$jobs" "${make_args[@]}" all make "${make_args[@]}" DESTDIR=/tmp/build/git strip install From 37cbc1921fbaa0fac39f2a44010afcf4ad70a3b1 Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Mon, 13 Jul 2026 19:46:35 -0700 Subject: [PATCH 07/32] ci: stabilize Windows SDK environment --- .github/workflows/codex-release.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/codex-release.yml b/.github/workflows/codex-release.yml index 636bc9471099dc..f3be531747d08d 100644 --- a/.github/workflows/codex-release.yml +++ b/.github/workflows/codex-release.yml @@ -117,7 +117,7 @@ jobs: file_pattern: PE32\+.*x86-64 has_gcm: true sdk_arch: x86_64 - sdk_flavor: minimal + sdk_flavor: full mingw_dir: mingw64 mingit_arch: amd64 mingit_filename: MinGit-2.55.0.2-64-bit.zip @@ -267,7 +267,6 @@ jobs: MINGW_DIR: ${{ matrix.mingw_dir }} run: | set -euo pipefail - . /etc/profile make_args=( "prefix=/$MINGW_DIR" @@ -294,7 +293,6 @@ jobs: set -euo pipefail if test "$TARGET_PLATFORM" = win32 then - . /etc/profile test -f /tmp/build/git/cmd/git.exe test -f "/tmp/build/git/$MINGW_DIR/libexec/git-core/git-lfs.exe" test -d "/tmp/build/git/$MINGW_DIR/share/git-core/templates" @@ -329,7 +327,6 @@ jobs: if test "$TARGET_PLATFORM" = win32 then - . /etc/profile git_binary=/tmp/build/git/cmd/git.exe git_env=( "PATH=/tmp/build/git/cmd:/tmp/build/git/$MINGW_DIR/bin:/tmp/build/git/usr/bin:$PATH" @@ -376,10 +373,6 @@ jobs: VERSION: ${{ needs.version.outputs.version }} run: | set -euo pipefail - if test "$TARGET_PLATFORM" = win32 - then - . /etc/profile - fi script/package.sh for extension in tar.gz lzma @@ -416,6 +409,15 @@ jobs: if-no-files-found: error retention-days: 7 + # The arm64 SDK puts its target Git first on PATH, but action cleanup + # runs on the x64 host and therefore needs the runner's native Git. + - name: Restore native Git for action cleanup + if: always() && matrix.target_platform == 'win32' + shell: pwsh + run: | + "C:\Program Files\Git\cmd" | + Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + release: name: Publish GitHub prerelease needs: From e80483541c490aab000f2d83e9ad418e2c400abd Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Mon, 13 Jul 2026 19:58:17 -0700 Subject: [PATCH 08/32] ci: build Windows arm64 on native runner --- .github/workflows/codex-release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codex-release.yml b/.github/workflows/codex-release.yml index f3be531747d08d..155d0d09cf544e 100644 --- a/.github/workflows/codex-release.yml +++ b/.github/workflows/codex-release.yml @@ -94,7 +94,7 @@ jobs: file_pattern: ELF 64-bit.*x86-64 has_gcm: true - name: Windows arm64 - os: windows-2025 + os: windows-11-arm target_platform: win32 asset_platform: windows arch: arm64 @@ -314,8 +314,8 @@ jobs: grep -E "$FILE_PATTERN" /tmp/git-file-type strings "$GIT_BINARY" | grep -F "$GITHUB_SHA" - - name: Smoke-test the native x64 distribution - if: matrix.arch == 'x64' + - name: Smoke-test the native distribution + if: matrix.arch == 'x64' || matrix.target_platform == 'win32' shell: bash env: TARGET_PLATFORM: ${{ matrix.target_platform }} From c3d385952b29b1a0a00daa7e9b7813f4f8606570 Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Mon, 13 Jul 2026 20:06:08 -0700 Subject: [PATCH 09/32] ci: recognize Windows arm64 binaries --- .github/workflows/codex-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codex-release.yml b/.github/workflows/codex-release.yml index 155d0d09cf544e..076fe83c45dd4a 100644 --- a/.github/workflows/codex-release.yml +++ b/.github/workflows/codex-release.yml @@ -99,7 +99,7 @@ jobs: asset_platform: windows arch: arm64 binary: /tmp/build/git/clangarm64/bin/git.exe - file_pattern: PE32\+.*Aarch64 + file_pattern: PE32\+.*ARM64 has_gcm: true sdk_arch: aarch64 sdk_flavor: full From 47e0a9208f1de93a3c658367ab2006f3978df897 Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Tue, 14 Jul 2026 10:07:14 -0700 Subject: [PATCH 10/32] ci: slim Codex release artifacts --- .github/workflows/codex-release.yml | 44 ++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codex-release.yml b/.github/workflows/codex-release.yml index 076fe83c45dd4a..de2ac63f188a7d 100644 --- a/.github/workflows/codex-release.yml +++ b/.github/workflows/codex-release.yml @@ -68,7 +68,8 @@ jobs: arch: arm64 binary: /tmp/build/git/bin/git file_pattern: Mach-O 64-bit executable arm64 - has_gcm: true + has_gcm: false + max_tar_bytes: 33554432 - name: macOS x64 os: macos-15-intel target_platform: macOS @@ -76,7 +77,8 @@ jobs: arch: x64 binary: /tmp/build/git/bin/git file_pattern: Mach-O 64-bit executable x86_64 - has_gcm: true + has_gcm: false + max_tar_bytes: 33554432 - name: Linux arm64 os: ubuntu-22.04 target_platform: ubuntu @@ -85,6 +87,7 @@ jobs: binary: /tmp/build/git/bin/git file_pattern: ELF 64-bit.*ARM aarch64 has_gcm: false + max_tar_bytes: 41943040 - name: Linux x64 os: ubuntu-22.04 target_platform: ubuntu @@ -92,7 +95,8 @@ jobs: arch: x64 binary: /tmp/build/git/bin/git file_pattern: ELF 64-bit.*x86-64 - has_gcm: true + has_gcm: false + max_tar_bytes: 41943040 - name: Windows arm64 os: windows-11-arm target_platform: win32 @@ -101,6 +105,7 @@ jobs: binary: /tmp/build/git/clangarm64/bin/git.exe file_pattern: PE32\+.*ARM64 has_gcm: true + max_tar_bytes: 83886080 sdk_arch: aarch64 sdk_flavor: full mingw_dir: clangarm64 @@ -116,6 +121,7 @@ jobs: binary: /tmp/build/git/mingw64/bin/git.exe file_pattern: PE32\+.*x86-64 has_gcm: true + max_tar_bytes: 83886080 sdk_arch: x86_64 sdk_flavor: full mingw_dir: mingw64 @@ -235,6 +241,20 @@ jobs: dependencies.json >"$updated" mv "$updated" dependencies.json + # Codex does not configure or invoke GCM on macOS or Linux. The + # self-contained .NET payload accounts for most of those bundles, while + # Windows MinGit configures credential.helper=manager and must retain it. + - name: Omit unused GCM from POSIX bundles + if: matrix.target_platform != 'win32' + shell: bash + working-directory: dugite-native + run: | + set -euo pipefail + updated="$(mktemp)" + jq '."git-credential-manager".files = []' \ + dependencies.json >"$updated" + mv "$updated" dependencies.json + # Dugite cross-compiles several targets. Keep Git's optional Rust # library disabled until its Makefile can direct Cargo at those targets. - name: Build the Dugite Native distribution @@ -259,6 +279,8 @@ jobs: # Dugite Native compiles its Git submodule on macOS and Linux. On # Windows it starts from MinGit, so replace MinGit's Git programs with # the build from this repository while retaining the portable runtime. + # MinGit omits dashed builtin aliases; installing them as copies would + # add hundreds of redundant MiB to the archive. - name: Install this Git build into the Windows distribution if: matrix.target_platform == 'win32' shell: bash @@ -276,6 +298,7 @@ jobs: NO_GETTEXT=YesPlease NO_INSTALL_HARDLINKS=YesPlease NO_CROSS_DIRECTORY_HARDLINKS=YesPlease + SKIP_DASHED_BUILT_INS=YesPlease ) jobs="${NUMBER_OF_PROCESSORS:-2}" make -j"$jobs" "${make_args[@]}" all @@ -296,6 +319,7 @@ jobs: test -f /tmp/build/git/cmd/git.exe test -f "/tmp/build/git/$MINGW_DIR/libexec/git-core/git-lfs.exe" test -d "/tmp/build/git/$MINGW_DIR/share/git-core/templates" + test ! -e "/tmp/build/git/$MINGW_DIR/libexec/git-core/git-add.exe" if test "$HAS_GCM" = true then test -f "/tmp/build/git/$MINGW_DIR/bin/git-credential-manager.exe" @@ -306,6 +330,8 @@ jobs: if test "$HAS_GCM" = true then test -x /tmp/build/git/libexec/git-core/git-credential-manager + else + test ! -e /tmp/build/git/libexec/git-core/git-credential-manager fi fi test -f /tmp/build/git/etc/gitconfig @@ -320,6 +346,7 @@ jobs: env: TARGET_PLATFORM: ${{ matrix.target_platform }} MINGW_DIR: ${{ matrix.mingw_dir }} + HAS_GCM: ${{ matrix.has_gcm }} run: | set -euo pipefail smoke=/tmp/codex-git-smoke @@ -352,7 +379,10 @@ jobs: printf '%s\n' "$build_options" grep -F "built from commit: $GITHUB_SHA" <<<"$build_options" env "${git_env[@]}" "$git_binary" lfs version - env "${git_env[@]}" "$git_binary" credential-manager --version + if test "$HAS_GCM" = true + then + env "${git_env[@]}" "$git_binary" credential-manager --version + fi env "${git_env[@]}" "$git_binary" init --quiet "$smoke/repo" echo test >"$smoke/repo/file" @@ -371,6 +401,7 @@ jobs: TARGET_PLATFORM: ${{ matrix.target_platform }} TARGET_ARCH: ${{ matrix.arch }} VERSION: ${{ needs.version.outputs.version }} + MAX_TAR_BYTES: ${{ matrix.max_tar_bytes }} run: | set -euo pipefail script/package.sh @@ -401,6 +432,11 @@ jobs: test "$actual" = "$expected" done + tarball="output/git-$VERSION-$ASSET_PLATFORM-$TARGET_ARCH.tar.gz" + tar_bytes="$(wc -c <"$tarball")" + printf '%s bytes: %s\n' "$tar_bytes" "$tarball" + test "$tar_bytes" -le "$MAX_TAR_BYTES" + - name: Upload release assets uses: actions/upload-artifact@v7 with: From 019104f830bc68d8062d2bbfa87bfe789f785b3a Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Tue, 14 Jul 2026 10:21:40 -0700 Subject: [PATCH 11/32] ci: loosen artifact size guardrails --- .github/workflows/codex-release.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/codex-release.yml b/.github/workflows/codex-release.yml index de2ac63f188a7d..98ffccc6e23e19 100644 --- a/.github/workflows/codex-release.yml +++ b/.github/workflows/codex-release.yml @@ -69,7 +69,7 @@ jobs: binary: /tmp/build/git/bin/git file_pattern: Mach-O 64-bit executable arm64 has_gcm: false - max_tar_bytes: 33554432 + max_tar_bytes: 67108864 - name: macOS x64 os: macos-15-intel target_platform: macOS @@ -78,7 +78,7 @@ jobs: binary: /tmp/build/git/bin/git file_pattern: Mach-O 64-bit executable x86_64 has_gcm: false - max_tar_bytes: 33554432 + max_tar_bytes: 67108864 - name: Linux arm64 os: ubuntu-22.04 target_platform: ubuntu @@ -87,7 +87,7 @@ jobs: binary: /tmp/build/git/bin/git file_pattern: ELF 64-bit.*ARM aarch64 has_gcm: false - max_tar_bytes: 41943040 + max_tar_bytes: 67108864 - name: Linux x64 os: ubuntu-22.04 target_platform: ubuntu @@ -96,7 +96,7 @@ jobs: binary: /tmp/build/git/bin/git file_pattern: ELF 64-bit.*x86-64 has_gcm: false - max_tar_bytes: 41943040 + max_tar_bytes: 67108864 - name: Windows arm64 os: windows-11-arm target_platform: win32 @@ -105,7 +105,7 @@ jobs: binary: /tmp/build/git/clangarm64/bin/git.exe file_pattern: PE32\+.*ARM64 has_gcm: true - max_tar_bytes: 83886080 + max_tar_bytes: 134217728 sdk_arch: aarch64 sdk_flavor: full mingw_dir: clangarm64 @@ -121,7 +121,7 @@ jobs: binary: /tmp/build/git/mingw64/bin/git.exe file_pattern: PE32\+.*x86-64 has_gcm: true - max_tar_bytes: 83886080 + max_tar_bytes: 134217728 sdk_arch: x86_64 sdk_flavor: full mingw_dir: mingw64 From a230211dd4d78a2e9cb83868879338a45026d794 Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Tue, 4 Aug 2026 13:05:32 -0700 Subject: [PATCH 12/32] ci: release only controller-published Codex commits Topic pull requests merge into codex before the controller rebuilds its recorded output. Treating every codex push as a release would run the full build matrix and publish artifacts for an intermediate tree. Compare each pushed commit with the output recorded in meta:codex.config before starting version detection. Skip the build and release jobs unless the controller published that exact commit; a later pending merge must not suppress its release. --- .github/workflows/codex-release.yml | 34 +++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/.github/workflows/codex-release.yml b/.github/workflows/codex-release.yml index 98ffccc6e23e19..fe3cdbc8a3abc6 100644 --- a/.github/workflows/codex-release.yml +++ b/.github/workflows/codex-release.yml @@ -13,8 +13,42 @@ concurrency: cancel-in-progress: false jobs: + publication: + name: Verify controller publication + runs-on: ubuntu-24.04 + outputs: + published: ${{ steps.verify.outputs.published }} + steps: + - name: Check the published controller output + id: verify + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + + meta=$(gh api \ + "repos/$GITHUB_REPOSITORY/git/ref/heads/meta" \ + --jq '.object.sha') + recorded=$(gh api \ + "repos/$GITHUB_REPOSITORY/contents/codex.config?ref=$meta" \ + -H 'Accept: application/vnd.github.raw+json' | + git config --no-includes --file /dev/stdin \ + --get codex.output-tip) + + if test "$GITHUB_SHA" = "$recorded" + then + printf 'published=true\n' >>"$GITHUB_OUTPUT" + printf 'Releasing controller-published commit %s.\n' "$GITHUB_SHA" + else + printf 'published=false\n' >>"$GITHUB_OUTPUT" + printf 'Skipping non-controller publication %s.\n' "$GITHUB_SHA" + fi + version: name: Determine version + needs: publication + if: needs.publication.outputs.published == 'true' runs-on: ubuntu-24.04 outputs: describe: ${{ steps.version.outputs.describe }} From 7bc6d5b86c3d8cbe385cd2825b5c1fdf65c896da Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Wed, 5 Aug 2026 11:18:01 -0700 Subject: [PATCH 13/32] ci: release controller-published Codex unstable commits The release workflow currently listens only to codex and checks every push against codex.output-tip. The controller records and atomically publishes codex-unstable.output-tip separately, so preview output pushes need an independent gate. Listen to both generated branches, skip deletion events, and select the recorded output from the exact pushed ref. Unknown refs and missing state fail closed. The existing version, build, and release chain still runs only when the selected output matches GITHUB_SHA. --- .github/workflows/codex-release.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/codex-release.yml b/.github/workflows/codex-release.yml index fe3cdbc8a3abc6..623c6e09aeb281 100644 --- a/.github/workflows/codex-release.yml +++ b/.github/workflows/codex-release.yml @@ -4,6 +4,7 @@ on: push: branches: - codex + - codex-unstable permissions: contents: read @@ -16,6 +17,7 @@ jobs: publication: name: Verify controller publication runs-on: ubuntu-24.04 + if: github.event.deleted == false outputs: published: ${{ steps.verify.outputs.published }} steps: @@ -26,6 +28,18 @@ jobs: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail + case "$GITHUB_REF" in + refs/heads/codex) + output_key=codex.output-tip + ;; + refs/heads/codex-unstable) + output_key=codex-unstable.output-tip + ;; + *) + printf 'unexpected release ref: %s\n' "$GITHUB_REF" >&2 + exit 1 + ;; + esac meta=$(gh api \ "repos/$GITHUB_REPOSITORY/git/ref/heads/meta" \ @@ -34,7 +48,7 @@ jobs: "repos/$GITHUB_REPOSITORY/contents/codex.config?ref=$meta" \ -H 'Accept: application/vnd.github.raw+json' | git config --no-includes --file /dev/stdin \ - --get codex.output-tip) + --get "$output_key") if test "$GITHUB_SHA" = "$recorded" then From 2bb6c1c18aa3170efff362eb3fb8f096134955f7 Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Wed, 5 Aug 2026 13:38:57 -0700 Subject: [PATCH 14/32] ci: stamp Codex release source refs Codex and codex-unstable releases currently share the same prerelease shape. Their target SHA differs, but the GitHub release object does not record which output ref triggered it. Consumers must therefore join release data with Actions runs to distinguish the lanes. Carry github.ref into the publish step and put source_ref and source_sha at the start of the release notes. This leaves tag and asset names unchanged while giving release-API consumers a machine-readable lane marker. --- .github/workflows/codex-release.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/codex-release.yml b/.github/workflows/codex-release.yml index 623c6e09aeb281..0fe82d4455cf82 100644 --- a/.github/workflows/codex-release.yml +++ b/.github/workflows/codex-release.yml @@ -523,10 +523,18 @@ jobs: env: GH_TOKEN: ${{ github.token }} SOURCE_DESCRIPTION: ${{ needs.version.outputs.describe }} + SOURCE_REF: ${{ github.ref }} VERSION: ${{ needs.version.outputs.version }} run: | set -euo pipefail assets=(artifacts/git-*) + release_notes=$( + printf '%s\n' \ + "source_ref=$SOURCE_REF" \ + "source_sha=$GITHUB_SHA" \ + "" \ + "OpenAI Git release artifacts for $SOURCE_DESCRIPTION, built from $GITHUB_SHA for Codex." + ) if gh release view "$VERSION" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1 then @@ -538,6 +546,6 @@ jobs: --repo "$GITHUB_REPOSITORY" \ --target "$GITHUB_SHA" \ --title "$VERSION" \ - --notes "OpenAI Git release artifacts for $SOURCE_DESCRIPTION, built from $GITHUB_SHA for Codex." \ + --notes "$release_notes" \ --prerelease fi From a9f225cf59fa91e35ff58c400697692161fda278 Mon Sep 17 00:00:00 2001 From: Friel Date: Sat, 15 Aug 2026 07:15:37 +0000 Subject: [PATCH 15/32] pack-objects: trace bytes written to stdout --- builtin/pack-objects.c | 6 ++++++ t/t5300-pack-object.sh | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 1d9dc3145432ea..9770f7aa98acdf 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -1338,6 +1338,7 @@ static void write_pack_file(void) uint32_t nr_remaining = nr_result; time_t last_mtime = 0; struct object_entry **write_order; + off_t bytes_written = 0; if (progress > pack_to_stdout) progress_state = start_progress(the_repository, @@ -1391,6 +1392,8 @@ static void write_pack_file(void) } if (pack_to_stdout) { + bytes_written += hashfile_total(f) + + the_repository->hash_algo->rawsz; /* * We never fsync when writing to stdout since we may * not be writing to an actual pack file. For instance, @@ -1511,6 +1514,9 @@ static void write_pack_file(void) written, nr_result); trace2_data_intmax("pack-objects", the_repository, "write_pack_file/wrote", nr_result); + if (pack_to_stdout) + trace2_data_intmax("pack-objects", the_repository, + "written/bytes", bytes_written); } static int no_try_delta(const char *path) diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh index 9dabb3615aff56..101729f1f6a2a7 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -33,6 +33,16 @@ test_expect_success 'setup' ' } >expect ' +test_expect_success 'pack-object traces bytes written to stdout' ' + test_when_finished "rm -f pack.trace pack.pack" && + GIT_TRACE2_EVENT="$PWD/pack.trace" \ + git pack-objects --quiet --revs --stdout >pack.pack <<-EOF && + $commit + EOF + bytes=$(test_file_size pack.pack) && + test_grep "\"key\":\"written/bytes\",\"value\":\"$bytes\"" pack.trace +' + test_expect_success 'setup pack-object Date: Mon, 17 Aug 2026 03:54:18 +0000 Subject: [PATCH 16/32] pack-objects: trace bytes for all pack output --- builtin/pack-objects.c | 10 +++++----- t/t5300-pack-object.sh | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 9770f7aa98acdf..0e7cebae34d8c8 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -1391,9 +1391,10 @@ static void write_pack_file(void) display_progress(progress_state, written); } + /* Every finalization path appends the pack checksum. */ + bytes_written += hashfile_total(f) + + the_repository->hash_algo->rawsz; if (pack_to_stdout) { - bytes_written += hashfile_total(f) + - the_repository->hash_algo->rawsz; /* * We never fsync when writing to stdout since we may * not be writing to an actual pack file. For instance, @@ -1514,9 +1515,8 @@ static void write_pack_file(void) written, nr_result); trace2_data_intmax("pack-objects", the_repository, "write_pack_file/wrote", nr_result); - if (pack_to_stdout) - trace2_data_intmax("pack-objects", the_repository, - "written/bytes", bytes_written); + trace2_data_intmax("pack-objects", the_repository, + "write_pack_file/wrote_bytes", bytes_written); } static int no_try_delta(const char *path) diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh index 101729f1f6a2a7..aac139e6a096eb 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -40,7 +40,21 @@ test_expect_success 'pack-object traces bytes written to stdout' ' $commit EOF bytes=$(test_file_size pack.pack) && - test_grep "\"key\":\"written/bytes\",\"value\":\"$bytes\"" pack.trace + test_grep "\"key\":\"write_pack_file/wrote_bytes\",\"value\":\"$bytes\"" pack.trace +' + +test_expect_success 'pack-object traces bytes written to split pack files' ' + test_when_finished "rm -f split.trace traced-pack-*" && + GIT_TRACE2_EVENT="$PWD/split.trace" \ + git -c pack.packSizeLimit=3m pack-objects --quiet traced-pack Date: Mon, 17 Aug 2026 03:57:02 +0000 Subject: [PATCH 17/32] pack-objects: accumulate bytes after finalization --- builtin/pack-objects.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 0e7cebae34d8c8..4b320fe6598dd5 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -1349,6 +1349,7 @@ static void write_pack_file(void) do { unsigned char hash[GIT_MAX_RAWSZ]; char *pack_tmp_name = NULL; + off_t pack_bytes; if (pack_to_stdout) { /* @@ -1391,8 +1392,7 @@ static void write_pack_file(void) display_progress(progress_state, written); } - /* Every finalization path appends the pack checksum. */ - bytes_written += hashfile_total(f) + + pack_bytes = hashfile_total(f) + the_repository->hash_algo->rawsz; if (pack_to_stdout) { /* @@ -1424,6 +1424,7 @@ static void write_pack_file(void) write_bitmap_index = 0; } } + bytes_written += pack_bytes; if (!pack_to_stdout) { struct stat st; From d7f8380b62126ebbe585323dc80c0661f033adf6 Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Sun, 12 Jul 2026 17:27:52 -0700 Subject: [PATCH 18/32] t/helper: teach pack-deltas to list delta entries In the following commit(s), some tests will need to distinguish between `REF_DELTA`s and `OFS_DELTA`s to exercise a new '--no-ref-delta' option for 'pack-objects'. Existing tools report delta relationships, but not how their bases are represented in the pack. Teach 'test-tool pack-deltas' a '--list-deltas' mode. For each delta entry, print the object ID, its REF_DELTA or OFS_DELTA type, and the base object ID or pack offset, respectively. This lets tests inspect pack headers without open-coding a parser. Signed-off-by: Taylor Blau --- t/helper/test-pack-deltas.c | 69 +++++++++++++++++++++++++++++++++++++ t/t5300-pack-object.sh | 8 +++-- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/t/helper/test-pack-deltas.c b/t/helper/test-pack-deltas.c index 959705fecaf144..db8eb5951f557e 100644 --- a/t/helper/test-pack-deltas.c +++ b/t/helper/test-pack-deltas.c @@ -7,6 +7,7 @@ #include "hash.h" #include "hex.h" #include "pack.h" +#include "packfile.h" #include "pack-objects.h" #include "parse-options.h" #include "setup.h" @@ -15,6 +16,7 @@ static const char *usage_str[] = { "test-tool pack-deltas --num-objects ", + "test-tool pack-deltas --list-deltas .idx", NULL }; @@ -81,19 +83,86 @@ static void write_ref_delta(struct hashfile *f, free(delta_buf); } +static int list_delta(const struct object_id *oid, + struct packed_git *p, + uint32_t pos, + void *_w_curs) +{ + struct pack_window **w_curs = _w_curs; + off_t obj_offset = nth_packed_object_offset(p, pos); + off_t cur = obj_offset; + size_t size; + enum object_type type = unpack_object_header(p, w_curs, &cur, + &size); + + if (type < 0) + die("unable to parse object at position %"PRIu32, pos); + if (type != OBJ_REF_DELTA && type != OBJ_OFS_DELTA) + return 0; + + if (type == OBJ_REF_DELTA) { + struct object_id base_oid; + const unsigned char *base = use_pack(p, w_curs, cur, + NULL); + + oidread(&base_oid, base, p->repo->hash_algo); + printf("%s REF_DELTA %s\n", oid_to_hex(oid), + oid_to_hex(&base_oid)); + } else { + off_t base_offset = get_delta_base(p, w_curs, &cur, + type, obj_offset); + + if (!base_offset) + die("unable to read base of object %s", oid_to_hex(oid)); + printf("%s OFS_DELTA %"PRIuMAX"\n", oid_to_hex(oid), + (uintmax_t)base_offset); + } + + return 0; +} + +static void list_deltas(const char *idx_name) +{ + struct packed_git *p; + struct pack_window *w_curs = NULL; + + p = add_packed_git(the_repository, idx_name, strlen(idx_name), 1); + if (!p || open_pack_index(p)) + die("unable to open pack index %s", idx_name); + + if (for_each_object_in_pack(p, list_delta, &w_curs, + ODB_FOR_EACH_OBJECT_PACK_ORDER)) + die("unable to iterate over objects in %s", idx_name); + + unuse_pack(&w_curs); + close_pack(p); + free(p); +} + int cmd__pack_deltas(int argc, const char **argv) { int num_objects = -1; + int list_deltas_mode = 0; struct hashfile *f; struct strbuf line = STRBUF_INIT; struct option options[] = { OPT_INTEGER('n', "num-objects", &num_objects, N_("the number of objects to write")), + OPT_BOOL(0, "list-deltas", &list_deltas_mode, + N_("list REF_DELTA and OFS_DELTA entries")), OPT_END() }; argc = parse_options(argc, argv, NULL, options, usage_str, 0); + if (list_deltas_mode) { + if (argc != 1 || num_objects >= 0) + usage_with_options(usage_str, options); + setup_git_directory(the_repository); + list_deltas(argv[0]); + return 0; + } + if (argc || num_objects < 0) usage_with_options(usage_str, options); diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh index 9dabb3615aff56..555e77747a1c8d 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -190,7 +190,9 @@ test_expect_success 'unpack without delta (core.fsyncmethod=batch)' ' test_expect_success 'pack with REF_DELTA' ' packname_2=$(git pack-objects --progress test-2 stderr) && - check_deltas stderr -gt 0 + check_deltas stderr -gt 0 && + test-tool pack-deltas --list-deltas test-2-$packname_2.idx >deltas && + test_grep " REF_DELTA " deltas ' test_expect_success 'unpack with REF_DELTA' ' @@ -204,7 +206,9 @@ test_expect_success 'unpack with REF_DELTA (core.fsyncmethod=batch)' ' test_expect_success 'pack with OFS_DELTA' ' packname_3=$(git pack-objects --progress --delta-base-offset test-3 \ stderr) && - check_deltas stderr -gt 0 + check_deltas stderr -gt 0 && + test-tool pack-deltas --list-deltas test-3-$packname_3.idx >deltas && + test_grep " OFS_DELTA " deltas ' test_expect_success 'unpack with OFS_DELTA' ' From 78ee4101eaf287e252b84aa76017ca2c2fe26765 Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Sun, 12 Jul 2026 17:27:52 -0700 Subject: [PATCH 19/32] pack-objects: introduce `--no-ref-delta` Some consumers of 'pack-objects' may wish to avoid packs which contain `REF_DELTA` entries. For instance, a 'receive-pack' implementation which retains the resulting pack without building an index of object IDs may prefer every delta base to be discoverable from an earlier entry in the same pack. Teach 'pack-objects' a new `--no-ref-delta` option to avoid writing `REF_DELTA` entries, without changing whether `OFS_DELTA` is allowed. When used without `--delta-base-offset`, no delta representation remains, so avoid delta search entirely. Otherwise, allow new deltas whose bases appear earlier in the same pack. For now, disable delta- and bitmap-reuse under `--no-ref-delta`, since either may copy an existing `REF_DELTA` entry. This is overly pessimistic, but simplifies the changes in this commit. The next commit re-enables reuse in the cases which do not require `REF_DELTA`. Signed-off-by: Taylor Blau --- Documentation/git-pack-objects.adoc | 8 ++++- builtin/pack-objects.c | 16 ++++++--- t/t5300-pack-object.sh | 52 +++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/Documentation/git-pack-objects.adoc b/Documentation/git-pack-objects.adoc index 65cd00c152f495..5e42e4429d0b8e 100644 --- a/Documentation/git-pack-objects.adoc +++ b/Documentation/git-pack-objects.adoc @@ -10,7 +10,8 @@ SYNOPSIS -------- [verse] 'git pack-objects' [-q | --progress | --all-progress] [--all-progress-implied] - [--no-reuse-delta] [--delta-base-offset] [--non-empty] + [--no-reuse-delta] [--delta-base-offset] [--no-ref-delta] + [--non-empty] [--local] [--incremental] [--window=] [--depth=] [--revs [--unpacked | --all]] [--keep-pack=] [--cruft] [--cruft-expiration=