Skip to content

cellstore: a folder that does not exist yet keeps one spelling through linked folders - #2037

Closed
AbirAbbas wants to merge 3 commits into
app/v3from
fix/cellstore-absent-tree-alias
Closed

AbirAbbas wants to merge 3 commits into
app/v3from
fix/cellstore-absent-tree-alias

Conversation

@AbirAbbas

Copy link
Copy Markdown
Collaborator

Found while running the engine suite on macOS (Apple M4 Pro, macOS 15.6), where /tmp and /var are links into /private.

What was wrong

canonicalTree (internal/cellstore/engine.go) resolved a tree only when it already existed. For a folder that did not exist yet it returned the spelled path, so the same folder changed spelling the moment it was created whenever an ancestor was a link (macOS /tmp and /var, or a linked home or project folder):

  • Taking a chat into a folder created on arrival. Engine.Follow (used by syncsetup take, the desktop's Continue here, and by handoff) computes the target tree before the workspace exists and writes that spelling into engine-tree. The next save's bindTarget then reads the resolved spelling, sees a different tree and runs a second rebind.
  • A chat reached through a link that saves only its own folder called its own not-yet-created work/ folder foreign (ForeignFolder compared the resolved chat root with the spelled work/), so the chat's own folder would be sealed instead of its working folder.

Mac evidence: two of the engine tests that fail on macOS at app/v3 12dadd64a are exactly these paths — TestFollowLongWorkspaceW4 (Follow registers /var/…/work, the dirty plan then runs on /private/var/…/work) and TestAHomeChatNeverCapturesTheSendersHomeOnAnotherMachine ("the chat's own work/ is called foreign"). Both pass with this change.

What changes

  • A missing tree resolves its longest existing ancestor and keeps the missing rest as written, so a folder has one spelling before and after it is created.
  • Only components that are truly absent (Lstat says so) are kept as written. A link whose target is missing is not a missing folder: a path through it keeps its absolute spelling, as before, so a dangling work/ link named through an alias stays foreign. Any other error (permission, a path through a file) also keeps the absolute spelling.
  • The missing suffix is collected in one pass and the ancestor resolved once.

Callers checked: tree, ForeignFolder, holdsHome, policyDir, target, materialize, socketFor (Unix and Windows) and Windows composedPath. Existing folders resolve exactly as before; only absent ones under a link change spelling, and bindTarget's existing rebind keeps any older recorded spelling working.

Tests

New, in internal/cellstore/canonical_tree_test.go (each makes its own link, so they mean the same on Linux):

  • TestCanonicalTreeResolvesAncestorOfMissingWorkspace — same spelling before and after creation.
  • TestCanonicalTreeKeepsAPathThroughAFile
  • TestForeignFolderKeepsMissingWorkUnderSymlinkedChat — own absent work/ is the chat's; an outside folder stays foreign.
  • TestCanonicalTreeDoesNotResolvePastADanglingLink

The first three fail on app/v3 and pass here.

Gates (on the Mac)

  • gofmt; go vet ./internal/cellstore for darwin, linux and windows; CGO_ENABLED=0 go build ./cmd/codeaf for darwin/arm64, linux/amd64 and windows/amd64.
  • New tests plus TestFollowLongWorkspaceW4 and TestAHomeChatNeverCapturesTheSendersHomeOnAnotherMachine: pass (both failed at 12dadd64a on the Mac).
  • Whole internal/cellstore on the Mac: 4 failures, all already failing at 12dadd64a on the Mac (socket and TMPDIR fixtures); the 2 above now pass.
  • Mac gate, full go test -count=1 -p 6 ./... compared by test name with the 12dadd64a Mac baseline: running (results will be added here).
  • CI, judged by content against app/v3 12dadd64a: PR gate 40933976895 fails exactly the 5 tests app/v3's PR gate 40925411112 fails (commentlaw, guard, session ×2, taxonomy); Full check 40933980773 fails 27 tests, all among app/v3's 28 (40925415150). No new failures.
  • The branch is on 12dadd64a; git merge-tree onto the new app/v3 (1c4ec8a25, pair: merge a computer's own chats when it joins, instead of replacing them #2031) is clean and pair: merge a computer's own chats when it joins, instead of replacing them #2031 touches none of these files.

Codex review (adversarial, read-only)

  • Round 1 (NOT SAFE): a dangling link was resolved past as if it were a missing folder → fixed (Lstat walk + test); repeated prefix walks → fixed (one pass); an unnecessary Windows skip → removed; pr: 0 in the change entry → kept, see below.
  • Round 2 (SAFE WITH FIXES): every fix accepted; the only remaining item is pr: 0.

The change entry follows app/v3's convention (docs/changes/unreleased/0-app-<slug>.md, pr: 0, as in #2014–#2016). codeaf-changes check reports "pr is missing" for every such entry on app/v3 (249 at the base); the number can be filled when app/v3's entries are rolled up.

Manual verification

Not reachable live here: the take path needs two paired computers (no second machine; the production relay is not used for tests). Covered instead by TestFollowLongWorkspaceW4, which drives Engine.Follow → Dirty exactly as a take does, and the ForeignFolder tests.

🤖 Generated with Claude Code

AbirAbbas and others added 3 commits October 29, 2026 19:16
canonicalTree returned the spelled path whenever the leaf did not exist,
so a workspace under a linked ancestor (macOS /tmp and /var, a linked home
or project folder) changed spelling the moment it was created. Follow then
recorded one tree and the next save rebound to the other, and ForeignFolder
called a linked chat's own absent work/ foreign. Resolve the longest existing
ancestor and keep the missing rest as written; other errors keep the
absolute spelling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review fixes: only components that are truly absent (Lstat says so) are kept
as written; a link whose target is missing keeps the absolute spelling, so a
chat's dangling work/ link named through an alias stays foreign. The missing
suffix is collected in one pass and the existing ancestor resolved once. The
through-a-file test runs on Windows too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@AbirAbbas
AbirAbbas force-pushed the fix/cellstore-absent-tree-alias branch from 42e85ec to ce1ad45 Compare October 29, 2026 23:17
@AbirAbbas

Copy link
Copy Markdown
Collaborator Author

Closing unmerged: the Mac push stopped early to save usage. What this fixes and what is left is tracked in #2053. The branch fix/cellstore-absent-tree-alias is kept.

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