Files
platform/COMMS/sidecar-app-store/29-history-layer.md
T
pastilhasandClaude Opus 5 95951fbe5a resolve transcripts and cwd against the caller's home, not the owner's
The history layer, and the last change that could be made without a live member.

claude-sessions.ts had `claudeHome = process.env.HOME_DIR ?? join(DATA_PATH,
email, 'home')`, which discards its argument whenever HOME_DIR is set — always,
on a real install. Every transcript read therefore resolved to the OWNER'S
~/.claude no matter who asked, and the comment above it asserted "single-user
platform" as though that were a property rather than an assumption. A member
reaching these functions would have been handed the owner's conversation list.

Now every read takes a ChatIdentity {email, home} with the home resolved from
resolveHomeDir(userId), and this file has no way to invent one. Both fields
travel together because they are genuinely different: general_chat_sessions
lives under DATA_PATH/<email>, not under a home. Collapsing them would be the
same class of mistake as undefined meaning "the owner".

websocket.ts's resolveCwd takes a home, so `~` expands against the caller's own.
Identity is resolved BEFORE the cwd — expanding `~` before knowing whose home it
is would be exactly the bug being removed — which also let a duplicate
resolveTurnIdentity call from 6aeb304 be deleted.

chat.ts resolves per request and throws FORBIDDEN rather than falling back, same
posture as resolveTurnIdentity. agent-runner passes the owner's home explicitly
rather than inheriting it, since that path really is owner-only.

Made at 01:00 after saying it should not be. Three things I am least sure of are
listed in COMMS 29 rather than left for the reviewer to find: chat routes now
have a failure mode they did not have, resolveBaseCwd's exported parameter
changed meaning rather than shape, and the bare-email rewrite in chat.ts was
mechanical with hand repair.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 00:48:09 +00:00

3.2 KiB

29 — the history layer is off getOwnerHomeDir

The owner asked me to carry on, so 27's "tomorrow" became tonight. This is the change I said I would not make at this hour, made at this hour — review it accordingly, and I would rather you were slow about it than quick.

What moved

claude-sessions.ts no longer knows how to invent a home. claudeHome(email) is gone. Every transcript read now takes a ChatIdentity { email, home }, and the home arrives resolved from resolveHomeDir(userId).

The old line was process.env.HOME_DIR ?? join(DATA_PATH, email, 'home') — which discards its argument whenever HOME_DIR is set, i.e. always on a real install. So every read resolved to the owner's ~/.claude regardless of who asked, and the comment above it asserted "single-user platform" as though that were a property rather than an assumption.

Both halves travel together rather than one standing in for the other, because they are genuinely different: general_chat_sessions lives under DATA_PATH/<email>, not under a home, so those paths stay email-derived. Collapsing them would have been the same class of mistake as undefined meaning "the owner".

chat.ts resolves the identity per request (chatIdentity), and throws FORBIDDEN rather than falling back — same posture as resolveTurnIdentity, for the same reason.

websocket.ts's resolveCwd takes a home rather than an email, so ~ expands against the caller's own home. Its old comment — "the server owner is the only account, absolute paths are theirs to use" — is replaced with what is actually true now: an absolute path still passes through, and it is the kernel that decides what a member's turn can open, not a string check here.

Ordering worth checking: identity is resolved before the cwd, because expanding ~ before knowing whose home it is would be exactly the bug this removes. That reordering also let me delete a second resolveTurnIdentity call that 25 had left further down the same function.

agent-runner.ts passes { email, home: homeDir } explicitly — it is an owner-only path, and I would rather it state that than inherit it.

What I am least sure of

  1. chatIdentity throws where the old code could not fail. Every /chat route now has a failure mode it did not have. Owner-only today, and resolveHomeDir returns isOwner from a positive check — but it is the kind of change that turns a database blip into a 403 on a screen that used to always load.
  2. resolveBaseCwd is exported. I changed its parameter from email to home and followed the compiler; if anything outside this diff passes an email, tsgo would have caught it, but the meaning of a wrong-but- well-typed string would not be caught by anything.
  3. The regex pass. I rewrote bare email references inside chat.ts mechanically and then repaired the damage by hand. tsgo is clean and 78 tests pass, but that is a weaker guarantee than usual for this file.

tsgo clean, 78 tests, both gates up, member unreachable in production.

What is left

Nothing I can write blind. deprovisionOsAccount is still mine and still tomorrow's. A member signing in, the first member turn, and the gates are the owner's.