fix(install): give the codex CLI install the same retry window - #910
fix(install): give the codex CLI install the same retry window#910tend-agent wants to merge 3 commits into
Conversation
The codex action's `npm install -g @openai/codex` ran bare — one attempt, no timeout, no backoff — while the two CDN installers around it ride out a blip on the shared window. It is the action's only third-party reach and it sits ahead of `Run Codex`, so a registry blip cost the whole run. Extracted to shared/steps/install-codex-cli.sh, which sources lib/retry.sh the way install-proxy-uv.sh does. 60s stands as the timeout: measured on a GitHub-hosted runner with an empty npm cache, the install is 2.6-3.1s across three runs — `@openai/codex` ships a prebuilt binary, so there is no dependency tree to resolve and no toolchain to run. No third parameter on retry_install. Closes #909
tend-agent
left a comment
There was a problem hiding this comment.
The extraction is faithful and the retry contract is now identical across the three installers. Passing CODEX_VERSION through env: instead of interpolating ${{ inputs.codex_version }} into the run: body is a real improvement over the block it replaces. Tests pass locally (52 in test_shared_steps.py), shellcheck -S warning is clean on the touched scripts.
One gap the new lib/retry.sh header surfaces rather than causes: it now enumerates the pre-agent third-party reaches as three, but there is a fourth. proxy/setup-sandbox.sh warms the uvx cache with "$UVX" --from "$MITMPROXY" mitmdump --version (under the "Warm the uvx cache first" comment) before it backgrounds the proxy — that resolves mitmproxy==$MITMPROXY_VERSION from PyPI on a cold UV_CACHE_DIR, under set -euo pipefail, ahead of the agent step in the Claude action. A cache miss (first run on a new pin, or an evicted actions/cache entry) plus a PyPI blip reddens the run having done none of the work the trigger asked for — the same failure this stack exists to close. It's the wrong PR to fix it in (different harness, different issue, and CLAUDE.md's atomic-PR rule), but happy to open a follow-up that puts that resolve on the window and updates the header's enumeration.
Non-blocking on the test suite: FAKE_NPM reads the package spec off $3 positionally, so it silently mis-parses if the install command ever grows a flag. The version assertion in the happy-path test is what would catch it, which is enough — just worth knowing the coupling is there.
The fake took it from $3, which is the spec only while the install command is exactly `install -g <spec>`; a flag added anywhere ahead of it would have the fake echo `-g` as the installed version instead. Raised in review of this PR as non-blocking, and cheap enough to remove the coupling outright.
|
Both points from the review, as author. The fourth reach. Confirmed and out of scope here:
Still based on #908 and will retarget to |
…will convert (#914) ## Problem Filing an issue in this repo is not a note to a maintainer — `tend-triage` fires on `issues`. Twice in 66 minutes this morning a run filed an issue describing a fix it had already fully scoped, and triage then re-derived that fix from the issue body and opened the PR: | Issue filed | Triage boots | Turns / cost | PR opened | |---|---|---|---| | [#909](#909) 07:18:53Z | [31300864191](https://github.com/max-sixty/tend/actions/runs/31300864191) 07:18:56Z | 64 / $4.08 | [#910](#910) `fix/issue-909` 07:25:17Z | | [#911](#911) 07:39:37Z | [31301666194](https://github.com/max-sixty/tend/actions/runs/31301666194) 07:39:40Z | 58 / $4.20 | [#912](#912) `fix/issue-911` 07:49:37Z | Three seconds from issue to triage boot in both cases. Neither issue was under-specified: #909 named `codex/action.yaml:120-124` and proposed extracting to `shared/steps/install-codex-cli.sh` sourcing `lib/retry.sh`; #911 named `proxy/setup-sandbox.sh:319` and proposed the same treatment. Both PRs implemented exactly the proposal. Triage's own session log confirms the conversion — run 31300864191's artifact is `claude-session-logs-n909`, and it ran `gh pr create --head fix/issue-909`, classifying the reporter as "the bot's own review of #908". So the issue bought nothing and cost $8.28 of agent time plus two threads. The filing run could have opened both PRs itself. <details><summary>Wider context — the cascade this sat inside</summary> A single transient CDN 403 lost one matrix leg of `review-reviewers` run [31297986524](https://github.com/max-sixty/tend/actions/runs/31297986524) at 06:03Z (`curl: (22) ... 403`, three attempts inside 15.7 s, the other four legs installing the same version from the same runner at the same moment succeeded). Between 06:29Z and 07:56Z that produced #906, #907, #908, #909, #910, #911 and #912 — five stacked PRs and two issues, none merged, across ~14 agent runs and roughly $30 at list prices. The engineering is sound and correctly atomic; this PR does not argue with any of it. Only the two issue hops are pure overhead, and they are the part with a mechanical fix. </details> ## Solution A short section in `running-in-ci` — the skill every workflow loads — stating that an issue in this repo is picked up by triage, so a fix you can already scope should be opened as a PR in the same run. The escape hatches stay: too large or ambiguous to fix, needs a maintainer decision, or verification is out of reach from CI. This generalizes a rule that already exists but only in two skills: `review-runs` ("Issue (fallback): Only for problems too large or ambiguous to fix directly") and `review-reviewers` carry it, so the runs that have it don't do this. The runs that did — a `tend-review` session and a `tend-mention` session — load neither. ## Gate assessment - **Evidence level**: High — 2 occurrences this window, and the self-amplifying-output family has been recorded in prior windows (2026-08-04: review→fix ping-pong over five rounds; two bot PRs seven minutes apart on one root cause). - **Structural**: yes for the costly half. The bot's choice to file rather than fix is stochastic, but once an issue exists the triage conversion has no decision point — it fires on the `issues` event every time. - **Change type**: targeted fix (one short section), normal evidence bar. - **Both gates**: pass. Found by `/tend-ci-runner:review-runs` on run [31302531474](https://github.com/max-sixty/tend/actions/runs/31302531474). --------- Co-authored-by: tend-agent <270458913+tend-agent@users.noreply.github.com>
Problem
install-claude-binary.shandinstall-proxy-uv.shboth ride out a CDN blip on the shared window inshared/steps/lib/retry.sh. The codex harness's third pre-agent installer wasn't on it:npm install -g "@openai/codex@..."ran as a bare inlinerun:block — one attempt, no timeout, no backoff. It is the codex action's only reach to a third party, and it sits ahead ofRun Codex, so a registry blip took the whole run red having done none of the work the trigger asked for.Solution
Extracted the install to
shared/steps/install-codex-cli.sh, sourcinglib/retry.shthe wayinstall-proxy-uv.shdoes; the action step becomes abash .../install-codex-cli.shwithCODEX_VERSIONinenv:. The version-pin rationale stays incodex/action.yamlnext to the input it explains.The 60s timeout stands
#909 flagged that
retry_installhardcodestimeout 60, sized for acurl | shthat fetches one installer script, and asked whether a global npm install could legitimately exceed it — in which case dropping codex under the current lib would convert slow installs into failures.Measured on a GitHub-hosted runner with an empty npm cache, three cold installs of
@openai/codex@0.131.0-alpha.22: 3.08s, 2.60s, 2.63s (218 MB unpacked, 2 packages). The package ships a prebuilt per-platform binary, so there is no dependency tree to resolve and no toolchain to run — the shape that would justify a longer window isn't there. 60s leaves ~20x headroom, so no optional third parameter onretry_install; the measurement and its date are recorded in the script's header so a future bump can re-check it rather than re-derive it.Testing
Four tests in
generator/tests/test_shared_steps.pymirroring the claude and uv suites, with a fakenpmanswering503 Service Unavailableon a schedule: rides out a four-failure burst, backs off 5/10/20/40 with jitter, reddens after five attempts, and installs first-try without sleeping. The happy-path test asserts the installed version echoes back, which keeps the pin honest — the step has to install thecodex_versionit was handed, not npm'slatest.Against the bare install the burst, backoff and redden tests fail (one attempt, no
after 5 attemptsmessage); against the new script all 52 tests in the file pass.shellcheck -S warningand the rest of pre-commit are clean on the touched files.Stacking
Based on #908, where
lib/retry.shlands — it will retarget tomainwhen that merges. The diff against #908 is this commit alone.Closes #909