Skip to content

Made the recipe state truthful: the Explorer and status bar follow config.yaml edits on disk, new-sandbox keys derive from the title, and names claimed by leaked sbx state are remembered and skipped (FR-009, FR-057) - #26

Merged
dzykovic merged 1 commit into
mainfrom
feature/truthful-recipe-state
Aug 5, 2026
Merged

Conversation

@dzykovic

@dzykovic dzykovic commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Risk & Size

Risk: High
Size: Medium

Risk is High by the repo's own classification because src/sbx.ts changed — though the
change there is error typing only (a create failure is now raised as a typed
NameClaimedError); no argument vector, flag, or CLI shape moved, so src/script.ts
needs no sync.

What & Why

Acceptance of spec 013 produced three failures with one root: the recipe and the names
derived from it go stale or collide, and the UI keeps acting on the old value — most
visibly, creating a sandbox could be blocked outright. Ships spec 014 (FR-009 extended,
FR-057):

  • The UI did not notice a recipe edited on disk. The tree resolves each node's key
    (and therefore its sbx name) at render time and refreshed only on focus change, terminal
    events, and lifecycle completion. Renaming a key in .sandbox/config.yaml and clicking
    Connect went out under the old name, which reads as "renaming does not help".
  • New-sandbox keys recycled. Keys came from the agent id (claude, claude-2, …), a
    dense counter over a tiny namespace, so a key freed by a rename or a removal was handed
    straight to the next new sandbox — reissuing a name that was permanently unusable.
  • Nothing remembered an unusable name. When sbx create fails because the name is
    still claimed by leaked sbx runtime state (upstream sbx rm <name> --force reports 'not found' but name remains claimed in meta.db, blocking subsequent sbx run --name <name> docker/sbx-releases#129, no released
    fix — Architecture §14), the extension forgot immediately and could derive it again.

Changes

  • src/extension.ts: a debounced FileSystemWatcher over .sandbox/*.yaml refreshes the
    status bar and fires sandboxConsole.refresh (dropping the tree's cached refs), so an
    edited recipe takes effect at once. Read-only discovery is preserved (FR-002) — the
    watcher observes, it never writes.
  • src/form.ts: a new sandbox's key is seeded from the entered title
    (Backend API (v2)backend-api-v2, capped at 40 chars, agent id as fallback) and
    then frozen — the form still locks it in edit mode, so renaming a title never moves a
    sandbox name, Dockerfile, or image tag. Derivation skips keys already in the recipe and
    keys whose sbx name is recorded unusable; a retry that had to abandon its pinned key
    drops the dead recipe entry instead of leaving a definition that cannot be built.
  • src/names.ts (new): the per-working-copy workspaceState record of unusable sbx names
    — local by construction, names only, capped at 50, irrelevant after sbx reset.
  • src/sbx.ts: the leaked-state 500 is raised as NameClaimedError (so no caller
    re-parses CLI output) and its message now says the name is remembered and skipped.
  • src/ops.ts: records the name in names.ts when a create fails with that error.
  • Docs: Features.md FR-009 (UI follows the recipe) + new FR-057;
    Architecture.md §4 (module), §6 (key derivation + watching), §7/§12 (Title seeds the
    key once), §14 (the remembered-names workaround); CLAUDE.md module map;
    spec 014 - Truthful Recipe State flipped to shipped and moved to docs/specs/completed/.

Deliberate consequence: the default Dockerfile name follows the key, so two sandboxes on
one agent no longer default to a shared claude.Dockerfile and one shared image.
Sharing stays available — typing the same file name in both — and is now explicit.

How to Verify

  1. npm run verify — exits 0.
  2. FR-057 key from title (F5, Extension Development Host): New Sandbox → Title
    Backend API (v2) → Save. .sandbox/config.yaml gets the key backend-api-v2 and the
    sandbox name reads <project>-backend-api-v2-<id>. Create a second one with an empty
    title → key falls back to the agent id (claude).
  3. Key frozen: Edit that sandbox, change the Title, Save. The key, the sbx name, and
    any <key>.Dockerfile are unchanged; only the Explorer label moves.
  4. FR-009 watcher: with the Sandboxes view open, edit .sandbox/config.yaml outside
    the window (or in another editor) — rename a key or add an entry. The tree and the
    status bar update within ~1s without a manual Refresh, and Connect targets the new name.
  5. FR-057 remembered names (only reproducible with a genuinely leaked name; otherwise
    read the code path): a create failing with
    failed to create network: … already exists reports the name as claimed and says it
    is remembered; the next New Sandbox with the same title derives <key>-2 rather than
    the dead name, and retrying the still-open form does not reuse it either.

…nfig.yaml edits on disk, new-sandbox keys derive from the title, and names claimed by leaked sbx state are remembered and skipped (FR-009, FR-057)
Copilot AI lite review requested due to automatic review settings August 4, 2026 23:28

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR makes sandbox naming and recipe-derived state “truthful” by ensuring the Explorer/status bar react promptly to on-disk recipe edits, deriving new sandbox keys from the user’s title (while keeping keys frozen thereafter), and persisting a per-working-copy set of sbx names proven to be permanently uncreatable due to leaked upstream runtime state (FR-009, FR-057).

Changes:

  • Add a debounced .sandbox/*.yaml watcher so UI surfaces refresh immediately after recipe/identity edits on disk.
  • Seed new sandbox key from the Title (sanitized + capped), keep it frozen after creation, and avoid generating keys that would map to locally-recorded unusable sbx names.
  • Introduce a typed NameClaimedError and a workspaceState-backed unusable-name record, written when a create fails with the leaked-state error and consulted during key derivation.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/sbx.ts Adds NameClaimedError and centralizes “name claimed by leaked state” detection into a typed failure.
src/ops.ts Records unusable sbx names when creates fail with NameClaimedError.
src/names.ts New workspaceState store for unusable sbx names (bounded list), queried by key derivation.
src/form.ts Derives new keys from Title once (fallback to agent), skips unusable-name candidates, and drops abandoned pinned entries on retry.
src/extension.ts Initializes names.ts and adds a debounced filesystem watcher to refresh UI on .sandbox/*.yaml edits.
docs/specs/completed/014 - Truthful Recipe State.md Adds shipped iteration spec documenting the behavior changes and rationale.
docs/Features.md Updates FR-009 behavior and adds FR-057 requirements.
docs/Architecture.md Updates module map/dependencies and documents watcher + key derivation + unusable-name workaround.
CLAUDE.md Updates module map/dependency direction to include names.ts.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@dzykovic
dzykovic merged commit 7087ed5 into main Aug 5, 2026
2 checks passed
@dzykovic
dzykovic deleted the feature/truthful-recipe-state branch August 5, 2026 09:10
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