Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion .claude/skills/running-tend/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,14 +156,17 @@ 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
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
Expand All @@ -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
Expand Down
80 changes: 80 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 <VALUE> 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: <path>'"
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"
Comment thread
tend-agent marked this conversation as resolved.
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 </dev/null 2>&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
Expand Down
23 changes: 12 additions & 11 deletions codex/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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: |
Expand Down
Loading