The privilege drop and the allowlist both held in production. Captured from /proc during the first real member chat turn: uid 1001, parent sudo, HOME and CLAUDE_CONFIG_DIR both inside the member's home, and exactly nine environment variables — the allowlist plus what setpriv supplies. Zero ANTHROPIC_*, POSTGRES_URL or JWT_SECRET. The defect is the cwd. websocket.ts:134 defaults a chat turn to the email-derived general_chat_sessions, and confineUserTree makes every sibling of home the platform's at 0700. So the member's turn starts in a directory it cannot enter — verified, cd fails — and every Bash call in that turn dies instantly, which is what the owner saw as "the shell is unusable". 29 reasoned that those paths stay email-derived because they live under DATA_PATH rather than a home. That is true and it does not follow that they are usable: platform-owned by design means a member's turn can never run there. Fix is a member-owned default, and I would put general_chat_sessions inside their home rather than repointing at the home itself, since it keeps the existing shape for both parties and does not change the owner's path at all. The sibling's 0700 should not be loosened — it holds attachments and email_accounts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3.1 KiB
42 — the first member turn ran, and its cwd is a directory the member cannot enter
The privilege drop and the allowlist both held in production. Captured from /proc during the first real
member chat turn:
uid=1001 parent=sudo secrets=0
HOME = …/jg@pertento.ai/home
CLAUDE_CONFIG_DIR = …/jg@pertento.ai/home/.claude
env = CLAUDE_CODE_ENTRYPOINT CLAUDE_CONFIG_DIR HOME LANG LOGNAME PATH SHELL TERM USER
Nine variables, exactly the allowlist plus what setpriv --init-groups --reset-env supplies. Zero
ANTHROPIC_*, zero POSTGRES_URL, zero JWT_SECRET. That is the first live test of the whole design and it
passed.
The defect
cwd = …/jg@pertento.ai/general_chat_sessions
drwx------ pastilhas:pastilhas
The member cannot enter their own turn's working directory. Verified: sudo -u green … cd →
can't cd to …/general_chat_sessions. Every Bash tool call in that turn fails immediately, including
echo hello, which is what the owner observed — the agent reported its shell as broken and could not read
its own cwd.
websocket.ts:134:
if (msg.context === 'chat') return msg.cwd?.trim() ? resolveCwd(home, msg.cwd) : ensureGeneralChatSessionsCwd(email);
The default chat cwd is email-derived, and confineUserTree:307-312 deliberately makes every sibling of
home the platform's at 0700 — "Every sibling of home is the platform's. 700 means traversal alone does
not open them." Correct for attachments and email_accounts; fatal as a member's cwd.
29 reasoned about this and got half of it right:
"
general_chat_sessionslives underDATA_PATH/<email>, not under a home, so those paths stay email-derived. Collapsing them would have been the same class of mistake asundefinedmeaning 'the owner'."
The path being email-derived is true. What did not follow is that it is therefore usable — it is platform-owned by design, so a member's turn can never run there. The email/home split was right; this is the one place where the answer is neither.
Fix
A member's default chat cwd should be somewhere they own. Two options:
- their home — one line, and it matches what an unqualified turn should mean for them
~/general_chat_sessionsinside their home — keeps the "chat sessions have their own directory" behaviour, member-owned, and leaves the platform's sibling untouched
I would take the second: it preserves the existing shape for both parties, and the owner's path does not change at all. The first is smaller if the directory carries no meaning for a member.
Do not loosen the sibling. It holds attachments and email_accounts, and the 0700 is doing real work.
Also observed
The member's agent reported every claude.ai connector as needing authorization. Expected: those belong to the Anthropic account they signed in with, not to us, and members get no MCP config from the platform by design. Worth knowing it presents as a wall of "needs auth" rather than as an absence.
Terminal path unaffected — green's claude in the web terminal runs fine as uid 1001 in their own home.