chat: run Claude un-isolated for the Super Admin (real HOME, ~/.claude parity)

Single-user platform: the Super Admin's Claude process now uses the real home
(HOME_DIR) instead of DATA_PATH/<email>/home, so its transcript store IS the same
~/.claude the terminal `claude` uses — platform and terminal sessions are
interchangeable (native `/resume` sees them). The session reader resolves the
same home. The generated container CLAUDE.md is no longer written for the Super
Admin (it would pollute the personal global ~/.claude/CLAUDE.md and is stale);
MCP tools still load via --mcp-config, and email/project panels inject their own
prompts. Sandboxed users keep their isolated home.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-24 12:19:31 +00:00
co-authored by Claude Opus 4.8
parent b3b8a59863
commit 420aa08783
2 changed files with 21 additions and 8 deletions
+6 -3
View File
@@ -6,8 +6,11 @@ import { DATA_PATH } from '../../data-path';
// The `claude` CLI persists every session as a JSONL transcript at
// $HOME/.claude/projects/<slug>/<session-uuid>.jsonl
// where <slug> is the working directory with every non-alphanumeric char replaced by '-'.
// A user's claude process runs with HOME = DATA_PATH/<email>/home (see sidecar/claude/user-instance.ts),
// so we read transcripts from there. We never maintain our own copy — Claude's files are authoritative.
// The (single-user, Super Admin) platform runs Claude with no isolation — HOME is the real home
// (HOME_DIR) — so its transcripts are the same store the terminal `claude` uses. We never keep our
// own copy; Claude's files are authoritative.
const claudeHome = (email: string): string => process.env.HOME_DIR ?? join(DATA_PATH, email, 'home');
/** Dedicated working directory for /chat sessions, so they form their own Claude "project" group. */
export const getClaudeSessionsCwd = (email: string): string => join(DATA_PATH, email, 'claude_sessions');
@@ -19,7 +22,7 @@ export const ensureClaudeSessionsCwd = (email: string): string => {
return dir;
};
const claudeProjectsDir = (email: string): string => join(DATA_PATH, email, 'home', '.claude', 'projects');
const claudeProjectsDir = (email: string): string => join(claudeHome(email), '.claude', 'projects');
/** Claude's folder name for a working directory. */
export const projectSlug = (cwd: string): string => cwd.replace(/[^a-zA-Z0-9]/g, '-');
+15 -5
View File
@@ -1,5 +1,6 @@
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { join, resolve } from 'node:path';
import { homedir } from 'node:os';
import type { SidecarCommand, SidecarEvent } from '../protocol';
import { initPaths, loadState, flushAndSave, acquireLock, releaseLock } from './state';
import { setMcpConfigPath } from './claude-manager';
@@ -31,7 +32,11 @@ const OFFICER_AUTH_TOKEN = await sign(
'30d',
);
const homeDir = join(DATA_PATH, email, 'home');
// Single-user platform: the Super Admin runs Claude with no isolation — real HOME, real ~/.claude —
// so platform sessions have perfect parity with terminal sessions (same config, credentials, and
// transcript store, interchangeable via `claude --resume`). Any non-super-admin keeps an isolated home.
const homeDir =
dbUser.role === 'Super Admin' ? (process.env.HOME_DIR ?? homedir()) : join(DATA_PATH, email, 'home');
const globalToolsDir = join(DATA_PATH, 'tools');
const userToolsDir = join(DATA_PATH, email, 'tools');
@@ -123,10 +128,15 @@ function generateMcpConfig(): McpPaths {
// ── Startup ──
try {
refreshClaudeMd();
} catch (err) {
console.error(`[claude:${email}] failed to refresh CLAUDE.md:`, err instanceof Error ? err.message : err);
// Only sandboxed users get the generated container CLAUDE.md. For the un-isolated Super Admin, HOME is
// the real home, so writing it there would pollute the personal global ~/.claude/CLAUDE.md (loaded by
// the terminal `claude` too) — parity means running as the user, not injecting platform context.
if (dbUser.role !== 'Super Admin') {
try {
refreshClaudeMd();
} catch (err) {
console.error(`[claude:${email}] failed to refresh CLAUDE.md:`, err instanceof Error ? err.message : err);
}
}
const mcpPaths = generateMcpConfig();