diff --git a/.claude/skills/running-tend/SKILL.md b/.claude/skills/running-tend/SKILL.md index 11e7df23..d849d0f1 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` | track latest; the surface job confirms the bump | ```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.latest ``` A stale `claude` binary resolves `--model opus`/`sonnet` to a superseded alias @@ -180,6 +183,13 @@ 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`). +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 model availability, +sandbox behavior, and `--output-last-message`, and note what you find in the PR. + ### `uses:` refs ```bash diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 87ebb6cf..21d50c91 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -87,6 +87,86 @@ jobs: kill "$pid" 2>/dev/null || true test -f "$confdir/mitmproxy-ca-cert.pem" + # 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 + # 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, 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: + - 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 + # 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" + + # `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 diff --git a/codex/action.yaml b/codex/action.yaml index 33fd53e9..c7f1d733 100644 --- a/codex/action.yaml +++ b/codex/action.yaml @@ -53,13 +53,13 @@ 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.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 + first set; stable carries it now, so this 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 +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`, 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`) 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: |