Skip to content

cellstore: each codeaf home gets its own private saving daemon - #2017

Merged
AbirAbbas merged 3 commits into
app/v3from
fix/app-home-workspace-seal
Oct 28, 2026
Merged

AbirAbbas merged 3 commits into
app/v3from
fix/app-home-workspace-seal

Conversation

@AbirAbbas

@AbirAbbas AbirAbbas commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Why

Found while building a real app with the desktop app on Windows. Every engine started furrow serve --socket <per-user dir>/e-<build hash>.sock, and that name doesn't depend on the home. So two engines with different homes (desktop and terminal, or two homes) bound one socket and broke each other's seals ("use of closed network connection").

The same run found a crew whose workspace was the home folder; saved history then walked the whole user profile. #2009 has since landed its own home-folder policy upstream. This PR drops its earlier version of that and keeps only two gaps the policy still had.

Change

One saving daemon per home.

  • The socket address now carries the absolute home and the build. Clients of the same home share one daemon; different homes never collide.
  • Stale e-*.sock endpoints are pruned only while holding that daemon's lifetime lock, inside the existing per-socket start gate.
  • The client already dials once per operation, so the next seal recovers after a dropped connection. A lost answer is never replayed.

The address stays under the AF_UNIX limit:

  • Unix: a home whose own address fits keeps it, the same file earlier engines used. A long home keeps the short temporary alias earlier engines used, as long as it is a private directory owned by this user (current uid, 0700, no symlinks). An upgraded engine therefore shares the daemon an older one started. Only a preclaimed or foreign alias falls back, to $XDG_RUNTIME_DIR and then to other validated private temporary directories.
  • Windows: falls back only to the user's own temp folder, with a private ACL. If no private endpoint fits within the limit, saved file history turns off with one sentence instead of every seal failing.

Two holes in the home-folder policy are closed:

  • With HOME unset, the check falls back to the login home.
  • With a symlinked home, the ancestors of the original path are now covered, not only those of the resolved target.

Verified

  • Linux: the whole internal/cellstore package passes with the pinned furrow fixture. That includes a regression that starts a daemon on the old long-home address, upgrades the client, and checks that both seals reach the same daemon PID. go vet (Linux and GOOS=windows) and gofmt are clean.
  • Native Windows (cellstore.test.exe, real furrow): 171 tests passed, 12 skipped (Unix-only fixtures). That covers the home/build socket identity, the user-temp-only fallback, a deep LOCALAPPDATA, case and trailing-separator homes, two homes sealing concurrently and restarting, pruning only dead sockets, recovery after an interrupted answer, and the no-private-socket fallback.
    • 3 tests failed, and they fail identically on an app/v3 build of the package: TestGoldenWALRecords (relative testdata), TestL2OnlyNewTurnConstructsATurn (temp-dir access) and TestComposedDirIsNamedOnlyWhenTheTreeIsAWorkspace (\\?\ prefix). Those come from running the binary outside the package directory, not from this change.
  • Adversarial reviews covered symlinked-home ancestors, Windows socket length, missing HOME, Unix socket preclaim/impersonation, the permanent Windows failure, a drive-root fallback, a test that could pass vacuously, and upgrade sharing. Their findings are fixed and covered by tests.

Not run here: the macOS runtime.


🤖 Generated with Claude Code

AbirAbbas added a commit that referenced this pull request Oct 28, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@AbirAbbas
AbirAbbas force-pushed the fix/app-home-workspace-seal branch from 3523de9 to fcb49f0 Compare October 28, 2026 20:18
Windows started every engine's furrow daemon on a socket named only by the
build, so two engines with different homes (desktop and terminal, or two
homes) bound the same endpoint and broke each other's seals ("use of closed
network connection"). The socket now carries the home and the build: same-
home clients still share one daemon, different homes never collide, and the
address stays under the AF_UNIX limit, falling back only to the user's own
temp folder with a private ACL. With no private bounded endpoint, saved file
history turns off with one plain sentence instead of failing every seal.

On Unix a long home used a predictable /tmp socket that nobody checked.
Short endpoints now prefer $XDG_RUNTIME_DIR, then the in-home path, then a
validated private temp directory (current uid, 0700, no symlinks, socket
owned by the user), and preclaimed or foreign endpoints are skipped before
dialing or pruning. Stale endpoints are pruned only under the daemon's start
lock, and the next seal still re-dials after a dropped connection.

Two gaps in the home-folder policy are closed: an unset HOME falls back to
the login home, and the ancestors of a symlinked home's original path count,
so neither can make a chat walk the profile.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@AbirAbbas
AbirAbbas force-pushed the fix/app-home-workspace-seal branch from 4e08732 to 76625b4 Compare October 28, 2026 20:31
…s used

The Unix selector preferred $XDG_RUNTIME_DIR, so after an upgrade a home
whose daemon an older engine had started got a second daemon on a new
address for the same store. A home whose own address fits keeps it again;
a long home keeps the short alias earlier engines used while it is a
private directory of this user, and only a preclaimed alias falls to the
private runtime directory and then other private candidates. A regression
starts the old daemon, upgrades the client and checks both seals share one
daemon.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@AbirAbbas
AbirAbbas force-pushed the fix/app-home-workspace-seal branch from 76625b4 to 0243b47 Compare October 28, 2026 20:33
@AbirAbbas AbirAbbas changed the title cellstore: a home-folder chat skips saved history; each home gets its own saving daemon cellstore: each codeaf home gets its own private saving daemon Oct 28, 2026
The Unix short alias hashed the home as spelled, so a long home reached
through a relative path or a link got its own daemon beside the one its
absolute spelling started. It is keyed by the canonical home now, which
for an absolute home without links is the key earlier engines used. The
tests that need a real engine skip, like the package's others, when the
build has none.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@AbirAbbas
AbirAbbas merged commit 12dadd6 into app/v3 Oct 28, 2026
24 of 34 checks passed
@AbirAbbas
AbirAbbas deleted the fix/app-home-workspace-seal branch October 28, 2026 21:43
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