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>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user