Skip to content

feat(cli): add the cli eval suite and experiment suite plumbing - #327

Merged
Coly010 merged 20 commits into
mainfrom
columferry/cli-eval-suite
Sep 23, 2026
Merged

Coly010 merged 20 commits into
mainfrom
columferry/cli-eval-suite

Conversation

@Coly010

@Coly010 Coly010 commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Final chunk of the closed #308 — the piece @mattrossman asked for: the minimal CLI suite plumbing and CODEOWNERS, standalone, with no CLI experiment internals to review. 15 files, 260 lines, and four of those files are 11–13 lines each.

Everything it depended on is now on main (#315's experiments/<owner>/ layout, and #324 + #325's sandbox options), so this is based on main and the diff is only its own content.

What it adds

cli as an eval suite and an experiment suite, @supabase/cli ownership over /evals/cli/, /experiments/cli/ and cli-eval-results.json, and the workflow wiring so eval-refresh and append-gh-pages-history know the suite exists.

The substance is five environment columns: one scenario run unchanged across forced CLI environments, so a failure isolates to which environment broke. They're thin now that experiments/presets.ts exists — the whole of the Docker-less column is:

export default defineExperiment({
  ...codexGpt6Luna,
  suite: ['cli'],
  // beta: the Docker-less path only exists in the managed stack, which ships in beta.
  localStack: localStackRuntime({ cliVersion: 'beta', docker: 'absent' }),
  skipEval: skipUnlessDockerless,
});
column environment picks up
codex-gpt-6-luna-cli-pinned repo-pinned CLI, Docker available every interface: cli eval that isn't hosted-linked
…-cli-stable npm latest same
…-cli-beta npm beta same
…-cli-nodaemon beta, daemon unreachable also needs needsDocker: false + projectRunning: false
…-cli-absent beta, no docker binary also needs needsDocker: false + projectRunning: false

All five columns share one skipEval: skipUnlessCli, so they run the same eval set and stay comparable — which is the whole point of the suite. An earlier revision made the pinned column the shared benchmark experiment with 'cli' appended to its suite; that experiment has no skipEval, so it picked up hosted-linked evals the other four skip. A CLI-owned pinned experiment fixes that and leaves this PR touching nothing outside CLI-owned paths.

skipUnlessCli / skipUnlessDockerless are the only things left over from the old experiments/_lib/; they live in experiments/cli/lib/, which discovery ignores for free since it only matches *.experiment.ts directly under an owner directory.

Verified, including the parts tests can't reach

format:check, typecheck, and the sandbox, core, framework, test:cli-lib, vercel-runner and web suites all pass.

Two things unit tests can't cover, checked directly instead:

  • Discovery, since it's filename-convention-based and fails silently: all 16 experiments resolve and load, experiments/cli/lib/ is correctly not treated as an experiment, and the five columns resolve to the intended runtimes and channels (beta, beta, beta, stable, and none for the pinned baseline).
  • The workflow, which only ever runs in CI, no longer resolves channels at all — it passes through the manual cli_stable_version / cli_beta_version dispatch inputs and otherwise lets run-vercel-evals.ts derive the channels from the actual pairs. Pre-filling both env vars would have short-circuited that narrowing.

Each run's results now record the CLI version the sandbox actually installed, read from the session's environment marker, so a stable row names its binary rather than just its channel. That needed cliVersionSchema widened — it rejected every prerelease, so a beta run's real version could not have been recorded at all.

test:cli-lib carries --passWithNoTests deliberately: evals/cli/ has no scorer tests until #281/#314/#316 land, and without the flag the script's exit code depends on which of its two paths happens to be populated.

Note on the column names

These follow the gpt-6-luna rename from #328. They key cli-eval-results.json, and no results exist yet, so there is nothing to churn — but that also means the names should settle before the suite is first run.

Next

#281, #314, #316 and #307 get re-parented onto main once this lands — they currently point at the retained branch of the closed #308. /cc @Rodriguespn @kanadgupta

Moves the CLI channel resolver into @supabase-evals/sandbox and lets
localStackRuntime's cliVersion accept 'stable'/'beta' in addition to an
exact version, resolved against npm's dist-tags at session start. An
eval's own cliVersion: frontmatter pin still wins. Forwards the
resolved channel pins to Vercel sandbox jobs so a run scores against
one version instead of each job re-resolving "latest" independently.
…vals/sandbox

Promote the CLI team's experiment-land dockerAwareLocalStackRuntime into a
first-class localStackRuntime({ docker: 'no-daemon' | 'absent' }) option, so
any experiment can stage a sandbox where the Docker daemon is unreachable or
the docker binary is absent entirely, alongside the mountDockerSocket sandbox
primitive and needsDocker eval frontmatter it depends on.
@vercel

vercel Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
evals Ignored Ignored Preview Sep 23, 2026 11:37am UTC

Request Review

- Export FORWARDED_ENV_NAMES from run-vercel-evals.ts and import it in the
  test instead of a drifted local copy, so a leaked XAI_API_KEY can no
  longer break agentEnvironment()'s assertions.
- Make cliDebUrl's arch param required and check both amd64 and arm64
  release assets, since the resolver's host arch can differ from the
  sandbox container's; delete the now-unused hostDebArch.
- Generalise the beta walk-back to every published X.Y.Z-beta.N version
  older than the unpublished one (not just the same minor), ordered with a
  numeric compareBetaVersionsDesc comparator instead of a string compare.
- Treat a thrown error for one walk-back candidate as "unavailable" and
  keep probing the rest, instead of aborting the whole walk-back; the
  initial dist-tag asset check still fails loud.
- Import isRecord from @supabase-evals/core/json instead of a local copy
  that dropped the array guard.
- Guard the version cache's rejection cleanup with an identity check so a
  stale promise can't evict a newer cached one.
- Drop cliDebUrl/hostDebArch from the sandbox barrel (no external
  consumers) and correct local-stack-runtime's doc comment: channel
  resolution is memoised per process, not per session.
… resolution

Strips agent-added comment cruft (module docblock essay, CI-run-ID call-out,
code-shape narration) back to the invariants and external quirks the code
can't express on its own; no behaviour change.
Condense multi-paragraph comments in local-stack-runtime.ts down to the
non-obvious fact each one exists to preserve, dropping restated code
sequences and step-by-step narration.
@Rodriguespn
Rodriguespn force-pushed the Rodriguespn/pr308-nested-experiments-proposal branch from a2edffd to 1e930b2 Compare September 22, 2026 11:51
…ls once per run

debAssetHeadOk conflated a GitHub 429/5xx with a missing asset (response.ok
false for both), which could downgrade a beta release during a GitHub
outage and record results under the wrong version. Only a 404 now means
"asset not published"; any other non-2xx throws. The beta walk-back loop
is updated to match: a 404 still advances to the next candidate, but a
thrown error aborts the walk-back and propagates instead of being caught
and skipped.

Separately, nothing set SUPABASE_CLI_STABLE_VERSION/SUPABASE_CLI_BETA_VERSION,
so each sandbox job resolved its own CLI channel version independently,
risking two different versions in one fan-out if a release landed mid-run.
runPairs now resolves each channel pin once via resolveChannelPins() before
runBounded and threads the same pins into every job's .env write.
Union each pair's effective channel (exact eval pins win and need none;
otherwise the pair's experiment's localStack.cliChannel) before fan-out,
instead of unconditionally resolving both channels. A run with no
CLI-channel experiments now does zero npm/GitHub lookups. An experiment
that can't be resolved to a config throws instead of silently counting
as needing no channel.
The controller's channel lookup built experiments/<name>.ts by hand, which
stopped matching when experiments moved to experiments/<owner>/*.experiment.ts.
Use the shared discovery helper so the path convention lives in one place.
…y/sandbox-docker-availability

# Conflicts:
#	packages/sandbox/src/cli-channel.ts
#	packages/sandbox/src/local-stack-runtime.ts
Splits the final chunk out of the closed #308: the cli suite/experiment
enums, CODEOWNERS scoping, the four forced-environment CLI experiment
columns plus their skip predicates, and CI wiring so the eval-refresh and
gh-pages-history workflows know about the new suite.
presets.ts landed without the baselineExperiment alias these spread, and
typechecking experiments/ now needs the test files excluded.
@Coly010
Coly010 force-pushed the columferry/cli-eval-suite branch from bdbaafe to 9cba14a Compare September 22, 2026 18:57
@Coly010
Coly010 changed the base branch from Rodriguespn/pr308-nested-experiments-proposal to columferry/sandbox-docker-availability September 22, 2026 18:57
…pt text, and read the environment marker without trusting the agent's PATH

writeRootFile only chmod'd after docker cp, leaving the marker and CLI
shims owned by the host UID rather than root. buildToolSurfaceAddendum
and buildLocalStackTools also described docker as installed/reachable
in the no-daemon and absent states. And the environment marker was
only reachable through exec(), whose SANDBOX_PATH has an agent-writable
first entry that could shadow cat.

Fix writeRootFile to chown+chmod in one checked root command, thread
the resolved Docker state into both prompt/tool builders, and add
DockerSandbox.readRootFile (docker exec in exec form, no shell) plus
LocalStackScoringContext.environmentMarker to read the marker as root
without going through resolveSandboxPath or the agent's PATH.
…cker-availability

# Conflicts:
#	README.md
#	packages/sandbox/src/cli-channel.ts
#	packages/sandbox/src/local-stack-runtime.ts
@Coly010
Coly010 changed the base branch from columferry/sandbox-docker-availability to main September 23, 2026 10:00
The model upgrade renamed the baseline preset and its experiment file, so
the cli columns follow it — their names key cli-eval-results.json, and no
results exist yet to churn.
@Coly010
Coly010 marked this pull request as ready for review September 23, 2026 10:09
@Coly010
Coly010 requested a review from a team as a code owner September 23, 2026 10:09

@Rodriguespn Rodriguespn left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for updating this for the GPT-6 changes. Can we make three changes before merging?

  • Can we record environmentMarker().cliVersion in raw and exported results, so each stable/beta run names the exact CLI binary?
  • Can we remove eager stable/beta resolution from the workflow, pass through only manual overrides, and let run-vercel-evals.ts resolve channels from the actual pairs?
  • Can we add a CLI-owned pinned experiment using codexGpt6Luna and skipUnlessCli, so pinned/stable/beta use the same eligibility rules without changing the shared benchmark/regression experiment?

…r channel resolution to the controller, and give the CLI suite its own pinned column

Records environmentMarker().cliVersion (the CLI binary that actually ran) into raw and exported results instead of the near-always-empty frontmatter pin, widening cliVersionSchema to accept prerelease versions so beta runs validate. Strips eager npm resolution from eval-refresh.yml's prepare job so run-vercel-evals.ts's requiredCliChannels narrowing runs as intended, folding the now-pass-through step into "Prepare inputs" and treating a blank env var as unset in resolveChannelPins. Adds a CLI-owned codex-gpt-6-luna-cli-pinned experiment (skipUnlessCli, no localStack override) so pinned/stable/beta share eligibility rules, and reverts the shared benchmark/regression experiment to its original suite.
@Coly010

Coly010 commented Sep 23, 2026

Copy link
Copy Markdown
Contributor Author

all three done in fab2e19, CI green.

  • results now record the version the sandbox actually installed, read from environmentMarker(), falling back to the eval's frontmatter pin when there's no sandbox. also flipped the precedence in export-results.ts - it had the frontmatter pin winning over what actually ran.
  • workflow no longer resolves anything, just passes through the manual cli_stable_version / cli_beta_version inputs. folded the step into "Prepare inputs" since that's all it did once the npm call came out. run-vercel-evals.ts derives the channels from the pairs as intended - good catch that pre-filling both env vars was silently short-circuiting that.
  • added codex-gpt-6-luna-cli-pinned with skipUnlessCli, and reverted the shared benchmark experiment back to ['benchmark','regression']. you were right, i'd checked for a config difference and missed that the eligibility predicate is the difference - pinned was picking up hosted-linked evals the other four skip, so the columns weren't comparable. side benefit is this PR now touches nothing outside cli-owned paths.

two things i hit on the way that weren't in your list:

cliVersionSchema was /^\d+\.\d+\.\d+$/, so it rejected every prerelease. recording a beta run's real version would have failed validation outright - widened it to match VERSION_RE in cli-channel.ts. it's the same const behind the frontmatter field, which seems fine since pinning a beta is legitimate.

resolveChannelPins used override ?? resolveCliVersion(...), so an env var set to empty string counted as a pin of '' rather than unset - which is precisely what a blank dispatch input now produces. trims and treats blank as unset, with a test.

@Rodriguespn Rodriguespn left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Re-ran the latest version of this PR with #281’s build-database-002-stack-lifecycle scenario across all five CLI experiments. Each row records the exact CLI version, channel, and resolved
runtime.
LGTM 🚢

@Coly010
Coly010 merged commit 55112b9 into main Sep 23, 2026
7 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.

2 participants