fix super admin claude: host cwd, host mcp config paths
Capture HOST_HOME before user-instance overrides process.env.HOME so Super Admin spawns claude in /home/pastilhas. Generate separate MCP configs for sandbox (sandbox paths) and host (real filesystem paths), pick based on role at spawn time. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -225,7 +225,6 @@ async function ensureClaudeSidecar(email: string): Promise<RegisteredSidecar> {
|
||||
}
|
||||
|
||||
async function spawnAndWaitForRegistration(email: string, name: string): Promise<RegisteredSidecar> {
|
||||
// Get proxy secret for auth
|
||||
const proxySecret = await getProxySecret();
|
||||
const proxyPort = process.env.ANTHROPIC_PROXY_PORT ?? '5051';
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ const SEND_TIMEOUT_MS = 5 * 60 * 1000;
|
||||
// The actual binary lives at ~/.local/bin/claude, symlinked from /usr/local/bin/claude.
|
||||
const CLAUDE_BIN = '/usr/local/bin/claude';
|
||||
|
||||
// Capture original HOME before user-instance overrides it
|
||||
const HOST_HOME = process.env.HOME!;
|
||||
const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data');
|
||||
|
||||
// Build full bwrap sandbox args for Claude (prefix + Anthropic env + runuser suffix)
|
||||
@@ -28,11 +30,13 @@ function buildSandboxArgs(email: string): string[] {
|
||||
// Active streaming processes
|
||||
const activeProcs = new Map<string, Subprocess>();
|
||||
|
||||
// MCP config path, set by user-instance at startup
|
||||
let mcpConfigPath: string | undefined;
|
||||
// MCP config paths, set by user-instance at startup
|
||||
let mcpSandboxPath: string | undefined; // path inside bwrap sandbox (/data/...)
|
||||
let mcpHostPath: string | undefined; // path on the host filesystem
|
||||
|
||||
export function setMcpConfigPath(path: string): void {
|
||||
mcpConfigPath = path;
|
||||
export function setMcpConfigPath(sandboxPath: string, hostPath: string): void {
|
||||
mcpSandboxPath = sandboxPath;
|
||||
mcpHostPath = hostPath;
|
||||
}
|
||||
|
||||
// ── Blocking send ──
|
||||
@@ -60,7 +64,9 @@ export async function spawnClaude(params: ClaudeSpawnParams): Promise<ClaudeCode
|
||||
'json',
|
||||
];
|
||||
|
||||
if (mcpConfigPath) claudeArgs.push('--mcp-config', mcpConfigPath);
|
||||
const isSuperAdmin = params.role === 'Super Admin';
|
||||
const mcpConfig = isSuperAdmin ? mcpHostPath : mcpSandboxPath;
|
||||
if (mcpConfig) claudeArgs.push('--mcp-config', mcpConfig);
|
||||
|
||||
const subModel = params.model?.split('/')[1];
|
||||
if (subModel) claudeArgs.push('--model', subModel);
|
||||
@@ -69,9 +75,8 @@ export async function spawnClaude(params: ClaudeSpawnParams): Promise<ClaudeCode
|
||||
claudeArgs.push('--resume', existingSession);
|
||||
}
|
||||
|
||||
const isSuperAdmin = params.role === 'Super Admin';
|
||||
const spawnCmd = isSuperAdmin ? claudeArgs : [...buildSandboxArgs(email), ...claudeArgs];
|
||||
const spawnCwd = isSuperAdmin ? (process.env.HOME ?? join(DATA_PATH, email, 'home')) : undefined;
|
||||
const spawnCwd = isSuperAdmin ? HOST_HOME : undefined;
|
||||
|
||||
const proc = Bun.spawn(spawnCmd, {
|
||||
stdin: 'pipe',
|
||||
@@ -155,7 +160,10 @@ export async function spawnClaudeStreaming(
|
||||
'--include-partial-messages',
|
||||
];
|
||||
|
||||
if (mcpConfigPath) claudeArgs.push('--mcp-config', mcpConfigPath);
|
||||
const { CLAUDECODE: _, ...cleanEnv } = process.env;
|
||||
const isSuperAdmin = params.role === 'Super Admin';
|
||||
const mcpConfig = isSuperAdmin ? mcpHostPath : mcpSandboxPath;
|
||||
if (mcpConfig) claudeArgs.push('--mcp-config', mcpConfig);
|
||||
|
||||
const subModel = params.model?.split('/')[1];
|
||||
if (subModel) claudeArgs.push('--model', subModel);
|
||||
@@ -164,10 +172,8 @@ export async function spawnClaudeStreaming(
|
||||
claudeArgs.push('--resume', existingSession);
|
||||
}
|
||||
|
||||
const { CLAUDECODE: _, ...cleanEnv } = process.env;
|
||||
const isSuperAdmin = params.role === 'Super Admin';
|
||||
const spawnCmd = isSuperAdmin ? claudeArgs : [...buildSandboxArgs(email), ...claudeArgs];
|
||||
const spawnCwd = isSuperAdmin ? (process.env.HOME ?? join(DATA_PATH, email, 'home')) : undefined;
|
||||
const spawnCwd = isSuperAdmin ? HOST_HOME : undefined;
|
||||
|
||||
const proc = Bun.spawn(spawnCmd, {
|
||||
stdin: 'ignore',
|
||||
|
||||
@@ -53,34 +53,54 @@ function refreshClaudeMd(): void {
|
||||
|
||||
// ── MCP config ──
|
||||
|
||||
function generateMcpConfig(): string {
|
||||
type McpPaths = { sandboxPath: string; hostPath: string };
|
||||
|
||||
function generateMcpConfig(): McpPaths {
|
||||
const contextDir = join(DATA_PATH, email!, '.container-context');
|
||||
mkdirSync(contextDir, { recursive: true });
|
||||
const configPath = join(contextDir, 'mcp.json');
|
||||
|
||||
// Inside the sandbox, user data is mounted at SANDBOX_DATA (/data)
|
||||
// Global tools stay at their original paths (mounted read-only at same path)
|
||||
const sandboxUserToolsDir = `${SANDBOX_DATA}/tools`;
|
||||
const toolsDirs = [globalToolsDir, ...(existsSync(userToolsDir) ? [sandboxUserToolsDir] : [])].join(':');
|
||||
const userRoot = join(DATA_PATH, email!);
|
||||
|
||||
const config = {
|
||||
// Sandbox config (paths relative to /data mount)
|
||||
const sandboxToolsDirs = [globalToolsDir, ...(existsSync(userToolsDir) ? [`${SANDBOX_DATA}/tools`] : [])].join(':');
|
||||
const sandboxConfig = {
|
||||
mcpServers: {
|
||||
'officer-tools': {
|
||||
type: 'stdio',
|
||||
command: 'bun',
|
||||
args: ['run', MCP_SERVER_SCRIPT],
|
||||
env: {
|
||||
PI_TOOLS_DIRS: toolsDirs,
|
||||
PI_TOOLS_DIRS: sandboxToolsDirs,
|
||||
OFFICER_EMAIL_DB: `${SANDBOX_DATA}/emails.db`,
|
||||
MCP_TOOLS_LOG: `${SANDBOX_DATA}/logs/mcp-tools.log`,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
writeFileSync(join(contextDir, 'mcp.json'), JSON.stringify(sandboxConfig));
|
||||
|
||||
writeFileSync(configPath, JSON.stringify(config));
|
||||
// Return the sandbox path (config file is inside user data, mounted at /data)
|
||||
return `${SANDBOX_DATA}/.container-context/mcp.json`;
|
||||
// Host config (real filesystem paths, for Super Admin)
|
||||
const hostToolsDirs = [globalToolsDir, ...(existsSync(userToolsDir) ? [userToolsDir] : [])].join(':');
|
||||
const hostConfig = {
|
||||
mcpServers: {
|
||||
'officer-tools': {
|
||||
type: 'stdio',
|
||||
command: 'bun',
|
||||
args: ['run', MCP_SERVER_SCRIPT],
|
||||
env: {
|
||||
PI_TOOLS_DIRS: hostToolsDirs,
|
||||
OFFICER_EMAIL_DB: join(userRoot, 'emails.db'),
|
||||
MCP_TOOLS_LOG: join(userRoot, 'logs', 'mcp-tools.log'),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
writeFileSync(join(contextDir, 'mcp-host.json'), JSON.stringify(hostConfig));
|
||||
|
||||
return {
|
||||
sandboxPath: `${SANDBOX_DATA}/.container-context/mcp.json`,
|
||||
hostPath: join(contextDir, 'mcp-host.json'),
|
||||
};
|
||||
}
|
||||
|
||||
// ── Startup ──
|
||||
@@ -91,8 +111,8 @@ try {
|
||||
console.error(`[claude:${email}] failed to refresh CLAUDE.md:`, err instanceof Error ? err.message : err);
|
||||
}
|
||||
|
||||
const mcpPath = generateMcpConfig();
|
||||
setMcpConfigPath(mcpPath);
|
||||
const mcpPaths = generateMcpConfig();
|
||||
setMcpConfigPath(mcpPaths.sandboxPath, mcpPaths.hostPath);
|
||||
|
||||
console.log(`[claude:${email}] started (HOME=${homeDir})`);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user