wire the member branch into the SDK spawn, still unreachable
ClaudeSpawnStreamingParams takes an optional member {osUser, home}; createSession
branches on it, using their binary and spawnClaudeAsMember together, or the
owner's CLAUDE_BIN as before.
THE BINARY AND THE PRIVILEGE DROP ARE ONE BRANCH ON PURPOSE. settingSources
makes ~/.claude authoritative for settings and ~ is whatever HOME the process
gets, so pointing the SDK at a member's binary while spawning as the service
user would read the OWNER'S settings and credential while running the member's
code — and it would look like it worked.
cwd defaults to member.home before HOST_HOME for the same reason: HOST_HOME is
this process's home, so a member would start in a directory they cannot read and
the failure would present as a broken agent rather than a wrong cwd.
Nothing populates `member`. Both gates refuse non-owners before any of this is
reached, so the delta is that spawnClaudeAsMember now has two importers instead
of one, and neither path a user can take changes. Verified rather than assumed,
since host made it a condition: both gates intact, 84 tests pass.
Not authorization: host gave an opinion on wire-first and deferred to the owner,
who has not ruled. Corrected in COMMS, where 01 had overstated it. The gates
come off on the owner's word alone; this reverts as one commit if the answer is
no.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,8 @@ import type { ChatEvent, PromptImage } from '../../api/chat/types';
|
||||
import type { ClaudeSpawnParams, ClaudeSpawnStreamingParams, ClaudeCodeResult, LiveClaudeSession } from '../protocol';
|
||||
import { setClaudeSession, clearClaudeSession, getClaudeSession } from './state';
|
||||
import { createParseState, processMessage } from './stream-parser';
|
||||
import { spawnClaudeAsMember } from './spawn-as-member';
|
||||
import { claudeBinIn } from '@@/os-user-claude';
|
||||
|
||||
const SEND_TIMEOUT_MS = 30 * 60 * 1000;
|
||||
|
||||
@@ -320,7 +322,9 @@ function createSession(params: ClaudeSpawnStreamingParams, onEvent: (event: Chat
|
||||
const q = query({
|
||||
prompt: input.gen as AsyncIterable<SdkUserMessage>,
|
||||
options: {
|
||||
cwd: params.cwd ?? HOST_HOME,
|
||||
// HOST_HOME is this process's home — the owner's. Defaulting a member's turn to it would start them in
|
||||
// a directory they cannot read, and the failure would look like a broken agent rather than a wrong cwd.
|
||||
cwd: params.cwd ?? params.member?.home ?? HOST_HOME,
|
||||
permissionMode: 'bypassPermissions',
|
||||
allowDangerouslySkipPermissions: true,
|
||||
includePartialMessages: true,
|
||||
@@ -347,7 +351,18 @@ function createSession(params: ClaudeSpawnStreamingParams, onEvent: (event: Chat
|
||||
],
|
||||
},
|
||||
abortController: abort,
|
||||
pathToClaudeCodeExecutable: CLAUDE_BIN,
|
||||
// A member's turn runs their own install as their own Linux account; the owner's runs as it always has.
|
||||
//
|
||||
// `settingSources` is why the binary and the spawn have to move together: it makes `~/.claude`
|
||||
// authoritative for settings, and `~` is decided by the HOME the process gets. Pointing the SDK at a
|
||||
// member's binary while spawning as the service user would read the OWNER'S settings and credential
|
||||
// while executing the member's code — the worst of both, and it would look like it worked.
|
||||
...(params.member
|
||||
? {
|
||||
pathToClaudeCodeExecutable: claudeBinIn(params.member.home),
|
||||
spawnClaudeCodeProcess: spawnClaudeAsMember(params.member),
|
||||
}
|
||||
: { pathToClaudeCodeExecutable: CLAUDE_BIN }),
|
||||
settingSources: ['user', 'project', 'local'],
|
||||
env: cleanEnv as Record<string, string>,
|
||||
stderr: (d: string) => {
|
||||
|
||||
Reference in New Issue
Block a user