Skip to content

data/process: degrade gracefully when a workspace lacks a stored ID - #670

Closed
andyrewlee wants to merge 1 commit into
advisor/056-fakeagent-build-cleanupfrom
advisor/fix-storeid-degrade
Closed

andyrewlee wants to merge 1 commit into
advisor/056-fakeagent-build-cleanupfrom
advisor/fix-storeid-degrade

Conversation

@andyrewlee

Copy link
Copy Markdown
Owner

Summary

Fixes a real regression in this stack (introduced by 051's fail-closed durable reservations, made visible by 055's added startup concurrency): a transient store error during workspace load — most measurably the darwin openat-under-os.Root ENOENT race in lockRegistryFile — left the snapshot's workspace with no storeID, and every subsequent spawn failed behind an unactionable "cannot reserve a port range" dialog for the rest of the session.

Three layers, ordered by proximity to the cause:

  • registry_lock_open.go — the spurious-ENOENT retry previously scoped to PortReservationStore.withLock moves into openRegistryLockRootRetry, so every store caller (workspace metadata, project registry, port reservations) rides out the same kernel race. A genuinely deleted state home still fails closed after the bounded budget; unix + windows lockRegistryFile share it; a test seam proves convergence and fail-closed exhaustion.
  • workspace_store_discovery.go — mergeDiscoveryLocked now reflects the resolved store key onto the caller's discovered object on every successful path (in-place merge, vanished-record, and rename branches). Previously the record was saved but the caller's object kept storeID="".
  • process/ports.go — ReserveWorkspace with no StoredID degrades to the root-keyed in-memory allocator (the exact pre-registry contract) with a Warn log instead of returning ErrWorkspaceMetadataNotPersisted. This matches the adoption guard's degrade-not-block posture; LookupWorkspaceInterval reports the transient interval truthfully in the i status. The sentinel error is removed.

Test plan

  • go test ./internal/data ./internal/process -count=1 and -race — green
  • New: TestLockRegistryFileRetriesTransientENOENT, TestLockRegistryFileENOENTBudgetExhausted, TestWorkspaceStore_UpsertFromDiscovery_ReflectsStoreID, TestPortAllocator_DurableUnsavedWorkspaceDegrades, env/session-env degrade cases
  • TestMouseWheelScrollsNormalScreenChatRedrawHistory (the e2e flake that exposed this) — passes; the storeID-wedged dialog no longer appears
  • make lint-strict-new — 0 issues
  • e2e suite in devcheck: all assertions pass; only the known ambient flakes (TempDir teardown, wheel-timing) under host load

Stacked on #669.

A transient store error during LoadMetadataFor/Save — most measurably the
darwin openat-under-os.Root ENOENT race in lockRegistryFile — left the
snapshot's primary checkout with no storeID, and every subsequent spawn
failed behind an unactionable "cannot reserve a port range" dialog for
the rest of the session (seen in the wild as a flaky e2e failure under
host load).

Three layers, ordered by proximity to the cause:

- registry_lock_open.go: the spurious-ENOENT retry previously scoped to
  PortReservationStore.withLock moves into openRegistryLockRootRetry so
  every store caller (workspace metadata, project registry, port
  reservations) rides out the same kernel race; a genuinely deleted state
  home still fails closed after the bounded budget. Both unix and windows
  lockRegistryFile paths share it, and a test seam proves convergence and
  fail-closed exhaustion.
- workspace_store_discovery.go: mergeDiscoveryLocked now reflects the
  resolved store key onto the caller's discovered object on every
  successful path — previously the in-place merge and vanished-record
  branches saved under storedID but left discovered.storeID empty.
- process/ports.go: ReserveWorkspace with no StoredID degrades to the
  root-keyed in-memory allocator (the pre-registry contract) with a Warn
  log instead of returning ErrWorkspaceMetadataNotPersisted — matching the
  adoption guard's degrade-not-block posture; LookupWorkspaceInterval
  reports the transient interval truthfully. The sentinel is removed.

Gates: internal/data + internal/process green incl. -race; full e2e
suite passes (the storeID-wedged dialog no longer appears); devcheck unit
sweep green; strict lint 0 issues.
@andyrewlee

Copy link
Copy Markdown
Owner Author

Superseded — content landed on main via merge 8580442 (plans/ scaffolding intentionally excluded).

@andyrewlee andyrewlee closed this Sep 28, 2026
@andyrewlee
andyrewlee deleted the advisor/fix-storeid-degrade branch September 28, 2026 07:22
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.

1 participant