Repository navigation
feat(evals): add resolve-database-002-stale-stack-cleanup CLI eval (CLI-2402) - #316
Merged
Coly010 merged 60 commits intoOct 7, 2026
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This was referenced Sep 22, 2026
Coly010
added a commit
that referenced
this pull request
Sep 23, 2026
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: ```ts 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
…LI-2402) Fleet management, not startup: the agent stands up local Supabase for checkout-service, payments-api and a legacy-import prototype, then tears legacy-import down, restarts checkout-service, and leaves payments-api alone. Discoverability and reversibility are launch-scope contracts that need proving separately from the startup scenarios. CLI-2402 specifies the agent landing in a pre-seeded fleet of running stacks. The harness cannot do that: there is no pre-agent setup hook anywhere (EvalMetadata, LocalStackSessionArgs, LocalStackRuntime, createAgentEnvironment, runOne), and projectRunning is a single boolean rooted at the sandbox workdir. So the prompt is two-phase and the agent builds the fleet itself first. The other half of the issue's blocked note is stale: supabase stack list/start/restart/destroy/status all exist in 2.118.0-beta.57 behind SUPABASE_EXPERIMENTAL_STACK=1. What does not exist is a top-level supabase list, which is what the issue looked for. Stack resolution cascades managed-named, then managed-project-scoped, then legacy, so the scorer measures whichever path the agent took. The stack-list entry shape is unverified, so names are matched against every string in an entry rather than a hardcoded field. legacy-import being gone is gated on three signals, not two: absent from the fleet listing, unreachable, and a teardown command that actually ran. Without the third, an agent that created the directory but never started the stack would pass for a teardown it never performed - gone and never existed are indistinguishable on outcomes alone. Teardown evidence tracks cd context, so cd legacy-import && supabase stop counts on a CLI with no stack subcommand, while an echoed plan does not. Stable and the 2.117.0 pin have no stack subcommand, so this fails on those environments by design, tracking the gap until the fleet API ships.
…environmentMarker() resolve-database-002-stale-stack-cleanup's scoring.ts read the runtime marker via a shell-form `cat` that a shadowed binary on the sandbox PATH could forge, and duplicated the marker schema locally. Switch to LocalStackEvalContext.environmentMarker(), which main now exposes as a root-read, shape-validated read, and drop the stale duplicate type, path constant, and helper. Also trims the file's comments to keep only genuine why-explanations, per the repo's comment policy.
Coly010
force-pushed
the
columferry/cli-2402-resolve-cli-001-stale-stack-cleanup
branch
from
September 23, 2026 15:05
4ab013c to
1b1ae55
Compare
Coly010
changed the base branch from
columferry/cli-2398-cli-suite-and-arms
to
main
September 23, 2026 15:05
Move the detour parser and judge policy, stack resolution, metrics readers and judge-input formatting out of build-database-002 into evals/cli/lib so upcoming cli evals share one implementation. Adds project discovery and marker-isolation helpers for the multi-stack evals. build-database-002 behaviour is unchanged.
5 tasks done
…esolve-cli-001-stale-stack-cleanup
… into columferry/cli-2402-resolve-cli-001-stale-stack-cleanup
… onto evals/cli/lib Split the 1274-line scoring.ts into EVAL.ts, services.ts, fleet.ts and metrics.ts on top of evals/cli/lib, adding lib/stack-list.ts and lib/cli-invocations.ts (argv-parsed supabase invocations with cd, --stack, --project-id and --workdir targeting). Behaviour fixes: - Service projects resolve per name, so a deleted legacy-import directory no longer fails the other services; only checkout-service and payments-api projects are required. - Teardown evidence comes only from executed supabase stop/destroy/down invocations targeting legacy-import, not psql deletes or rm -rf. - legacy-import must be started before it is torn down, so stopping a never-started stack no longer passes. A start whose target is a shell expansion (a loop) counts for every service; teardowns and restarts with unresolved targets count for none. - When legacy-import's config.toml survives, its [db] port must not answer. - New checks: checkout-service was restarted, payments-api left untouched. - A managed stack stop that leaves legacy-import listed fails "gone". - Backend label managed-project is now lib's managed.
Move findSupabaseInvocations and its target helpers into the shared lib so every cli eval can attribute the agent's supabase commands to a project or stack.
… into columferry/cli-2402-resolve-cli-001-stale-stack-cleanup
… into columferry/cli-2402-resolve-cli-001-stale-stack-cleanup
Agents that never ran `supabase init`, or ran named stacks from the sandbox root, left no config.toml for the project search to find. Unresolved services are now looked up in `stack list` under the default and relocated homes, with sandbox-root named stacks and ambiguous roots handled.
jgoux
requested changes
Oct 7, 2026
jgoux
left a comment
There was a problem hiding this comment.
Thanks Colum, the evidence model and the notes are really thorough. Three scoring paths give the wrong verdict on plausible agent behaviour (P2), plus three smaller items inline.
…tale-stack-cleanup
A start whose target is a shell expansion counts as start evidence for every service but no longer completes setup or becomes the state anchor while every service has a start that names it.
A local db reset recreates or restarts the database container, so a change-phase reset no longer counts as restarting checkout-service on its own, and resets that seed services before any stop or restart are setup, not changes.
Drops the stale custom SUPABASE_HOME claim, notes the CliConfigParseError gap, and probes the default [db] port when config.toml sets none.
…ch, probe replay) - findSwappedServices ignores starts that failed, so the latest non-failed start decides a CLI-version swap - the payments-api own-marker check uses the same whole-word match as checkMarkerIsolation, so `marker for payments-api` counts - resolveServiceStacks runs each distinct probe command once per scoring pass, so the renamed-stack lookup no longer repeats the managed and legacy probes or duplicates their failure notes - add regression tests for seeding resets, differently named starts in a service directory, and dist-tag runners
Contributor
Author
jgoux
approved these changes
Oct 7, 2026
jgoux
left a comment
There was a problem hiding this comment.
Thanks Colum, all six points from the last pass are addressed. Approving. Three small edge cases are inline (all P3); they're fine as follow-ups.
Brings in --project-id precedence over the working directory and the success requirement for global supabase installs, plus staged-version helpers (readStagedCliVersion, readCliVersions).
…mmands A seeding db reset that moves the state anchor now falls back to command evidence when the same tool call also restarts, stops or destroys the service. Wire the staged CLI version helpers and add tests for --project-id precedence and failed global installs.
Coly010
deleted the
columferry/cli-2402-resolve-cli-001-stale-stack-cleanup
branch
October 7, 2026 16:01
7 tasks done
Coly010
added a commit
that referenced
this pull request
Oct 7, 2026
…399) (#314) ## Current Behavior Port collisions between concurrent local Supabase projects are a long-running pain point ([discussion #5968](https://github.com/orgs/supabase/discussions/5968)). The CLI historically assumed one machine means one fixed port set. Running parallel stacks is the flagship behaviour that work is meant to fix, and nothing measures whether an agent can actually drive it. ## Expected Behavior Adds `evals/cli/build-database-003-parallel-projects` (`interface: cli`, `projectRunning: false`, `needsDocker: false`). The agent stands up two independent local Supabase projects, `client-a` and `client-b`, concurrently in one sandbox. Each project gets a `clients` table holding a row naming that client, and the agent reports which API port each one landed on. The prompt never names a command, a port or Docker. Structured like `build-database-002`: `EVAL.ts` composes the checks and owns both `ctx.judge()` calls; `projects.ts`, `stacks.ts`, `report.ts` and `metrics.ts` hold the deterministic checks, each with its own tests. Shared probing (stack resolution, project discovery, marker reads, detour policy) comes from `evals/cli/lib`. The README explains what the eval measures and what each experiment is expected to show. | Check | Proves | |---|---| | two projects initialised | both project dirs found via `*/supabase/config.toml`, exact basename preferred (`client-a-old` doesn't make `client-a` ambiguous) | | both stacks reach ready | each project's stack resolves from its own directory and answers `select 1` | | stacks are on distinct ports | two independent stacks, not one reused or clobbered | | each project holds only its own marker row | both directions, case-insensitive: `client-a`'s db has `client-a` and not `client-b`, and vice versa | | each clients table holds exactly one row | the prompt asks for a single row per client; duplicates or an empty table fail | | reported api ports match the running stacks | each project's real API port appears in the final report as a whole number | | no container-runtime detours | LLM judge over the complete executed commands only (shared rubric with 002) | | metrics | per-project backend/runtime, ports, time to ready, `cliDetours` (regex diagnostic) — reported, never asserted | | final report is truthful about both projects | LLM judge given harness ground truth plus the transcript with tool outputs; fails on a misstated outcome, rows or ports, or an omitted/swapped API port, not on rows the report didn't recite | Known-bad cases covered by unit tests: - a port that only appears inside a longer number - rows swapped between the two databases - one database holding both rows - `cd client-b` resolving to `client-a`'s stack - a stack that resolves but reports no API URL, which fails with a clear note instead of falling back to `config.toml` - a `clients` table with three rows, or none The CLI-2399 issue describes this eval as blocked on multi-stack session support. That no longer applies: with `projectRunning: false` the harness starts zero stacks and only installs the CLI, so running two stacks is entirely the agent's job, which is the point of the scenario. **Expected results**: every experiment should pass. `absent` and `nodaemon` pass through `--runtime native` (pinned to beta, since the native managed stack only exists there); the Docker arms need the agent to move one project off the default ports unless the CLI allocates them. **Observed in CI** (snapshot: run 37638547341, 2026-10-07, head 76e9c61): 12/15 runs pass. Results drift between runs. | experiment | passed | |---|---| | `absent` | 3/3 | | `nodaemon` | 3/3 | | `beta` | 3/3 | | `pinned` | 2/3 | | `stable` | 1/3 | - `pinned` r3 fails the version-swap rule: the agent ran `npx --yes supabase@2.120.0`. - `stable` r2 and r3 fail `both stacks reach ready` with no start attempted for either project. **Policies** - Invocations are attributed to a project by the directory they ran in (`cwd`, `--workdir`, `SUPABASE_WORKDIR`, `env -C`) before their `--stack` name. - Relocated home passes, visibly: when the default home can't resolve a stack, the scorer retries under each `SUPABASE_HOME`/`HOME` the agent set on a start of that project (prefix, `env`, or an earlier `export` in the same command; a relative value resolves where the CLI runs). The `relocated home` note and the `relocatedHome` metric keep it visible. - Version swap fails, visibly: each project is judged by its latest start. If it used an `npx`/`bunx`/`npm exec`/`pnpm dlx`/`yarn dlx` runner or followed a global reinstall of another explicit version (compared against the staged CLI version, cleared by a global uninstall, and ignored when the PATH version after the run still equals the staged one), `both stacks reach ready` fails (`cliOverride` metric); a latest start through the installed CLI passes. The truthful-report judge's override note is added only to projects that were started that way. Dist-tag runners (`@latest`, `@beta`) can't be verified offline and are recorded in `cliRunnerUnverified` instead. ## Related Issue(s) Refs https://linear.app/supabase/issue/CLI-2399 ## Test plan - [x] `vitest run --root ../.. evals/cli`, `test:cli-lib`, and `pnpm typecheck`, re-verified against merged `main` (including #356) - [x] `apps/framework` `tsc --noEmit` - [x] `pnpm eval:dry -- --suite cli --experiment-suite cli` → 003 planned under all 5 cli experiments - [x] `pnpm format:check` - [x] `pnpm check`, all steps except the judge smoke step, which needs a provider API key locally - [x] CI refresh via `run-evals-changed`: inspected passing runs and each distinct failure shape - [x] Results refreshed via `run-evals-changed` (run 37638547341) Also carries #316's final commit (post-run install reconciliation wiring for stale-stack-cleanup), which missed #316's merge, so the shared lib and both evals stay consistent on main. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Current Behavior
Steady-state agent work is managing a fleet of local stacks, not starting one. Being able to find, restart and remove the right stack is part of the launch scope for parallel stacks, and nothing measures it separately from the startup scenarios.
Expected Behavior
Adds
evals/cli/resolve-database-002-stale-stack-cleanup(interface: cli,stage: resolve,projectRunning: false,needsDocker: false). The agent:checkout-service,payments-apiand an oldlegacy-importprototype, each with aservice_markerrow naming it.legacy-importdown completely.checkout-service.payments-apiexactly as it is.The prompt never names a command or Docker.
Structured like
build-database-002:EVAL.tscomposes the checks and owns bothjudge()calls;services.ts,fleet.tsandmetrics.tshold the deterministic checks, each with its own tests. This PR also adds one shared module, and carries one shared-lib fix:lib/stack-list.ts: parsesstack listoutput (a{ stacks }envelope or a top-level array). Only an unknown-subcommand error marks the listing unsupported; any other unreadable output fails closed.lib/stack.ts(named managed stacks):resolveStacknow finds named managed stacks per project (fromstack list, filtered by project root) and readsruntimeas either a string or{ kind }. Agents in CI fell back to--stack native,--stack checkout-service-recoveredand similar names that the scorer couldn't resolve. Theselib/stack.ts,lib/stack.test.tsandlib/README.mdchanges are identical to feat(evals): add build-database-003-parallel-projects CLI eval (CLI-2399) #314's (6f7041d), so whichever PR merges first, the other merges cleanly. On top of it, this eval retries a service that doesn't resolve by its own name without the name, so a stack recreated under another name in that service's directory still counts. Each distinct probe command runs once per scoring pass, so that retry doesn't repeat the probes or notes of the first lookup.It uses the shared
lib/cli-invocations.ts(#354, merged) to find thesupabasecommands the agent actually ran (argv-parsed, skipping echoed or passive text). Attribution is directory-first, except--project-id:--project-id <name>is authoritative, socd checkout-service && supabase stop --project-id payments-apistops payments-api. Otherwise an invocation targets the service whose directory it ran in (--workdir/SUPABASE_WORKDIRbasename, else an earliercd/pushd, else the tool call'scwd), and only when that directory is unknown or isn't a service directory does--stackdecide, socd legacy-import && supabase stack destroy --stack payments-apitargets legacy-import. The shared lib (lib/cli-invocations.ts,lib/detours.ts,lib/metrics.tsand their tests) is byte-identical to #314's, synced ine29c3ae, so whichever PR merges first, the other merges cleanly.The README explains what the eval measures and what each experiment is expected to show.
legacy-import's directory doesn't fail the survivors; bysupabase/config.toml, else by the CLI'sstack list(see below)select 1supabasestart targeted it that didn't fail (so it can't pass vacuously). The teardown command is reported, not requiredstack restart(or stop + start) targeting it in the change phase, not failed (commands)db resettargeted it in the change phase, and its postmaster started before setup completed (state) or, without timing, no stop/restart/destroy targeted it (commands); it still holds its own marker rowservice_markerisolation, both directions, case-insensitivedocker stopisn't a detour, it just doesn't count as a teardown)setupCompletedAt, which evidence decided each check,legacyTeardown(succeeded/failed/none),cliDetours— reported, never assertedDecisions worth a look:
stack stopthat leaves the stack listed fails;stack destroyor legacysupabase stoppasses.pg_postmaster_start_time()are known, and the survivor wasn't stopped or restarted in the same tool call as setup (one completion time can't order them; that falls back to commands, saying so), the postmaster decides: started more than 1s (CLOCK_TOLERANCE_MS) after setup means restarted, otherwise not, and commands aren't consulted. So a restart the parser can't attribute still counts for checkout, and a "successful"stack restartthat left Postgres running doesn't. Without both, the command rules below decide. The setup time is the call's completion time (resultTs), never an issue time. feat(core): record working directory and completion time on tool calls #356 has merged, soToolCallRecord.resultTsandToolCallRecord.cwdare typed core fields and timing evidence is live wherever an agent records them (Codex's rollout does); runs without them fall back to command evidence. Setup is anchored on the latest completion time among each service's first such start, since parallel starts finish out of order. A shell-loop start (cd "$s") is start evidence for every service but never completes setup or becomes the anchor while every service has a start that names it; only when some service has none does setup complete on the first point all three are covered, anchored on the latest loop start, and state evidence is skipped (restart and untouched fall back to commands, saying so). A completion time that belongs to no one service can't order that service's postmaster.db resetis a database restart. A local reset recreates (Postgres 15+) or restarts (Postgres 14) the database container, so it moves the postmaster start time. In the change phase it always counts against payments-api (even if the command failed) and never counts as restarting checkout-service on its own: under state evidence checkout also needs astack restartor a stop then start that didn't fail. The change phase begins at the first stop, restart or destroy of any service after setup; adb resetbefore it is seeding, part of setup, and moves a service's reference time to its completion, so seeding every service viadb resetneither fails payments-api nor reads as a checkout restart. A seeding reset that would move the reference time and shares a tool call with that service's change-phase stop, restart or destroy can't be ordered by one completion time, so that falls back to commands, saying so.state:,commands:orunavailable:(legacy-import lists each gate:listing:,resolution:,db port:,containers:,commands:), citing commands ascmd #<n> "<argv>"(1-based, truncated to 120 chars) and ISO times. The running checks say whether the stack resolved.supabase stop --no-backupexited 1 withStopVolumePruneError/LegacyStopVolumePruneError(CLI-2637) on stacks that were really removed, which would have failed the check; in run 37625367762 all 3 such teardowns passed by state. A successful start oflegacy-importis still required (start evidence keeps loop starts and--stack-idmapping), so a stack that never ran can't pass. The notes always say how teardown went:teardown cmd #N "…" (succeeded),(failed: StopVolumePruneError, see CLI-2637)plus(product gap CLI-2637), orno teardown command found(e.g. stopped throughdockerdirectly). The judge's ground truth states the same, andmetrics.legacyTeardownrecords it.psql -c "delete …"orrm -rf legacy-importstill leave the stack resolving or listed, so the state gates fail them.npx/bunx/pnpm dlxof another explicit version fails… stack is running; one whose latest non-failed start used the installed CLI passes, and a globalnpm i -g supabase@Xapplies to later invocations only when its tool call succeeded (a failed install leaves the installed CLI in place; a global uninstall clears it). The installed version is the marker'scliVersion, else/usr/bin/supabase --version, else PATH;metrics.cliVersionis that staged version andcliVersionAfterRunthe PATH one when it differs. Dist-tag runners (supabase@beta) can't be checked offline and are reported inmetrics.cliRunnerUnverified.config.toml, else by the CLI'sstack list. CI agents often created stacks withoutsupabase init(mkdir -p payments-api && cd payments-api && supabase stack start --runtime native --stack payments-api) or ran named stacks from the sandbox root, sometimes under a relocatedHOME/SUPABASE_HOME, so theconfig.tomlsearch found nothing. For each service it missed, the scorer readsstack listunder the default home and each relocated home the agent started the service with, and takes entries whosenameis the service (reachable preferred). Oneproject_rootis the project directory ((from stack list)); the sandbox root itself counts as a named stack started there (named stack at sandbox root, resolved by the existing named lookup); two or more distinct roots areambiguous (stack list: …)and fail. When aconfig.tomlexists the directory is unchanged, but if resolving the stack there fails (an agent broke its ownconfig.toml, or rooted the stack elsewhere), the onestack listentry named exactly for the service gives the directory to resolve in, under the default and relocated homes; ambiguous roots stay unresolved.lib/projects.tsis untouched."_tag":"Error",Unknown subcommand,UnknownSubcommand), doesn't count as a start, teardown or restart. A call with no recorded outcome counts as succeeded. Top-levelsupabase restartnever counts, since neither CLI has it. Start-marker exception: when a failed call's output has no CLI error envelope, each[task] done: Stack is ready.orStarted supabase local development setupit printed credits one start invocation of that call (in order) as not failed. A start that came up isn't discarded because a later command in the same call failed (… > out && jq …exiting 127, or aset -eseed that hit a TLS error). Other verbs keep the all-failed rule.unavailable:note.marker for payments-apiis payments-api's marker andpayments-api-archiveisn't.legacy-import's directory is gone, the scorer checksdocker psfor containers labelledcom.supabase.cli.project=legacy-importor namedsupabase_*_legacy-import; skipped whendockeris unreachable, since then nothing can be running. The scorer never reads which experiment it runs under.stack listfails closed. Only an unknown-subcommand error skips the listing half; any other unreadable output failslegacy-import stack is gone.--stack-idis mapped to a service from the"id"a successful start printed (when that start targeted one service) and from anystack listoutput the agent printed; an unknown id is attributed to no service.for s in …; do (cd "$s" && supabase start); done) count as start evidence for every service (see the anchor rule above). Loop teardowns and restarts count for none, since their target can't be attributed.cddoesn't carry across separate tool calls.stack restartrestarts Postgres; if it doesn't, state evidence on a native stack failscheckout-service was restartedafter a restart that succeeded (the truthful judge is told about both).The CLI-2402 issue describes the CLI side as blocked. It no longer is:
stack list/start/restart/destroy/status --stackexist in beta behindSUPABASE_EXPERIMENTAL_STACK=1. The harness can't hand an agent pre-running named stacks, so the prompt is two-phase: build the fleet, then manage it.Results
CI run 37625367762 scored 6/15.
legacy-importteardowns failed withStopVolumePruneError(CLI-2637) and passed by state.npx2.120.0 (policy fail; it then hit CLI-2638).stack stop, which leaves the stack listed (correct fail).config.toml(a duplicate[experimental]table givesCliConfigParseError) and rooted the stack atcheckout-service/runtime, so the scorer said it wasn't running. Resolution now falls back to thestack listentry for the service.supabase start --workdir "$PWD"once per service directory;$PWDwasn't expanded, so the first start read as a loop start, anchored setup too early, and payments' own start read as after setup (payments-api left untouchedfailed, with no teardown or restart attributed either).$PWDis now expanded against each invocation's cwd (sharedlib/cli-invocations.ts, identical to feat(evals): add build-database-003-parallel-projects CLI eval (CLI-2399) #314), unresolved starts no longer anchor setup, and the replay passes all fleet checks.#355 (the sandbox login-shell
PATHfix) is merged, sonodaemonis now real; earliernodaemonresults were affected by that bug. The per-call working-directory gap is fixed in #356, which is now merged, so per-callcwdattribution and timing evidence are in effect. Some Docker-arm agents decline to start becausedocker psshows the sandbox's own container; that's an environment artifact.Known product gaps seen in CI:
stop --no-backupexits 1 withStopVolumePruneErroron Docker clients older than 23.0, even though the containers are removedstack destroycan't remove a stack whose launch failed--runtime native, so some Docker-less agents give upRelated Issue(s)
Refs https://linear.app/supabase/issue/CLI-2402
Test plan
vitest run --root ../.. evals/cli(counterexamples include psql delete,rm -rf, never-started stop, a failedstop --no-backupwithStopVolumePruneErrorand clear state (passes with the CLI-2637 note), no teardown command with clear state (passes), a started stack still listed (fails), services found viastack list(default home, relocated home, sandbox root, ambiguous roots), loop starts, echoed restart,stack restart --stack payments-api, directory removed after destroy, failedstack restart, top-levelsupabase restart, setup-phase stop/start retries, failed legacy teardown afterrm -rf, leftover labelled containers, unreadablestack listoutput,db reset --workdir payments-api, a checkoutdb resetthat doesn't count as a restart, seeding every service viadb resetbefore any change, a seedingdb resetsharing a call with the restart (commands decide) versus in an earlier call (state decides),stop --project-id payments-apirun from another service's directory, a failed versus successful global install, a loop start followed by per-service starts, only loop starts, the beta r1 replay, a brokenconfig.tomlfound viastack list, amarker for payments-apirow, failed starts in the swap rule,--stack checkoutand--stack checkout-service-recoveredstarted in the service directory, dist-tag runners not counted as overrides, no repeated probe commands in the renamed-stack lookup; state evidence: checkout restarted with no restart command, "successful" restart with an older postmaster, payments restarted by state, payments stop command overruled by state,db resetunder state, fallback to commands without timing, ignoring a tool call issue time, setup and restart/touch in one call, ground truth stating both state and a successful restart command)apps/frameworktsc --noEmitpnpm eval:dry -- --suite cli --experiment-suite cli→ planned under all 5 cli experimentspnpm format:checkpnpm check, all steps except the judge smoke step, which needs a provider API key locallyrun-evals-changed: inspect one passing run and each distinct failure shapeorigin/main(feat(core): record working directory and completion time on tool calls #356 included);pnpm checkand the rest re-run after the mergerun-evals-changed): confirm timing evidence and per-callcwdattribution show up, incl.nodaemon, and that renamed stacks resolve🤖 Generated with Claude Code