ci: smoke the pinned Codex CLI surface, and bump it off the alpha line - #923
ci: smoke the pinned Codex CLI surface, and bump it off the alpha line#923tend-agent wants to merge 7 commits into
Conversation
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
tend-agent
left a comment
There was a problem hiding this comment.
The job works — I installed the pinned 0.131.0-alpha.22 and ran the script body end-to-end with no OpenAI credential: all four codex exec flags match, codex plugin marketplace add + codex plugin add succeed, the Installed plugin root: line parses, and the skill file is there. Reviewing as the author, so flagging rather than approving.
One thing the rule change probably wants to decide the other way. It codifies dist-tags.alpha as the bump target on the premise that codex_version "has no release stream to track either" — but that premise has expired. npm view @openai/codex dist-tags.latest is now 0.147.0, sixteen minors past the 0.131.0-alpha.17 that first shipped codex plugin add. I installed the stable 0.147.0 and ran this PR's entire smoke script against it: all four flags present, both plugin subcommands succeed, Installed plugin root: parses, and skills/ and scripts/ both materialize under the returned root. So there is a release stream to track, and it's the more conservative one — the new job would confirm it exactly the same way it confirms an alpha.
The knock-on is that merging as-is leaves two in-repo instructions pointing opposite ways. codex/action.yaml's codex_version description still reads "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", and the Install Codex CLI step comment ends "Bump the default once a stable npm release ships PR #21396" — a condition that is now met. The next weekly run reads that alongside the new skill paragraph and gets contradicting guidance; whichever it follows, the pin stays on a prerelease line for a reason that no longer holds. The mechanical half is the inline suggestion below; the table row's "keep it on its prerelease line" and those two comments in codex/action.yaml would need to move with it. That's a policy call rather than a fix I'd push unilaterally, so leaving it for a maintainer.
Smaller: the smoke re-implements the action's awk -F': ' '/^Installed plugin root: /' parse rather than sharing it, so a change to the action's parsing can leave this job greening a pattern production no longer uses — the failure mode the adjacent test-proxy job avoids by running shared/steps/install-proxy-uv.sh "rather than repeating it". #910 is already extracting shared/steps/install-codex-cli.sh, which would be a natural home for the install-and-parse pair.
Verification notes
@openai/codex dist-tags at review time: latest: 0.147.0, alpha: 0.148.0-alpha.5.
Full smoke script run against stable 0.147.0:
codex-cli 0.147.0
OK --model
OK --sandbox
OK --output-last-message
OK --config
Added marketplace `tend` from /home/runner/work/tend/tend.
Added plugin `tend-ci-runner` from marketplace `tend`.
Installed plugin root: <CODEX_HOME>/plugins/cache/tend/tend-ci-runner/0.1.0
ROOT OK
SKILL OK
SCRIPTS OK
codex plugin --help on 0.147.0 lists add, list, marketplace, remove — the subcommand the pin's prerelease line was chosen for is on stable.
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.
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.
|
On the shared-parse point: agreed in principle, and I've left it out of this PR deliberately. #910 is open and already creating Worth noting the duplication is narrower than it looks in the meantime: the job asserts the prefix contract ( |
tend-agent
left a comment
There was a problem hiding this comment.
Ran the job's script body end-to-end against the newly pinned 0.147.0 with no OpenAI credential: all four flags present in codex exec --help, codex plugin marketplace add and codex plugin add both succeed, Installed plugin root: parses, and skills/ and scripts/ both land under the returned root with list-recent-runs.sh carrying its executable bit. The flag list matches the action's ARGS array exactly — --model, --sandbox, --output-last-message, and the conditional --config — with nothing else reaching codex exec. Reviewing as the author, so commenting rather than approving.
One file the retarget missed. ddb735c corrected the skill's table row and both codex/action.yaml comments, but the test-codex-surface job's own header comment still opens on the premise those corrections retired — inline below. Second inline is the skill paragraph leading with history instead of the rule.
Verification notes
codex-cli 0.147.0
OK --model
OK --sandbox
OK --output-last-message
OK --config
Added marketplace `tend` from /home/runner/work/tend/tend.
Added plugin `tend-ci-runner` from marketplace `tend`.
Installed plugin root: <CODEX_HOME>/plugins/cache/tend/tend-ci-runner/0.1.0
ROOT OK / SKILL OK / SCRIPTS OK
npm view @openai/codex@0.147.0 engines is { node: '>=16' }, so the job's node-version: "20" is not a constraint on the pin; dist-tags.latest is 0.147.0 and alpha is 0.148.0-alpha.5, so the bump target is what the retargeted rule asks for.
codex plugin marketplace add prints its own Installed marketplace root: line, but it goes to a separate command's stdout and the awk pattern is ^-anchored on Installed plugin root: , so there's no collision with the parse.
tend-agent
left a comment
There was a problem hiding this comment.
Ran the job's script body end-to-end against the pinned 0.147.0 with no OpenAI credential — exit 0, including the new probe block, and test-codex-surface is green on this HEAD in CI. Both probes behave as the comment claims: the negative control returns the unknown configuration field error in ~40 ms, the positive one falls through to Not inside a trusted directory, so no network call and no measurable cost. shellcheck is clean on the extracted step body. Reviewing as the author, so commenting rather than approving.
One thing the narrowing dropped. The "does not cover" list went from four items to two, but only one of the removed pair (model_reasoning_effort) became covered — model selection is still unverified and is now unmentioned in both places. The job asserts that --model <VALUE> exists in codex exec --help; it says nothing about whether the string the action actually passes is still a model. I checked whether that gap could be closed the same way model_reasoning_effort was, and it can't: unlike -c keys, --model gets no local validation at all, so there is no offline probe to add — it can only stay on the list.
That leaves it the least-guarded item of the four, not the most. codex/action.yaml defaults model: gpt-5.5 and that default ships to every adopter, and the repo's own reason for not enumerating codex models (KNOWN_MODELS_BY_HARNESS in generator/src/tend/config.py, "Codex's catalog churns (gpt-5.1-codex was current at harness bring-up; gone by the next month)") is exactly the argument for keeping a weekly reader pointed at it. A weekly run reading either of these two texts concludes the only session-shaped gaps are sandbox behavior and --output-last-message. Inline suggestions restore it in both.
Verification notes
Probes run against 0.147.0, from a mktemp -d (non-repo) cwd, exactly as the job runs them:
$ codex exec --strict-config -c 'model_reasoning_effortZZZ="low"' x </dev/null
Error loading config.toml: unknown configuration field `model_reasoning_effortZZZ` in -c/--config override
real 0m0.037s
$ codex exec --strict-config -c 'model_reasoning_effort="low"' x </dev/null
Not inside a trusted directory and --skip-git-repo-check was not specified.
real 0m0.112s
--strict-config validates field names only — model_reasoning_effort="bogusvalue" is accepted as readily as "low", "medium", "high", "xhigh". So the assertion is scoped correctly to the key, and the error string it emits matches what the check greps for.
--model is not validated locally at any point reachable without a key:
$ codex exec --strict-config --skip-git-repo-check --sandbox danger-full-access --model totally-bogus-model-zzz x
ERROR: unexpected status 401 Unauthorized ... url: https://api.openai.com/v1/responses
Identical output for --model gpt-5.5. The 401 is the first thing that rejects either string, so a dropped model is indistinguishable from a valid one until a real session runs.
ARGS in the action's Run Codex step is --model, --sandbox, --output-last-message, and the conditional --config model_reasoning_effort=... — one -c key, so the single positive probe covers the whole config surface the action passes.
Full smoke script at 0.147.0: codex plugin marketplace add and codex plugin add both succeed, Installed plugin root: parses to a real directory, skills/triage/SKILL.md and an executable scripts/list-recent-runs.sh both land under it.
Problem
codex_versionis pinned at0.131.0-alpha.22incodex/action.yamlwhilelatestis0.147.0. The weekly rule governing it asked for a confirmation nothing in this repo could produce — "bump only to a release confirmed to run undercodex exec" — because there is noOPENAI_API_KEYin thetendenvironment (gh secret list --env tendreturns onlyCLAUDE_CODE_OAUTH_TOKENandTEND_BOT_TOKEN), noharness: codexoverride in.config/tend.yaml, and no codex step inci.yaml. The bump condition was unsatisfiable, so the pin drifted by default rather than by decision.Both proposals in #922 need that same missing secret, so neither is actually the cheaper one. What doesn't need it is the part of the confirmation that is mechanically checkable — and it turns out to be the part where a silent break would land.
Solution
A
test-codex-surfacejob inci.yaml, shaped after the existingtest-proxysmoke: read the pin out of the action withyq -e, install it, and assert the surface the action depends on.Run Codexstep passes —--model,--sandbox,--output-last-message,--config. Matching on--flag <pins each to an option that still takes a value, so a release that keeps the name but drops the argument fails too.Installed plugin root:prefix thatcodex plugin addprints and the action parses withawkto setCLAUDE_PLUGIN_ROOT, plus that both trees skills reach for through that variable land under it —skills/andscripts/. Onlyskills/is declared inplugin.json, soscripts/materializing is a packaging side effect a release could stop honouring while every skill shelling out to${CLAUDE_PLUGIN_ROOT}/scripts/breaks.No credential is involved:
codex --version,codex exec --help,codex plugin marketplace add, andcodex plugin addare all local. So the job runs on every PR, which also guards the other direction — a change to.agents/plugins/marketplace.jsonor the plugin layout that Codex can no longer install now fails here rather than in an adopter's job, which nothing catches today.The bump target moves to
latest, and the pin moves with it. The premise behind the prerelease pin has expired:codex plugin addwasalpha-only when the pin was set, but stable0.147.0carries it. So therunning-tendweekly rule now readstrack latest(dropping toalphaonly for a fix not yet released), the twocodex/action.yamlcomments that still saidlatestwas0.130.0and lacked the subcommand are corrected, andcodex_versionis bumped0.131.0-alpha.22→0.147.0. The bump ships here rather than separately because this job is its confirmation — the gate and the first bump it gates are the same review, and the job would otherwise land green against a pin nobody had re-examined.The rule also states plainly what stays unverified without a key — sandbox behavior under a real session, and whether the agent's final message actually reaches
--output-last-message— with an instruction to skim the codex CHANGELOG for those paths, mirroring theclaude_versionrule.Testing
The job's script body ran end-to-end in this session with no OpenAI credential present, against the old pin, the new one, and the current alpha. Beyond that,
--strict-configturns out to validate-coverrides, which reaches further than--helpmatching does: it confirmsmodel_reasoning_effortis still a recognized field at0.147.0rather than a silently-ignored one.Surface probe
plugin addparseskills/+scripts/0.131.0-alpha.22(old pin)0.147.0(new pin,latest)0.148.0-alpha.5(alpha)codex exec --helpat0.147.0, identical to the old pin for every flag the action passes:--sandboxstill acceptsdanger-full-access, which is the action's default:[possible values: read-only, workspace-write, danger-full-access].Config-key probe, and how far a keyless run gets
--strict-configrejects unknown-ckeys, so it can distinguish "field still exists" from "field silently ignored":Running the action's exact argument set with a deliberately invalid key gets all the way to the API call, so every flag and the
gpt-5.5model string were accepted and the only thing that stopped it was auth:Not attempted, and not reachable here: a successful
codex execsession — so the final message actually landing in--output-last-message, and sandbox behavior under a live model, remain unverified.CHANGELOG skim,
rust-v0.132.0→rust-v0.147.0One removal in range touches
codex exec's flag surface: "Remove the deprecatedcodex exec --full-autoflag; use--sandbox workspace-writeinstead" (#36054, inrust-v0.147.0). The action never passed--full-auto— it passes--sandboxexplicitly — so it is unaffected.Nothing else in range removes or renames a flag the action passes, and plugin work across the range is additive (search, remote catalogs, manifests) rather than changing the local
marketplace add/plugin addpath this depends on.Refs #922. The agent-session half stays open: it needs a maintainer to put an
OPENAI_API_KEYin thetendenvironment, which is the single decision both of the issue's options reduce to. (The body previously saidCloses #922while also saying it didn't cover that half — corrected here so merging doesn't auto-close it.)