Uh oh!
There was an error while loading. Please reload this page.
feat(stack): isolate sibling worktrees and named stacks (CLI-2107) - #6171
Conversation
Extend the managed layer so linked worktrees, ordinary Git checkouts,
bare-repository worktrees, and named stacks coexist without sharing
mutable runtime state.
- Git workspace inspection (managed/git.ts): classifies primary
checkouts, linked worktrees, bare-repo worktrees, and ordinary
folders by parsing Git metadata files directly; read-only and
side-effect free. Reftable-backend repos are refused explicitly.
- Git identity storage: project ID in common-scope git config
(supabase.projectId), checkout ID in <gitDirectory>/supabase-checkout.json,
branch context IDs in branch.<name>.supabaseContextId. Fresh clones
receive new IDs for free; git itself carries context IDs across
branch renames. Config claims use append-and-adopt-first with lock
retries so concurrent claimants converge.
- Registry schema v4: project-scoped branch contexts, checkout-scoped
detached/workspace contexts, checkout kinds, one location per
checkout keyed to the checkout root.
- resolveStack({workspacePath, stackName, operation}) is the single
identity/decision path (provisionOrdinaryStack/inspectOrdinaryWorkspace
folded in); status is strictly read-only, start ensures identities
and provisions via the existing pending->publish machinery.
- Enriched projections expose full opaque IDs, checkout kind/path,
context kind/branch, ports, and lifecycle so every sibling instance
is distinguishable; stop/delete stay global-by-opaque-ID.
- CLI-2102 worktree/named-stack/reclamation fixtures are now
behaviorally exercised against both registry adapters with real git
repos, including an end-to-end three-sibling isolation proof.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:cf6784a221
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Supabase CLI previewnpx --yes https://pkg.pr.new/supabase/cli/supabase@91d8f4812ee53751deb73840f2143f48c36a233ePreview package for commit |
…aimant Two concurrent starts on one unclaimed checkout both compared against a pre-claim read, so both reported identityMarkerCreated: true. The claim outcome from claimFileAtomically is now threaded through ensureGitCheckoutIdentity so only the actual winner reports creation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ack identity Round-2 review findings: - configValue now enters only plain [section] headings (a subsection like [core "fake"] no longer leaks into [core]) and applies git's last-value-wins precedence instead of first-match. - The core runtime accepts an opaque instanceId: container names derive from it when present (port-derived otherwise, unchanged), and every container carries a com.supabase.stack-id label for future global reclamation. Managed callers pass the stack UUID here, removing the stale port-derived-name collision across sibling stacks. - architecture.md no longer hardcodes the managed failure-class count. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jgoux
commented
Aug 13, 2026
Round-2 review response (external review, commit 2b660c7): Fixed — git config subsection/precedence parsing (P2). Fixed at the package boundary — runtime stack identity (P1). Fixed — stale docs count (P3). |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:2b660c761f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…pace-error fingerprints Round-3 review batch: - StackConfig.instanceId is validated at config resolution against a Docker-name-safe pattern (managed UUIDs pass) instead of reaching container-name interpolation unchecked. - configValue strips surrounding double quotes (with minimal unescaping) after comment-stripping, so quoted core.bare, extensions.refStorage, and worktreeConfig values read as git does. - UnsupportedGitWorkspaceError carries a closed cause discriminant (inside-git-directory | malformed-metadata | reftable); telemetry derives per-cause fingerprint suffixes instead of collapsing unrelated causes into one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jgoux
commented
Aug 13, 2026
Round-3 review batch (commit 5abc0d5) — three accepted P2 findings, handled as one closed batch:
Also per the review: the bot's This closes the approved batch; no further automated fix rounds planned on this PR. |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:5abc0d51b3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:21d1e05ec1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:b30131fd30
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
avallete
left a comment
There was a problem hiding this comment.
Review (round 4, human-directed)
Overall assessment: the implementation is sound. I verified the isolation design end to end on the branch: workspace classification reads git metadata directly with typed refusals for every malformed shape, identity claims use append-and-adopt-first-value under git's config lock (the collapse provably preserves the first value under every interleaving I could construct), the v4 schema's scoping rules (project-scoped branch contexts, partial-unique checkout-scoped contexts) are enforced in both adapters and exercised by the same contract fixtures, and registerStack's claim creation is correctly inside the uninterruptible mask with interruption re-raised rather than reported as failure. The runtime instanceId seam is byte-identical for callers that omit it, and nothing in the CLI passes it yet, so there is no user-visible behavior change. The three Codex rounds plus the depthfirst findings were all either fixed with regression tests or rejected with sound reasoning — I agree with both rejections (the .git/commondir containment rule would refuse legitimate --separate-git-dir and worktree topologies, and the git branch -c copy case genuinely needs CLI-2108's re-keying semantics).
check:all passes for packages/stack and apps/cli. One finding from running the suite locally:
SIGNIFICANT (DX / test portability) — the reftable fixture requires git ≥ 2.45 with no guard
git.integration.test.ts ("refuses a repository whose refs are stored in a reftable", line ~424) runs git init --ref-format=reftable unconditionally. --ref-format was added in git 2.45; on stock git 2.43 (Ubuntu 24.04 LTS default, and the remote dev containers this repo's sessions run in — where I reproduced it) the helper throws and pnpm test:core in packages/stack is red out of the box: 1 failed | 303 passed. CI is green only because the runners carry a newer git, so the failure lands on exactly the machines contributors use. Given the repo policy that a failing check in a changed workspace belongs to whoever runs it, this test will mislead the next contributor into debugging "their" failure.
Smallest fix: probe once at module scope (attempt git init --ref-format=reftable in a temp dir, or parse git --version ≥ 2.45) and it.skipIf(!supportsReftable) this one test. The two config-declared reftable refusal tests above it don't need a real reftable repo and keep covering the extensions.refStorage path on old git; only the .invalid HEAD-stub tripwire coverage would be conditionally skipped.
NON-BLOCKING OBSERVATIONS
- Registry v4 leaves
registry-v3.sqlite3and its stacks' data roots orphaned on disk. Acknowledged unreleased-POC policy, just noting the stale file is never cleaned up either. - Branch-copy context sharing (
git branch -ccopyingsupabaseContextId) stays deferred to CLI-2108 per the resolved P1 thread; the deferral is documented in the PR description, which is the right place for it.
Verdict: Approve with non-blocking comments — the reftable test guard is worth fixing in this PR since it's a five-line change to a file the PR adds, but CI is green and it doesn't affect shipped behavior.
Generated by Claude Code
avallete
left a comment
There was a problem hiding this comment.
The one finding (SIGNIFICANT, DX / test portability)
Running the suite locally: 1 failed | 303 passed. The new reftable test at packages/stack/src/managed/git.integration.test.ts:424 runs git init --ref-format=reftable unconditionally, but --ref-format needs git ≥ 2.45. This environment has git 2.43 (same as stock Ubuntu 24.04 LTS), so pnpm test:core is red out of the box on common contributor machines — CI passes only because runners ship newer git. Smallest fix: probe reftable support once and it.skipIf that single test; the two config-declared reftable tests above it don't need a real reftable repo and keep their coverage on old git.
I also noted two non-blocking observations in the review: the orphaned registry-v3.sqlite3 file (accepted no-migration POC policy) and the CLI-2108 deferral being properly documented. I posted the review as a COMMENT rather than a formal approval since the author declared the automated fix loop closed — the reftable guard is a five-line change worth making in this PR, but it doesn't block merge.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:91d8f4812e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (OBJECT_ID_PATTERN.test(head)) { | ||
| return { kind: "detached", commit: head }; |
There was a problem hiding this comment.
Verify detached HEAD resolves to a commit
When HEAD contains a syntactically valid 40- or 64-digit hex value that is absent from the object database—or names a non-commit object—this branch reports a valid detached context and allows start to claim identities and register a stack. Git rejects such a checkout; I reproduced the absent-object case with git status exiting 128 with fatal: bad object HEAD. Validate that the object exists and is a commit before returning this detached-head result, otherwise classify it as UnsupportedGitWorkspaceError with malformed-metadata.
Useful? React with 👍 / 👎.
| if (existing.stack.status === "active") { | ||
| if (existing.operation !== undefined) { |
There was a problem hiding this comment.
Recheck tombstones before reporting stack reuse
When a concurrent deleteStack claims and tombstones this stopped stack after prepareStack returned the stale active record but before requireProjection runs, this branch still reports a successful reuse. The returned stack projection is then tombstoned while stacks excludes it, so one resolution contradicts itself and tells the caller that start settled on a deleted stack. Revalidate the projected status or retry registration when the previously active row has become tombstoned.
Useful? React with 👍 / 👎.
Uh oh!
There was an error while loading. Please reload this page.
## Summary - Scope managed stack projection reads to the resolved project, checkout, and context before hydrating port assignments. - Share one pure context-registration decision across the SQLite and in-memory adapters while keeping each adapter transactionally authoritative. - Preserve global listing behavior, deterministic ordering, tombstone handling, and existing context identity semantics. ## Linked issue [CLI-2185](https://linear.app/supabase/issue/CLI-2185/stack-scope-managed-registry-reads-and-centralize-context-policy) ## Reviewer context This builds on the managed-state APIs introduced by supabase#6171, which is now merged. The branch has been rebased onto develop, so the PR diff is limited to the focused CLI-2185 change.
Implements CLI-2107: extends the managed-state baseline (#6152) so linked worktrees, ordinary Git checkouts, bare-repository worktrees, branches, and named stacks coexist without sharing mutable runtime state. All identity and isolation policy lives in
@supabase/stack/managed; no CLI handler changes.Git identity storage
managed/git.ts): classifies primary checkouts, linked worktrees, bare-repo worktrees, and ordinary folders by parsing Git metadata files directly (.gitfile/dir,commondir,HEAD) — read-only inspection performs zero writes and needs nogitbinary. Repositories using the reftable ref backend are refused with a typedUnsupportedGitWorkspaceErrorinstead of silently collapsing branch contexts (theirHEADcompat stub always namesrefs/heads/.invalid).supabase.projectIdin<commonDir>/config), so all linked worktrees — including worktrees of a bare repository with no primary worktree — share it, and a freshgit clonereceives new identity for free (clone copies no config).<gitDir>/supabase-checkout.json), so every worktree is an independent checkout.branch.<name>.supabaseContextId(common scope): worktrees on the same branch share a context,git branch -mcarries the ID with the section rename, and branch deletion orphans the context without touching stack data.git config --add, re-read, adopt the immutable first value, idempotent collapse) with lock-contention retries — a plain set-then-reread protocol demonstrably loses claims under concurrency.Registry schema v4
Branch contexts are project-scoped; detached and workspace contexts are checkout-scoped (partial unique index per checkout). Checkouts record their kind, and each checkout has exactly one canonical location keyed to the checkout root (starting from a nested subdirectory resolves and reuses the same location). The live-stack uniqueness keystone
(checkout_id, context_id, name)is unchanged. The registry file is nowregistry-v4.sqlite3; as with v3, this unreleased POC has no data-migration path.resolveStack
resolveStack({ workspacePath, stackName?, operation: "start" | "status" })is the single identity/decision path —provisionOrdinaryStack/inspectOrdinaryWorkspaceare folded in, not layered over.statusis strictly read-only (no markers, no config keys, no registry rows);startensures identities and provisions through the existing pending→publish/operation-claim machinery. Interrupt-safety around claim creation was tightened so an op-boundary interrupt cannot leak a pending stack with a live operation claim. Stack names are validated as lowercase DNS labels before any write.deleteStack/updateStack/inspectStack/listStacksremain global-by-opaque-ID with no workspace resolution required, and projections now expose full IDs, checkout kind/path, context kind/branch locator, ports, and lifecycle so every sibling instance is deterministically distinguishable.Fixtures made executable
The CLI-2102 worktree, bare-repo, named-stack, stack-name, and reclamation fixtures in
managed-stack-contract.tsare now driven through the real service against both registry adapters using real git repos/worktrees in temp dirs. Symbolic fixture IDs are checked as equality/inequality relations (shared symbol ⇒ same real UUID, distinct symbols ⇒ distinct UUIDs), so editing fixture data changes test behavior. A three-sibling end-to-end proof (two worktrees + one named stack) shows distinct data roots and ports, and that a global delete-by-ID leaves the survivors' records, ports, and data untouched.Reviewer notes
whenactions expressed as CLI argv in fixtures are driven through their managed-API equivalents; CLI command wiring and human/JSON rendering are later issues, per the issue's implementation boundary.identity.valid-stack-names-resolve-deterministicallynames aresolveStackNamesmethod that does not exist; it is honestly expressed as per-name read-onlyresolveStackcalls (zero-write witnessed). A fixture cleanup could rename the method or add the missingstackgiven-facts.🤖 Generated with Claude Code