correct two claims about agents that the SDK disproves

docs/per-user-linux-accounts.md carried the reasoning that per-user agents were
a large piece of work, and both halves of that reasoning were wrong.

The SDK does have somewhere to put a uid — spawnClaudeCodeProcess, documented
for running Claude Code in VMs and containers — so a member's turn does not have
to become its own process. And the credential claim was backwards: the proxy
holds the OWNER'S credential, reading the owner's own ~/.claude/.credentials.json,
so pointing a member at it spends the owner's account on the member's turns.
The previous handoff had already retracted that one; the doc had not caught up,
which is how a retracted claim stays live.

Corrected in place rather than deleted, with what was believed and why it was
wrong, because the superseded version is the interesting part: the first claim
is what made agents look like a later stage than they are.

Adds the constraint that actually is out of scope, which the old text never
stated: no platform process ever runs as a member, because the sidecar holds
POSTGRES_URL and the JWT secret.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 21:33:19 +00:00
co-authored by Claude Opus 5
parent 701933d30d
commit ed52faae02
2 changed files with 31 additions and 11 deletions
@@ -38,11 +38,15 @@ them. Installed SDK is **0.2.59**; `package.json:31` pins `^0.2.41`.
the SDK also needs to reach the spawned process in a way `setpriv` breaks, this whole approach is wrong and
the fallback is the earlier plan (member turn as its own process).
Second stale claim, already corrected once: `docs/per-user-linux-accounts.md:230` still says a member's
`claude` "needs only `ANTHROPIC_BASE_URL` pointed at the proxy and no key of its own." The previous handoff
§5 retracted that. **Both passages need editing; neither has been edited.** Left alone deliberately so this
document is the record of what was believed when the code was written, rather than the doc silently agreeing
with the code.
Second stale claim, already retracted once by the previous handoff §5:
`docs/per-user-linux-accounts.md:230` said a member's `claude` "needs only `ANTHROPIC_BASE_URL` pointed at the
proxy and no key of its own." That is backwards — the proxy holds the **owner's** credential
(`sidecar/claude/proxy.ts:7` reads the owner's own `~/.claude/.credentials.json`), so pointing a member at it
spends the owner's account on their turns.
**Both passages have now been corrected in `docs/per-user-linux-accounts.md`** (same commit range as the code).
The superseded text is in that file's git history. If you are reading this handoff against an older checkout,
the doc is the one that moved.
---
+22 -6
View File
@@ -223,12 +223,28 @@ crossed the ancestor that mattered.
- **This is not a sandbox.** A member with a shell is on the machine. They cannot read the owner's files
or another member's, and `sudo` is not theirs — but they can run code, see process names, and reach the
network. It isolates members from each other and from accidents, not from the host.
- **Agents come later.** `claude-manager.ts` drives turns through `query()` from
`@anthropic-ai/claude-agent-sdk`, which spawns `claude` itself and takes `env`/`cwd` but has nowhere to
put a uid. Dropping privileges has to happen *outside* the SDK, which makes a member's turn its own
process — a change of shape rather than a flag. The credential is not the problem:
`officer-anthropic-proxy` already holds it, so a member's `claude` needs only `ANTHROPIC_BASE_URL`
pointed at the proxy and no key of its own.
- **Agents come later** — but by less than this said. Two claims here were wrong and are corrected on
2026-08-11; the superseded text is in the git history of this file, and the working state is
`COMMS/sidecar-app-store/2026-08-11-per-user-claude-handoff.md`.
It said the SDK "has nowhere to put a uid", so dropping privileges had to happen *outside* it, making a
member's turn its own process — "a change of shape rather than a flag". It is a flag: `sdk.d.ts:951`
exposes `spawnClaudeCodeProcess`, documented for running Claude Code "in VMs, containers, or remote
environments", and `node:child_process.spawn` already satisfies the `SpawnedProcess` shape it wants. So the
existing sidecar wraps the CLI spawn in `runAsArgv` per turn and there is no second process to stand up.
It also said the credential is not the problem because `officer-anthropic-proxy` already holds it, so a
member's `claude` "needs only `ANTHROPIC_BASE_URL` pointed at the proxy". That is backwards. The proxy holds
the **owner's** credential (`sidecar/claude/proxy.ts:7` reads the owner's own
`~/.claude/.credentials.json`), so pointing a member at it spends the owner's account on the member's
turns. Per-user Claude means their own login in their own home, and `setpriv --reset-env` is what makes that
the default rather than something to remember: nothing crosses into their process unless it is written into
the argv.
What does remain out of scope: **no platform process ever runs as a member.** The agent sidecar needs
`POSTGRES_URL` and the JWT signing secret, so a member-uid process holding them could read every account and
sign a token as the owner — more than their shell can do, and already refused by `assertSecretsClosed`. The
harness stays the service user's; only `claude` itself drops privileges.
- **`pty`, `vault` and `opencode` receive no identity at all** (`TODO.md` → Multi-user). pty keys purely
on a `sessionId` from the query string, and its `/_officer/sessions` endpoints list and kill *every*
session on the box. Safe today only because terminal is owner-only. **The moment a member has a shell