Skip to content

refactor(codex-cloud): replace the Taskfile and mise with a shell script - #3839

Merged
max-sixty merged 3 commits into
mainfrom
codex-simp
Aug 17, 2026
Merged

refactor(codex-cloud): replace the Taskfile and mise with a shell script#3839
max-sixty merged 3 commits into
mainfrom
codex-simp

Conversation

@max-sixty

@max-sixty max-sixty commented Aug 17, 2026

Copy link
Copy Markdown
Owner

The Codex Cloud environment commands ran through four layers: a settings field invoked mise, mise fetched Task, Task ran scripts/codex-cloud/Taskfile.yaml, and that Taskfile's whole body was one shell block. This collapses them to bash scripts/codex-cloud/codex.sh setup and bash scripts/codex-cloud/codex.sh maintain.

mise was there only to break a bootstrap circle. The script already installs cargo-insta, cargo-nextest, Nushell, and PowerShell from their release archives with SHA-256 checks, but Task could not join that list while Task was also running it — installing Task from the Taskfile needs Task already present. Dropping Task as the runner dissolves the circle: codex.sh is plain bash, and Task installs from its own archive with a checksum like the other four. Nothing bootstraps anything.

Two things go with it. The launchers no longer verify a SHA-256 digest of the script before running it as root, and MISE_NO_CONFIG=1 is gone because mise is. Both were introduced in #3810 to stop a task branch changing the code setup runs as root. The environment carries no variables or secrets, it is recreated per task, and the agent already runs repo code there as ubuntu. The payoff is that the two settings fields never need editing again — previously every Taskfile change meant pasting a fresh digest into both and invalidating the cache.

The root Taskfile.yaml had setup-codex and maintain-codex forwarding into the deleted Taskfile. Nothing called them: the Cloud launchers targeted the sub-Taskfile directly with -t, and both require root on the universal image, so neither runs on a dev machine. Deleted.

test_codex_cloud_launchers_match_taskfile is deleted too. It derived the Taskfile's digest and Task version so the README launchers could not drift from the file's actual bytes. Without a digest both sides were hardcoded constants in the same repo, and it never could observe the Codex Cloud settings field that actually holds the command — an environment-specific test in a suite whose job is testing wt.

Testing

cargo run -- hook pre-merge --yes passes. The Task archive's checksum was verified against a fresh download rather than copied from the release notes.

codex.sh itself needs Linux, root, and the universal image's UID-1000 ubuntu user, so nothing here executes it — bash -n and shellcheck are clean, and the first real exercise is the next Codex Cloud environment build. The shell body is carried over from the Taskfile unchanged apart from the Task install and the dropped mise lines.

This was written by Claude Code on behalf of max-sixty

The saved setup and maintenance commands verified a SHA-256 digest of
scripts/codex-cloud/Taskfile.yaml before running it as root, and pinned Task
through `mise x task@<version>`. Every Taskfile change therefore meant editing
two Codex Cloud settings fields, which also invalidated the environment cache.

The launchers are now `mise x task -- task -t scripts/codex-cloud/Taskfile.yaml
<task>`, and they stay fixed across Taskfile edits. Setup installs Task globally
and symlinks it into /root/.local/bin, so the Cargo wrapper's PATH no longer
names a version-specific mise install directory.

This gives up the property that a task branch cannot change the code setup runs
as root, along with the `MISE_NO_CONFIG=1` guard against a branch-supplied mise
config. The environment carries no variables or secrets and is recreated per
task.

The launcher-sync test drops the digest derivation and instead checks that the
Taskfile still defines the tasks the README launches.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two references to the checksum gate outlive it, and the diff drops a retry setting that reads as incidental rather than intended.

CLAUDE.md still points at a checksum-gated setup. Its Quick Start line reads Codex Cloud: use the checksum-gated setup in scripts/codex-cloud/README.md — after this PR there is no checksum to gate on. CLAUDE.mdDocumentation says "Behavior changes require doc updates", and draft #3838 deletes this exact line, which is what makes the coupling visible.

.claude/skills/running-tend/SKILL.md now instructs a future weekly run to do something impossible. Under Weekly Maintenance: CI Pin Bumps, the Codex Cloud bullet opens with "pins Task through mise" and ends "…then copy the Taskfile's new digest into both README launchers". Both are false after this change, so the next tend-weekly run either invents a digest step or burns it, and keeps hunting for a Task pin that no longer exists. Dropping the digest clause and either removing Task from that bullet or noting it tracks latest would settle it.

Both lines are outside the diff — happy to push a commit with the two edits if you'd rather they land on this branch than in whichever of #3835#3838 you keep.

Sibling drafts. #3835, #3836, #3837, and #3838 are all open against the same three files (#3838 additionally covers CLAUDE.md, tests/CLAUDE.md, and the root Taskfile.yaml). Assuming this is the one you're landing, closing the four keeps the next reviewer from diffing them against each other.

Checked and clean: no {{.TASK_VERSION}} references survive the var's removal; sha2 and Regex both still have other users in the workspace, so neither the dev-dependency nor the import goes unused; and test -x /root/.local/bin/task catches a dangling symlink as well as a missing one. The MISE_NO_CONFIG and Task-pin tradeoffs are argued in the PR body, so I'm not re-litigating them.

Comment thread scripts/codex-cloud/Taskfile.yaml Outdated
…rences

`MISE_HTTP_RETRIES: 6` is the retry budget for the `mise use --global task`
fetch, which is now the one download in setup without a retry cushion, so it
stays on the task. Keeping it off the launcher preserves the fixed launcher.

The `CLAUDE.md` Quick Start line and the `running-tend` weekly bullet both still
described a checksum gate and a Task pin that no longer exist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All three points from the last review are addressed in 230f790: MISE_HTTP_RETRIES: 6 is back on the setup task (thread resolved), the CLAUDE.md Quick Start line no longer calls the setup checksum-gated, and the weekly running-tend bullet drops the digest-copy step and now says Task tracks latest through mise — which matches the unpinned mise use --global task in the Taskfile. Nothing outstanding on the diff; the approval is held only on CI.

The red on this head is a GitHub incident, not the PR. GET /repos/nushell/nushell/releases is returning [] right now, authenticated and unauthenticated alike, while GET /repos/nushell/nushell/releases/tags/0.115.0 returns the release with all 16 assets — including nu-0.115.0-x86_64-unknown-linux-gnu.tar.gz. hustcer/setup-nu resolves its asset off the list endpoint, so it fails with No release for Nushell matching version specifier 0.115.0 and takes test (linux), test (macos), fast-checks, code-coverage, and both advisory affected tests down at the setup step, before any of this PR's code is reached. The same jobs were green on ca8e92d thirty minutes earlier against the same pin.

I reran the failed jobs. That cleared lint, which had died on a different symptom of the same incident (429 Too Many Requests from codeload fetching Swatinem/rust-cache), but setup-nu fails identically on attempt 2 — so further reruns won't help until the releases endpoint recovers. test (windows) passed on both attempts.

Not approving over a red rollup; I'll approve on the next green run.

Evidence

Reproducible from outside CI while the incident lasts:

$ gh api 'repos/nushell/nushell/releases?per_page=3' --jq 'length'
0
$ gh api repos/nushell/nushell/releases/tags/0.115.0 --jq '.assets|length'
16

Failing step, identical on attempt 1 and attempt 2:

Try to get assets for Nu: arch = x64, platform = linux, feature = default
##[error]No release for Nushell matching version specifier 0.115.0 of default feature.

lint, attempt 1 only — cleared by the rerun:

##[error]Response status code does not indicate success: 429 (Too Many Requests).
##[error]Failed to download archive 'https://codeload.github.com/Swatinem/rust-cache/tar.gz/6323deb...' after 3 attempts.

setup-nu is used at four sites, so the same blast radius applies to nightly and benchmarks if the endpoint is still degraded when they fire: .github/actions/test-setup/action.yaml, .github/workflows/nightly.yaml, .github/workflows/benchmarks.yaml, .github/workflows/coverage.yaml.

mise was in the environment commands only to fetch Task, and Task only ran a
shell block. Task could not join the script's own `install_binary` list,
because the Taskfile was what ran that list — installing Task from it needs
Task already present. Dropping Task as the runner breaks the circle: `codex.sh`
is plain bash, and Task installs from its release archive with a SHA-256 check
like cargo-insta, cargo-nextest, Nushell, and PowerShell. Nothing bootstraps
anything, so the launchers are `bash scripts/codex-cloud/codex.sh setup` and
`… maintain`.

The root Taskfile's `setup-codex` and `maintain-codex` forwarded into the
deleted Taskfile and nothing called them: the Cloud launchers targeted the
sub-Taskfile directly, and both require root on the universal image, so neither
runs on a dev machine.

`test_codex_cloud_launchers_match_taskfile` derived the Taskfile's digest so the
README launchers could not drift from its bytes. Without a digest both sides
were constants in the same repo, and it never could observe the Codex Cloud
settings field that actually holds the command.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@max-sixty max-sixty changed the title chore(codex-cloud): simplify the environment launchers refactor(codex-cloud): replace the Taskfile and mise with a shell script Aug 17, 2026
@max-sixty
max-sixty merged commit d73ce69 into main Aug 17, 2026
41 checks passed
@max-sixty
max-sixty deleted the codex-simp branch August 17, 2026 18:13
max-sixty added a commit that referenced this pull request Aug 17, 2026
#3841)

Consolidates the two remaining Codex Cloud drafts, #3835 and #3838, onto
what #3839 landed, then cuts what was left. Main's setup is a 163-line
script and a 26-line README under `scripts/codex-cloud/`; this is one
79-line file at `dev/codex.sh`, beside the repo's other development
files, with the README folded into its header. That empties `scripts/` —
the directory existed only for this.

**Codex Cloud runs as root** (from #3835). Setup used to replace
`/root/.cargo/bin/cargo` with a wrapper that re-executed cargo as a
UID-1000 `ubuntu` user under `tini`, and maintenance chowned the
checkout, the rustup home, and three cache directories to match. All of
it existed to keep the suite's permission tests from skipping, since
root can write to a read-only file.

Worth stating plainly: ten tests now skip on Codex Cloud. The pair
carrying the most weight is
`test_remove_foreground_succeeds_with_stuck_directory` and its
`_detached` twin, the only automated coverage of `wt remove` against a
directory it cannot delete. It is not a new hole — `setup-web` creates
no non-root user, so the Claude Code web environment has always skipped
them, and `tests/integration_tests/approval_pty.rs:157` carries a
standing TODO about it. Codex Cloud was the one environment buying an
exception, and a cargo wrapper, `tini`, `runuser`, and four chown passes
were the price. Both environments now agree about what the suite
observes, and that TODO is the single place to fix it for both.

Three of the ten decided that skip by reading `$USER` rather than by
probing the filesystem, which fails open: a container that runs as root
without exporting `USER` runs them and asserts an error root never gets.
They probe now, through one helper, like the other seven.

**Task, the checksums, and the retries are gone.** Nothing invoked Task
on Codex Cloud once #3839 stopped routing the launchers through it, so
it is no longer installed. The archive checksums follow the Taskfile
digest for the same reason that one went: HTTPS authenticates GitHub and
the container is disposable and secret-free, so verifying each download
bought a helper pair and a 64-char line per tool for very little. Each
install is now `curl | tar` and an `install`.

The version numbers stay. The gate runs `--all-features`, so nu and pwsh
drive PTY snapshots their own versions can move, and
`.github/actions/test-setup/action.yaml` pins cargo-insta,
cargo-nextest, and nu to the same three versions — unpinning those would
make the environment and CI disagree about snapshot output. Nothing
under `.github/` pins PowerShell, so CI runs whatever the runner image
ships and that version answers to nothing but these scripts.

**`setup-web` catches up** (from #3838). It gains `lsof`, installs
Nushell from its release archive rather than checking that one is
already present, bootstraps `uv` with pre-commit, and puts
`$HOME/.local/bin` on PATH. `wt` installs from the debug build produced
a few lines earlier instead of through a second full compile. Its `cargo
install` of cargo-insta and cargo-nextest stays unconditional, as on
main: neither form pins, and a `command -v` guard would have frozen
whatever versions the image happened to carry.

## Testing

`cargo run -- hook pre-merge --yes` passes, shellcheck is clean at
warning level, and `task --list` still parses. The `.tar.xz` and
`.tar.gz` extractions were run against the real release archives to
confirm the tar flags and the paths inside them. The three converted
tests pass unprivileged; their skip branch rests on the same probe the
other seven root-skipping tests already use.

Neither setup path is executable from a dev machine — Codex Cloud needs
Linux and root on the universal image, `setup-web` needs a web image —
so the first real exercise is the next environment build.

> _This was written by Claude Code on behalf of max-sixty_

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants