From 2dc663d7f0fecf2986c2eb5f6a6000def0c878cd Mon Sep 17 00:00:00 2001 From: tend-agent <270458913+tend-agent@users.noreply.github.com> Date: Sun, 9 Aug 2026 09:48:28 +0000 Subject: [PATCH 1/7] ci: smoke the pinned Codex CLI surface so the pin can be bumped MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The weekly rule holds codex_version to a bump 'confirmed to run under codex exec', a confirmation nothing in this repo could produce — so the pin drifted seventeen minors by default rather than by decision. Add a credential-free CI job that installs the pinned version and asserts the two things a silent break would hit: the flags the action passes to codex exec, and the 'Installed plugin root: ' line it parses to set CLAUDE_PLUGIN_ROOT. A bump PR now verifies itself for the half that needs no key, and a plugin-manifest change codex can no longer install fails here rather than in an adopter's job. Closes #922 --- .claude/skills/running-tend/SKILL.md | 15 +++++++- .github/workflows/ci.yaml | 57 ++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/.claude/skills/running-tend/SKILL.md b/.claude/skills/running-tend/SKILL.md index 11e7df23..0d4987bc 100644 --- a/.claude/skills/running-tend/SKILL.md +++ b/.claude/skills/running-tend/SKILL.md @@ -156,7 +156,7 @@ both. | `claude_version` | `claude/action.yaml` | track latest | | `mitmproxy_version` | `claude/action.yaml` | track latest | | `uv_version` | `claude/action.yaml` | move it with `mitmproxy_version` | -| `codex_version` | `codex/action.yaml` | keep it on its prerelease line; bump only to a release confirmed to run under `codex exec` | +| `codex_version` | `codex/action.yaml` | keep it on its prerelease line; bump it and let CI's surface job confirm it | ```bash yq '.inputs.claude_version.default' claude/action.yaml @@ -164,6 +164,9 @@ npm view @anthropic-ai/claude-code dist-tags.latest yq '.inputs.mitmproxy_version.default' claude/action.yaml curl -fsS https://pypi.org/pypi/mitmproxy/json | jq -r .info.version + +yq '.inputs.codex_version.default' codex/action.yaml +npm view @openai/codex dist-tags.alpha ``` A stale `claude` binary resolves `--model opus`/`sonnet` to a superseded alias @@ -180,6 +183,16 @@ only launches that mitmproxy and CI smokes the two together, so it needs no release stream of its own; move both in one PR, at whatever uv is latest then (`curl -fsS https://pypi.org/pypi/uv/json | jq -r .info.version`). +`codex_version` has no release stream to track either — the bump moves it to +whatever the `alpha` dist-tag holds. CI's `test-codex-surface` job installs the +pinned version and asserts the flags `codex exec` is passed plus the +`Installed plugin root: ` line the action parses, so a bump that breaks the CLI +surface fails on its own PR. That is the whole of the confirmation reachable +here: no `OPENAI_API_KEY` reaches this repo's runs, so an agent session — model +selection, `model_reasoning_effort`, whether the final message lands in +`--output-last-message` — stays unverified. Skim the codex CHANGELOG across the +bump for those paths and note it in the PR. + ### `uses:` refs ```bash diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 87ebb6cf..3d9396ab 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -87,6 +87,63 @@ jobs: kill "$pid" 2>/dev/null || true test -f "$confdir/mitmproxy-ca-cert.pem" + # `codex_version` is the one action pin with no upstream release stream to + # follow, and the weekly rule that governs it asked for a confirmation + # nothing here could produce ("bump only to a release confirmed to run under + # `codex exec`"): no OPENAI_API_KEY reaches this repo's runs and no workflow + # sets `harness: codex`, so the pin drifted by default rather than by + # decision. This is the half of that confirmation which needs no credential, + # so it can gate every bump PR. + # + # It covers where a silent break actually lands: `codex exec` dropping or + # renaming a flag the action passes, and `codex plugin add` no longer + # printing the `Installed plugin root: ` prefix the action parses with awk to + # set CLAUDE_PLUGIN_ROOT. Either one fails every adopter's Codex run at the + # first step. It guards the other direction too — a change to + # .agents/plugins/marketplace.json or the plugin manifest that Codex can no + # longer install fails here rather than in an adopter's job. + # + # It does not cover an agent session: model selection, + # `model_reasoning_effort`, sandbox behavior, and whether the final message + # reaches --output-last-message all need a real key. + test-codex-surface: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v4 + with: + node-version: "20" + # `yq -e` exits non-zero if the input is ever renamed, rather than + # yielding "null" and failing later as an unresolvable npm version. + - name: Smoke the pinned Codex CLI surface + run: | + v=$(yq -e '.inputs.codex_version.default' codex/action.yaml) + npm install -g "@openai/codex@$v" + codex --version + + # Every flag the action's `Run Codex` step passes. Matching on + # `--flag <` pins the option to one that still takes a value, so a + # release that keeps the name but drops the argument fails here. + help=$(codex exec --help) + for flag in --model --sandbox --output-last-message --config; do + grep -qF -- "$flag <" <<<"$help" || { + echo "::error::codex exec no longer accepts $flag in $v" + exit 1 + } + done + + # The action registers the marketplace from the repo root and reads + # the install path back out of the install output. + codex plugin marketplace add "$PWD" + out=$(codex plugin add tend-ci-runner@tend) + echo "$out" + root=$(awk -F': ' '/^Installed plugin root: /{print $2}' <<<"$out") + if [ -z "$root" ] || [ ! -d "$root" ]; then + echo "::error::codex $v no longer prints a parseable 'Installed plugin root: '" + exit 1 + fi + test -f "$root/skills/triage/SKILL.md" + # The OAuth wrapper ships inside the install-tend plugin rather than under # generator/, so the suites above never reach it. Its extractors decide what # becomes an adopter's model credential, and a wrong answer stores a secret From 4e4216c2cc965540f30b9d6d2e034710aa8d7920 Mon Sep 17 00:00:00 2001 From: tend-agent <270458913+tend-agent@users.noreply.github.com> Date: Sun, 9 Aug 2026 09:58:16 +0000 Subject: [PATCH 2/7] ci: retarget the codex bump at latest, which now ships plugin add Review found the premise had expired: stable 0.147.0 carries `codex plugin add` and passes this PR's whole smoke script, so the alpha line is a leftover rather than a requirement. Point the weekly rule at `latest` and fix the two action.yaml comments that still said `latest` was 0.130.0 and lacked the subcommand. --- .claude/skills/running-tend/SKILL.md | 11 ++++++----- codex/action.yaml | 21 +++++++++++---------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/.claude/skills/running-tend/SKILL.md b/.claude/skills/running-tend/SKILL.md index 0d4987bc..6bef78ba 100644 --- a/.claude/skills/running-tend/SKILL.md +++ b/.claude/skills/running-tend/SKILL.md @@ -156,7 +156,7 @@ both. | `claude_version` | `claude/action.yaml` | track latest | | `mitmproxy_version` | `claude/action.yaml` | track latest | | `uv_version` | `claude/action.yaml` | move it with `mitmproxy_version` | -| `codex_version` | `codex/action.yaml` | keep it on its prerelease line; bump it and let CI's surface job confirm it | +| `codex_version` | `codex/action.yaml` | track latest; the surface job confirms the bump | ```bash yq '.inputs.claude_version.default' claude/action.yaml @@ -166,7 +166,7 @@ yq '.inputs.mitmproxy_version.default' claude/action.yaml curl -fsS https://pypi.org/pypi/mitmproxy/json | jq -r .info.version yq '.inputs.codex_version.default' codex/action.yaml -npm view @openai/codex dist-tags.alpha +npm view @openai/codex dist-tags.latest ``` A stale `claude` binary resolves `--model opus`/`sonnet` to a superseded alias @@ -183,9 +183,10 @@ only launches that mitmproxy and CI smokes the two together, so it needs no release stream of its own; move both in one PR, at whatever uv is latest then (`curl -fsS https://pypi.org/pypi/uv/json | jq -r .info.version`). -`codex_version` has no release stream to track either — the bump moves it to -whatever the `alpha` dist-tag holds. CI's `test-codex-surface` job installs the -pinned version and asserts the flags `codex exec` is passed plus the +`codex_version` was held to the `alpha` dist-tag because only prereleases +carried `codex plugin add`; stable ships it now, so bump to `latest` and drop to +`alpha` only for a fix not yet released. CI's `test-codex-surface` job installs +the pinned version and asserts the flags `codex exec` is passed plus the `Installed plugin root: ` line the action parses, so a bump that breaks the CLI surface fails on its own PR. That is the whole of the confirmation reachable here: no `OPENAI_API_KEY` reaches this repo's runs, so an agent session — model diff --git a/codex/action.yaml b/codex/action.yaml index 33fd53e9..9c7c5912 100644 --- a/codex/action.yaml +++ b/codex/action.yaml @@ -55,11 +55,12 @@ inputs: codex_version: default: "0.131.0-alpha.22" description: >- - `@openai/codex` npm version to install (e.g. `0.131.0-alpha.22`). - Must ship the `codex plugin add` subcommand (PR #21396, first - released in `rust-v0.131.0-alpha.17`). On npm this currently lives - on the `alpha` dist-tag — `latest` is still 0.130.0 and lacks it, - so the default pins an explicit alpha rather than tracking `latest`. + `@openai/codex` npm version to install (e.g. `0.147.0`). Must ship + the `codex plugin add` subcommand (PR #21396, first released in + `rust-v0.131.0-alpha.17`). That was `alpha`-only when the pin was + set, which is why the default is still a prerelease; `latest` has + since caught up, so a bump tracks `latest`. CI's + `test-codex-surface` job confirms whichever version is pinned. allowed_bots: default: "*" description: Bots allowed to trigger workflows (engagement gate, not used yet) @@ -112,11 +113,11 @@ runs: # Install the Codex CLI from npm. `@openai/codex` ships a prebuilt # per-platform binary, so this is ~3 s with no Rust toolchain. We pin - # an explicit version (`codex_version`, default a `0.131.0-alpha.*`) - # because tend needs `codex plugin add` (PR #21396, first in - # `rust-v0.131.0-alpha.17`) and as of 2026-05-15 npm's `latest` - # (0.130.0) still predates it — only the `alpha` dist-tag carries it. - # Bump the default once a stable npm release ships PR #21396. + # an explicit version (`codex_version`) because tend needs `codex + # plugin add` (PR #21396, first in `rust-v0.131.0-alpha.17`), which + # was carried only by the `alpha` dist-tag when the pin was set. It + # is on stable now, so the bump target is `latest`; the pinned + # prerelease is a leftover, not a requirement. - name: Install Codex CLI shell: bash run: | From ddb735ce7a60e886228c733246cf39cbaefa6f07 Mon Sep 17 00:00:00 2001 From: tend-agent <270458913+tend-agent@users.noreply.github.com> Date: Sun, 9 Aug 2026 09:58:53 +0000 Subject: [PATCH 3/7] ci: assert scripts/ materializes too, not just skills/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit plugin.json declares only `skills`, but skills shell out to ${CLAUDE_PLUGIN_ROOT}/scripts/ — so a packaging change that stops materializing scripts/ would break every one of them with the job green. --- .github/workflows/ci.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3d9396ab..01b6ccaf 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -142,7 +142,13 @@ jobs: echo "::error::codex $v no longer prints a parseable 'Installed plugin root: '" exit 1 fi + # Both trees skills reach for through CLAUDE_PLUGIN_ROOT. Only + # `skills/` is declared in plugin.json, so `scripts/` materializing + # is a packaging side effect that a release could stop honouring — + # and every skill shelling out to ${CLAUDE_PLUGIN_ROOT}/scripts/ + # would break with the job still green. test -f "$root/skills/triage/SKILL.md" + test -x "$root/scripts/list-recent-runs.sh" # The OAuth wrapper ships inside the install-tend plugin rather than under # generator/, so the suites above never reach it. Its extractors decide what From 1c32790c256684c8075fd3484bc56a0f956d20b9 Mon Sep 17 00:00:00 2001 From: tend-agent <270458913+tend-agent@users.noreply.github.com> Date: Sun, 9 Aug 2026 11:52:24 +0000 Subject: [PATCH 4/7] chore: bump codex_version to 0.147.0, off the alpha line --- codex/action.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/codex/action.yaml b/codex/action.yaml index 9c7c5912..c7f1d733 100644 --- a/codex/action.yaml +++ b/codex/action.yaml @@ -53,13 +53,12 @@ inputs: default: danger-full-access description: Codex sandbox mode (workspace-write, read-only, danger-full-access) codex_version: - default: "0.131.0-alpha.22" + default: "0.147.0" description: >- `@openai/codex` npm version to install (e.g. `0.147.0`). Must ship the `codex plugin add` subcommand (PR #21396, first released in `rust-v0.131.0-alpha.17`). That was `alpha`-only when the pin was - set, which is why the default is still a prerelease; `latest` has - since caught up, so a bump tracks `latest`. CI's + first set; stable carries it now, so this tracks `latest`. CI's `test-codex-surface` job confirms whichever version is pinned. allowed_bots: default: "*" @@ -113,11 +112,12 @@ runs: # Install the Codex CLI from npm. `@openai/codex` ships a prebuilt # per-platform binary, so this is ~3 s with no Rust toolchain. We pin - # an explicit version (`codex_version`) because tend needs `codex - # plugin add` (PR #21396, first in `rust-v0.131.0-alpha.17`), which - # was carried only by the `alpha` dist-tag when the pin was set. It - # is on stable now, so the bump target is `latest`; the pinned - # prerelease is a leftover, not a requirement. + # an explicit version (`codex_version`) rather than floating on + # `latest` so an adopter's run can't change engine mid-release. The + # pin sat on an `alpha` for a while because only prereleases carried + # `codex plugin add` (PR #21396, first in `rust-v0.131.0-alpha.17`); + # stable ships it now, so the pin tracks `latest` and drops back to a + # prerelease only for a fix that hasn't been released yet. - name: Install Codex CLI shell: bash run: | From 5f5ad8a37cab89a5b6df0c48986733fbb944368a Mon Sep 17 00:00:00 2001 From: tend-agent <270458913+tend-agent@users.noreply.github.com> Date: Sun, 9 Aug 2026 12:02:49 +0000 Subject: [PATCH 5/7] docs(running-tend): lead the codex_version rule with the rule --- .claude/skills/running-tend/SKILL.md | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/.claude/skills/running-tend/SKILL.md b/.claude/skills/running-tend/SKILL.md index 6bef78ba..d30d0923 100644 --- a/.claude/skills/running-tend/SKILL.md +++ b/.claude/skills/running-tend/SKILL.md @@ -183,16 +183,12 @@ only launches that mitmproxy and CI smokes the two together, so it needs no release stream of its own; move both in one PR, at whatever uv is latest then (`curl -fsS https://pypi.org/pypi/uv/json | jq -r .info.version`). -`codex_version` was held to the `alpha` dist-tag because only prereleases -carried `codex plugin add`; stable ships it now, so bump to `latest` and drop to -`alpha` only for a fix not yet released. CI's `test-codex-surface` job installs -the pinned version and asserts the flags `codex exec` is passed plus the -`Installed plugin root: ` line the action parses, so a bump that breaks the CLI -surface fails on its own PR. That is the whole of the confirmation reachable -here: no `OPENAI_API_KEY` reaches this repo's runs, so an agent session — model -selection, `model_reasoning_effort`, whether the final message lands in -`--output-last-message` — stays unverified. Skim the codex CHANGELOG across the -bump for those paths and note it in the PR. +Bump `codex_version` to `latest`; drop to `alpha` only for a fix not yet +released. CI's `test-codex-surface` job installs whatever is pinned and asserts +the CLI surface the action depends on, so a bump that breaks it fails on its own +PR. No `OPENAI_API_KEY` reaches this repo's runs, so a live agent session stays +unverified — skim the codex CHANGELOG across the bump for sandbox behavior and +`--output-last-message`, and note what you find in the PR. ### `uses:` refs From 2c853519e33e9c67affcc18ac5543776a0397343 Mon Sep 17 00:00:00 2001 From: tend-agent <270458913+tend-agent@users.noreply.github.com> Date: Sun, 9 Aug 2026 12:02:49 +0000 Subject: [PATCH 6/7] ci: retire the no-release-stream premise, and assert model_reasoning_effort --- .github/workflows/ci.yaml | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 01b6ccaf..c0072095 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -87,8 +87,7 @@ jobs: kill "$pid" 2>/dev/null || true test -f "$confdir/mitmproxy-ca-cert.pem" - # `codex_version` is the one action pin with no upstream release stream to - # follow, and the weekly rule that governs it asked for a confirmation + # The weekly rule that governed `codex_version` asked for a confirmation # nothing here could produce ("bump only to a release confirmed to run under # `codex exec`"): no OPENAI_API_KEY reaches this repo's runs and no workflow # sets `harness: codex`, so the pin drifted by default rather than by @@ -103,9 +102,9 @@ jobs: # .agents/plugins/marketplace.json or the plugin manifest that Codex can no # longer install fails here rather than in an adopter's job. # - # It does not cover an agent session: model selection, - # `model_reasoning_effort`, sandbox behavior, and whether the final message - # reaches --output-last-message all need a real key. + # It does not cover an agent session: sandbox behavior under a live model, + # and whether the final message reaches --output-last-message, both need a + # real key. test-codex-surface: runs-on: ubuntu-24.04 steps: @@ -150,6 +149,24 @@ jobs: test -f "$root/skills/triage/SKILL.md" test -x "$root/scripts/list-recent-runs.sh" + # `model_reasoning_effort` is passed as a `-c` override, so a rename + # would be silently ignored rather than rejected — `--help` can't see + # it. `--strict-config` does validate `-c` keys, and both probes stop + # at the trusted-directory check before any network call, so this + # stays offline and fast. Run from a non-repo dir to hit that check. + probedir=$(mktemp -d) + probe() { (cd "$probedir" && codex exec --strict-config -c "$1" x &1); } + # Negative control: if this stops erroring, --strict-config no longer + # validates -c keys and the positive check below proves nothing. + if ! probe 'model_reasoning_effortZZZ="low"' | grep -q 'unknown configuration field'; then + echo "::error::codex $v no longer rejects unknown -c keys under --strict-config; the check below is void" + exit 1 + fi + if probe 'model_reasoning_effort="low"' | grep -q 'unknown configuration field'; then + echo "::error::codex $v no longer accepts the model_reasoning_effort config key" + exit 1 + fi + # The OAuth wrapper ships inside the install-tend plugin rather than under # generator/, so the suites above never reach it. Its extractors decide what # becomes an adopter's model credential, and a wrong answer stores a secret From 73db05e0110885994b4ff2318602a5fb6cb50439 Mon Sep 17 00:00:00 2001 From: tend-agent <270458913+tend-agent@users.noreply.github.com> Date: Sun, 9 Aug 2026 12:24:13 +0000 Subject: [PATCH 7/7] ci: put model selection back on what the codex smoke does not cover --- .claude/skills/running-tend/SKILL.md | 4 ++-- .github/workflows/ci.yaml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.claude/skills/running-tend/SKILL.md b/.claude/skills/running-tend/SKILL.md index d30d0923..d849d0f1 100644 --- a/.claude/skills/running-tend/SKILL.md +++ b/.claude/skills/running-tend/SKILL.md @@ -187,8 +187,8 @@ Bump `codex_version` to `latest`; drop to `alpha` only for a fix not yet released. CI's `test-codex-surface` job installs whatever is pinned and asserts the CLI surface the action depends on, so a bump that breaks it fails on its own PR. No `OPENAI_API_KEY` reaches this repo's runs, so a live agent session stays -unverified — skim the codex CHANGELOG across the bump for sandbox behavior and -`--output-last-message`, and note what you find in the PR. +unverified — skim the codex CHANGELOG across the bump for model availability, +sandbox behavior, and `--output-last-message`, and note what you find in the PR. ### `uses:` refs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c0072095..21d50c91 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -102,9 +102,9 @@ jobs: # .agents/plugins/marketplace.json or the plugin manifest that Codex can no # longer install fails here rather than in an adopter's job. # - # It does not cover an agent session: sandbox behavior under a live model, - # and whether the final message reaches --output-last-message, both need a - # real key. + # It does not cover an agent session: model selection, sandbox behavior + # under a live model, and whether the final message reaches + # --output-last-message all need a real key. test-codex-surface: runs-on: ubuntu-24.04 steps: