Skip to content

feat(evals): add build-cli-004-worktree-stacks (CLI-2400) - #307

Open
kanadgupta wants to merge 4 commits into
mainfrom
kanad-claude/cli-2400-worktree-stacks-cli-suite
Open

kanadgupta wants to merge 4 commits into
mainfrom
kanad-claude/cli-2400-worktree-stacks-cli-suite

Conversation

@kanadgupta

Copy link
Copy Markdown
Member

Tracks CLI-2400 under the Slim CLI evals RFC. Supersedes #295.

Note

Stacked on #281 and uses its cli suite and needsDocker field. The diff shows Colum's commits until #281 lands; this PR's own change is the single evals/cli/build-cli-004-worktree-stacks/ folder.

The problem it's solving

The headline agent workflow for the Slim CLI launch is one human plus N coding agents, each working in its own git worktree, each needing its own isolated local Supabase stack. Nothing in this repo measured whether an agent can get there: three worktrees, three stacks, zero leakage.

What the PR adds

One new eval in the cli suite, evals/cli/build-cli-004-worktree-stacks/. Starting from an empty sandbox, the agent is asked to set up a repo with worktrees feature-a, feature-b, feature-c, start a local stack in each, and add a different table (widgets / gadgets / gizmos) plus one sample row per worktree. No seed data, no framework changes, no experiment edits: needsDocker: false puts it on the Docker-less arms, the interface rule puts it on the stable and beta arms, and the cli suite puts it on the pinned Codex Luna baseline.

A scorer that grades the end state, never the method. Because the harness's built-in ctx.query / stackStatus resolve a single stack from the workspace root, every check addresses stacks itself by cd-ing into each worktree:

  • three real git worktrees on distinct branches, discovered by finding a repo in the workspace and asking git worktree list --porcelain (so worktrees placed outside the workspace still count; three plain directories don't);
  • one live stack per worktree with three distinct database endpoints, which is what catches aliasing or reuse;
  • per-table schema isolation via to_regclass against all three stacks;
  • at least one row per table in its home stack;
  • each table created by a migration file in its worktree;
  • an always-passing metrics check reporting fleet wall-clock across the three stacks, CLI version and channel, per-stack backend and runtime, and the number of supabase start invocations.

Stack resolution asks the managed backend first (SUPABASE_EXPERIMENTAL_STACK=1 supabase stack status --env --output-format json) and falls back to the legacy one, the same shape #281 landed on. Environment-agnostic: the scorer never branches on which arm it runs under.

Deliberate choices

  • Nothing tells the agent about the managed stack or its feature flag. Worktree isolation with no config edits only exists on the managed stack (beta channel, gated behind experimental.stack). Whether agents can discover that is part of what's measured.
  • services: is omitted, for the reason feat(evals): add build-database-002-stack-lifecycle CLI eval (CLI-2398) #281 documents: the sandbox shim's legacy -x gotrue,kong,... is rejected by the managed backend.
  • Known footgun left in. supabase init pins ports in config.toml; the managed stack treats them as exact intents, so the second worktree's start fails with Persisted database port is unavailable until the ports are removed. That is the "no port surgery" promise the launch makes.

What the first CI run showed (#295, 18 runs across six experiments)

Two passes, both on the legacy backend, each with a hand-edited project_id and a full set of shifted ports per worktree. That is real signal about what agents do today, and the metrics check records the backend so it stays distinguishable from a managed-stack pass.

The 16 failures were two harness defects, both fixed here:

  • 12 agents stopped within ~10 s to ask for a repository. The prompt said "set this repo up" against an empty workspace. It now says the project is brand-new, following the wording of build-database-002-stack-lifecycle, and asks for the worktrees as folders in this directory, following deploy-self-hosting-001's location hint.
  • 2 agents built everything correctly with worktrees at /tmp/feature-* and the scorer's name-based search under the workspace missed them. Discovery now goes through git.

Verification

Verified by hand against CLI 2.118.0-beta.37 on the native runtime: three sibling worktrees get three distinct stack identities with no config edits, and a table created in one is absent from the others.

  • Scorer unit tests (scoring.test.ts, 28 cases; fixtures captured from real beta CLI output)
  • pnpm typecheck, Biome
  • pnpm eval:dry -- --suite cli --experiment-suite cli → 5 experiments × 2 evals
  • Real scorer executed against three hand-built native stacks on the beta CLI, with worktrees both inside and outside the workspace: all checks pass; stopping a stack or leaking a table fails the right checks
  • Agent-backed runs via the run-evals-changed label on this PR (no Anthropic or OpenAI keys locally)

🤖 Generated with Claude Code

@kanadgupta
kanadgupta requested a review from a team as a code owner September 17, 2026 20:31
@kanadgupta kanadgupta added the run-evals-changed Add to a PR to refresh only the benchmark evals that have had changes label Sep 17, 2026
@vercel

vercel Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
evals Ready Ready Preview Sep 24, 2026 5:36pm UTC

Request Review

@kanadgupta
kanadgupta changed the base branch from main to columferry/cli-2398-add-a-docker-less-local-stack-e2e-eval-agent-cli-lifecycle September 17, 2026 21:02
@kanadgupta kanadgupta added run-evals-changed Add to a PR to refresh only the benchmark evals that have had changes and removed run-evals-changed Add to a PR to refresh only the benchmark evals that have had changes labels Sep 17, 2026
@Coly010
Coly010 force-pushed the columferry/cli-2398-add-a-docker-less-local-stack-e2e-eval-agent-cli-lifecycle branch from f4af609 to 9e35200 Compare September 18, 2026 08:24
@Coly010
Coly010 force-pushed the kanad-claude/cli-2400-worktree-stacks-cli-suite branch from c883a62 to 3158ffd Compare September 18, 2026 08:35
@Coly010

Coly010 commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Heads-up: I rebased this branch (kanad-claude/cli-2400-worktree-stacks-cli-suite) with --force-with-lease and it now carries only your two commits (f3d48c7 eval, 3158ffd CI results) on top of #281's current head.

Why: #281 was split into two PRs this morning — #308 (the cli suite, needsDocker, experiments/_lib runtime and the -cli-* experiments) and #281 (the stack-lifecycle eval only, stacked on #308) — which rewrote the history this branch was built on, so the PR had started showing my superseded commits in its diff. Nothing in your commits changed; pnpm check, the scorer tests for both CLI evals (90) and pnpm eval:dry -- --suite cli --experiment-suite cli (5 experiments × 2 evals) all pass on the rebased branch.

Nothing you need to do. Your eval is already in evals/cli/ with needsDocker: false, which is exactly how the Docker-less arms now select evals (skipUnlessDockerless in experiments/_lib), so it runs under all five arms without the experiment allow-list edits from #295. Merge order is #308 → #281 → this.

Two things from the review round on #308 that touch your runs, for awareness only:

  • The first CI run of the cli suite (35274398896, the one that produced your cli-eval-results.json) lost every beta-channel pair because npm's beta tag pointed at 2.118.0-beta.52 while its GitHub release was still a draft (.deb 404). The resolver now verifies the asset and falls back to the newest published beta, and the workflow pins one version per run — so the next refresh should fill in the -cli-beta/-cli-nodaemon/-cli-absent rows.
  • The Docker-less arms no longer mount the Docker socket at all (mountDockerSocket: false) in addition to the DOCKER_HOST + shim layering.

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
kanadgupta and others added 2 commits September 23, 2026 15:33
…2400)

One human plus N coding agents, each in its own git worktree, is the
headline workflow for the Slim CLI launch (FDBKIN-20391). Each worktree
needs its own isolated local Supabase stack with zero leakage between
them. Nothing in this repo measured that.

Scenario: starting from an empty sandbox, the agent creates a repo with
worktrees feature-a/b/c, starts a local stack in each, and adds a
different table (widgets/gadgets/gizmos) plus one seed row per worktree.
No seed data and no framework changes: the agent builds everything,
including the worktrees.

Scorer (end state only, all via ctx.exec because the harness's built-in
ctx.query/stackStatus resolve a single stack from the workspace root):

- three real git worktrees on distinct branches, discovered by finding a
  repo in the workspace and asking `git worktree list --porcelain`, so
  worktrees the agent placed outside the workspace still count and three
  plain directories don't;
- one live stack per worktree with three distinct host:port database
  endpoints, which is what catches aliasing/reuse;
- per-table schema isolation via to_regclass against all three stacks;
- at least one row per table in its home stack;
- each table created by a migration file in its worktree;
- an always-passing metrics check reporting fleet wall-clock across the
  three stacks, CLI version/channel, per-stack backend and runtime, and
  the number of `supabase start` invocations.

Stack resolution asks the managed backend first
(SUPABASE_EXPERIMENTAL_STACK=1 supabase stack status --env) and falls back
to the legacy one, the same shape as build-database-002-stack-lifecycle.
Verified against CLI 2.118.0-beta.37: the managed backend rejects the
legacy -o flag, the DB password is random per stack, and the legacy
backend cannot see managed stacks.

Suite placement follows #281: `evals/cli/` with `needsDocker: false`, so
the eval runs on the pinned Codex Luna baseline plus the four
`codex-gpt-5.6-luna-cli-*` arms with no experiment edits. `services:` is
omitted because the sandbox shim's legacy `-x` names are rejected by the
managed backend.

Two details come from the first CI run of an earlier draft (#295, 18
runs across six experiments):

- The prompt now says the project is brand-new and asks for the worktrees
  as folders in this directory. The earlier "set this repo up" made 12 of
  18 agents look for a repository in the empty workspace and stop to ask
  for one within about ten seconds.
- Worktree discovery goes through git rather than a name search under the
  workspace. Two agents built everything correctly with worktrees at
  /tmp/feature-*, and the name search reported them as missing.

The two runs that passed did so on the legacy backend by hand-editing
project_id and every port per worktree; the metrics check records the
backend so that path stays distinguishable from a managed-stack pass.

Pure helpers are unit-tested in scoring.test.ts (run hint at the top of
the file); the shapes in the fixtures were captured from real beta CLI
output. The full scorer was also run against three hand-built native
stacks on the beta CLI, with worktrees both inside and outside the
workspace, and passes all checks.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…entMarker()

Replace the local /tmp/supabase-eval-runtime.json reader with the
scoring context's environmentMarker(), which reads as root and
validates the marker's shape. Update the README's stale
codex-gpt-5.6-luna-cli-{stable,beta,nodaemon,absent} arm names to the
current five codex-gpt-6-luna-cli-{pinned,stable,beta,nodaemon,absent}
environments, and correct the claim that pinned is a separate shared
baseline rather than one of the five. Drop leftover references to the
removed experiments/_lib/docker-aware-local-stack.ts helper.
@Coly010
Coly010 force-pushed the columferry/cli-2398-add-a-docker-less-local-stack-e2e-eval-agent-cli-lifecycle branch from bdc011c to c3c6459 Compare September 23, 2026 15:05
@Coly010
Coly010 force-pushed the kanad-claude/cli-2400-worktree-stacks-cli-suite branch from 1974f30 to 365772b Compare September 23, 2026 15:05
@Coly010
Coly010 changed the base branch from columferry/cli-2398-add-a-docker-less-local-stack-e2e-eval-agent-cli-lifecycle to main September 23, 2026 15:05

This branch was successfully deployed

1 active deployment
Preview — d2d40625 Deployed Sep 24, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run-evals-changed Add to a PR to refresh only the benchmark evals that have had changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants