Skip to content

fork-ci: run tests with nextest, and record a baseline - #18

Merged
thedancingdeveloper merged 4 commits into
fork/mainfrom
fork/ci-nextest
Oct 9, 2026
Merged

thedancingdeveloper merged 4 commits into
fork/mainfrom
fork/ci-nextest

Conversation

@thedancingdeveloper

Copy link
Copy Markdown

Summary

  • Since the workspace bins are built, the app-server and core integration suites actually run, and hooks: per-handler failClosed and PreToolUse on write_stdin #6 and Let spawn_agent carry an output schema #10 fail tests unrelated to their changes. `cargo test -p` ignores `codex-rs/.config/nextest.toml`, which serializes the `app_server_integration` and `core_apply_patch_cli_integration` groups, retries once and sets the slow timeouts.
  • Mirror `.github/workflows/rust-ci-full-nextest-platform.yml`: install `bubblewrap`, install cargo-nextest 0.9.103 through `taiki-e/install-action@44c6d64` (upstream's pin), and run `cargo nextest run -p`. `RUST_MIN_STACK` and the workspace bin build stay.
  • The cli skip moves from a `cargo --skip` to a nextest filterset read from `.github/fork-ci-known-failures.txt`. That file lists only tests shown to fail on unmodified `fork/main`, each with the baseline run that proved it.

Baseline

  • Run on `fork/ci-nextest-baseline`, which is unmodified `fork/main` plus only this workflow change, forcing codex-core, codex-app-server, codex-app-server-protocol, codex-hooks, codex-config, codex-home and codex-ext-skills: https://github.com/TheDancingDeveloper-org/codex/actions/runs/37985596324
  • The known-failures list is filled from that run's results. Nothing is added without it showing the test fail.

Test plan

cargo test -p runs the app-server and core integration suites without the
serialization, retries and slow timeouts in codex-rs/.config/nextest.toml,
so #6 and #10 fail tests that are unrelated to their changes. Mirror
rust-ci-full-nextest-platform.yml: install bubblewrap, install cargo-nextest
0.9.103 from the same taiki-e/install-action pin, and run `cargo nextest run
-p` so that config applies. RUST_MIN_STACK and the workspace bin build stay.

The cli skip moves from a cargo --skip to a nextest filterset read from
.github/fork-ci-known-failures.txt. That file lists only tests shown to fail
on unmodified fork/main, each with the baseline run that proved it, and it
starts empty until that baseline exists.

@thedancingdeveloper thedancingdeveloper left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Review (epic lead): blockers on head 328a468. The design is right: it mirrors upstream (bubblewrap, nextest 0.9.103 via upstream's taiki pin, the in-tree nextest.toml), adds a forced-package workflow_dispatch input for baselines, and keeps known failures in a file.

  1. Multiple known-failure filters cancel each other. Each entry becomes its own -E "not (<expr>)". nextest unions multiple -E filtersets: a test runs if it matches any of them. With two entries A and B, not A OR not B matches A (A isn't B), so both known failures run again. It only works by accident with zero or one entry. Build a single expression, -E "not ((A) | (B) | …)", and omit -E entirely when the file is empty. Test the two-entry case with a quick cargo nextest list -E … on any small crate and paste the output.
  2. The codex-cli exception has never had real baseline evidence. This PR drops the old --skip packaged_daemon_bootstrap_seeds_local_package, and that's correct under the new rule. But the "baseline" cited for it before (run 37944449502 on fork/ci-baseline) succeeded, almost certainly because that branch changed only the workflow, so the diff-derived package list was empty and nothing was tested. Add codex-cli to the forced packages of the new baseline run. If the test fails there, add its line citing that run; if it passes, it needs no entry.
  3. The baseline run 37985596324 is still in progress. The file must be filled from its results, with each line citing it, before this can merge. If a listed test is flaky (passes on retry under nextest's retries = 1), say so in its reason, or leave it out.

Non-blocking: ${{ inputs.packages }} is interpolated straight into the shell. Only repo writers can dispatch, but pass it via env: and read "$PACKAGES". That's the standard Actions injection hygiene.

nextest unions multiple -E filtersets, so one `not` per line re-includes
every excluded test as soon as the file has two entries. Verified with
cargo-nextest 0.9.103: `nextest list -E 'not ((test(alpha)) | (test(beta)))'`
lists only gamma, while two separate `-E 'not (...)'` flags list all three.
Build one disjunction and omit -E entirely when the file is empty.

The forced package list now arrives through an env var instead of being
interpolated into the script.

@thedancingdeveloper thedancingdeveloper left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Review (head 9ff85e4): blocker 1 is fixed. Known failures are joined into one -E "not ((A) | (B))", and -E is omitted when the file is empty. The printf '%s | ' / ${joined% | } join is correct. inputs.packages now goes through env:.

Still open:

  • (2) codex-cli isn't yet among the forced baseline packages. The old cli "baseline" ran no tests.
  • (3) Baseline run 37985596324 is still in progress, and the known-failures file is empty. Fill it from that run, each line citing it, and paste a cargo nextest list -E … for a two-entry file showing both are excluded.

Once both are in and #6 or #10 is green under the new step, this has no blockers.

set -e stopped the loop at the first failing crate, so the baseline only
ever tested codex-core. Record the status and keep going, then exit with it,
so one crate's failures do not hide the next crate's.

@thedancingdeveloper thedancingdeveloper left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Review (head cf08ad2): the new commit is right. The test loop now runs every crate and exits with the combined status (|| status=1, then exit "$status"), so one failing crate doesn't hide the others' results. That matters for a baseline. Still open, unchanged from my last review: (2) codex-cli in the forced baseline, and (3) the known-failures file is still empty. Fill it from the new baseline (37986693807 is running), each line citing the run, and include the two-entry nextest list proof.

@thedancingdeveloper thedancingdeveloper left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Review (head cf08ad2): new blocker: root cause of the mass failures.

Under the old step, the five PR runs (#6, #10, #12, #13, #14) fail identical test sets per crate. In codex-app-server it's suite::v2::analytics::* and about 8 of suite::v2::command_exec::*. In codex-core it's agents_md::restricted_project…, apply_patch_cli::* (symlink and hard-link escape, local sandbox) and many approvals::*. Almost all of them need the bubblewrap sandbox.

Upstream's shared .github/actions/setup-ci, which its Linux nextest jobs use (rust-ci-full-nextest-platform.yml:76, :291), has:

- name: Enable unprivileged user namespaces (Linux)
  run: |
    sudo sysctl -w kernel.unprivileged_userns_clone=1
    if sudo sysctl -a 2>/dev/null | grep -q '^kernel.apparmor_restrict_unprivileged_userns'; then
      sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
    fi

Ubuntu 24.04's AppArmor userns restriction blocks bwrap, and fork-ci doesn't relax it.

  • Fix: copy that step (the four lines, not the whole setup-ci action, which also moves the target dir) into fork-ci before the tests, with a comment pointing at .github/actions/setup-ci/action.yml.
  • Then re-run the baseline with it. The current baseline (37986693807) was measured without it, so its failures would wrongly fill the known-failures file with dozens of sandbox tests. Only what still fails after this fix belongs in the file.

The earlier blocker (codex-cli in the forced baseline) still applies.

Ubuntu 24.04's AppArmor user-namespace restriction blocks bwrap, so every
sandbox test (command_exec, approvals, apply_patch_cli, analytics) fails in
fork-ci. Upstream's Linux nextest jobs avoid it through
.github/actions/setup-ci/action.yml, which sets
kernel.unprivileged_userns_clone=1 and, where the knob exists,
kernel.apparmor_restrict_unprivileged_userns=0. Do the same here.

The known-failures list stays empty until a baseline run that includes this
step shows what still fails.

@thedancingdeveloper thedancingdeveloper left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Review (head cd841da): the userns step is correct and minimal. It's the four lines from upstream's .github/actions/setup-ci/action.yml, with kernel.apparmor_restrict_unprivileged_userns=0 guarded by its existence check, sits before the tests, and cites its source. The remaining condition is evidence: baseline run 37993288225 (with this step) has to finish, the known-failures file gets only what still fails there (each line citing it), codex-cli must be in the forced set, and #6 or #10 must go green under the new step. I'll check the file against that run's failures myself.

@thedancingdeveloper

Copy link
Copy Markdown
Author

Oversight review of the delta 9ff85e4..cd841da: no blockers (head cd841da)

  • The userns sysctls match upstream's setup-ci, and the job runs on GitHub-hosted ubuntu-24.04, so the host-wide kernel change is confined to a throwaway VM. It must not be copied to a self-hosted runner.
  • The per-crate loop now collects every failure and exits non-zero at the end, which is what the baseline needs.
  • fork-ci is green on this head (16m25s).

Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com

@thedancingdeveloper
thedancingdeveloper merged commit af8bd29 into fork/main Oct 9, 2026
1 of 2 checks passed
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.

1 participant