completly remove sandbox related code as the downstream libraries do not work with it on various os

This commit is contained in:
webdevcody
2026-01-07 08:54:14 -05:00
parent 4d4025ca06
commit 1316ead8c8
34 changed files with 589 additions and 1230 deletions

View File

@@ -16,7 +16,6 @@
*/
import type { Options } from '@anthropic-ai/claude-agent-sdk';
import os from 'os';
import path from 'path';
import { resolveModelString } from '@automaker/model-resolver';
import { createLogger } from '@automaker/utils';
@@ -57,139 +56,6 @@ export function validateWorkingDirectory(cwd: string): void {
}
}
/**
* Known cloud storage path patterns where sandbox mode is incompatible.
*
* The Claude CLI sandbox feature uses filesystem isolation that conflicts with
* cloud storage providers' virtual filesystem implementations. This causes the
* Claude process to exit with code 1 when sandbox is enabled for these paths.
*
* Affected providers (macOS paths):
* - Dropbox: ~/Library/CloudStorage/Dropbox-*
* - Google Drive: ~/Library/CloudStorage/GoogleDrive-*
* - OneDrive: ~/Library/CloudStorage/OneDrive-*
* - iCloud Drive: ~/Library/Mobile Documents/
* - Box: ~/Library/CloudStorage/Box-*
*
* Note: This is a known limitation when using cloud storage paths.
*/
/**
* macOS-specific cloud storage patterns that appear under ~/Library/
* These are specific enough to use with includes() safely.
*/
const MACOS_CLOUD_STORAGE_PATTERNS = [
'/Library/CloudStorage/', // Dropbox, Google Drive, OneDrive, Box on macOS
'/Library/Mobile Documents/', // iCloud Drive on macOS
] as const;
/**
* Generic cloud storage folder names that need to be anchored to the home directory
* to avoid false positives (e.g., /home/user/my-project-about-dropbox/).
*/
const HOME_ANCHORED_CLOUD_FOLDERS = [
'Google Drive', // Google Drive on some systems
'Dropbox', // Dropbox on Linux/alternative installs
'OneDrive', // OneDrive on Linux/alternative installs
] as const;
/**
* Check if a path is within a cloud storage location.
*
* Cloud storage providers use virtual filesystem implementations that are
* incompatible with the Claude CLI sandbox feature, causing process crashes.
*
* Uses two detection strategies:
* 1. macOS-specific patterns (under ~/Library/) - checked via includes()
* 2. Generic folder names - anchored to home directory to avoid false positives
*
* @param cwd - The working directory path to check
* @returns true if the path is in a cloud storage location
*/
export function isCloudStoragePath(cwd: string): boolean {
const resolvedPath = path.resolve(cwd);
// Normalize to forward slashes for consistent pattern matching across platforms
let normalizedPath = resolvedPath.split(path.sep).join('/');
// Remove Windows drive letter if present (e.g., "C:/Users" -> "/Users")
// This ensures Unix paths in tests work the same on Windows
normalizedPath = normalizedPath.replace(/^[A-Za-z]:/, '');
// Check macOS-specific patterns (these are specific enough to use includes)
if (MACOS_CLOUD_STORAGE_PATTERNS.some((pattern) => normalizedPath.includes(pattern))) {
return true;
}
// Check home-anchored patterns to avoid false positives
// e.g., /home/user/my-project-about-dropbox/ should NOT match
const home = os.homedir();
for (const folder of HOME_ANCHORED_CLOUD_FOLDERS) {
const cloudPath = path.join(home, folder);
let normalizedCloudPath = cloudPath.split(path.sep).join('/');
// Remove Windows drive letter if present
normalizedCloudPath = normalizedCloudPath.replace(/^[A-Za-z]:/, '');
// Check if resolved path starts with the cloud storage path followed by a separator
// This ensures we match ~/Dropbox/project but not ~/Dropbox-archive or ~/my-dropbox-tool
if (
normalizedPath === normalizedCloudPath ||
normalizedPath.startsWith(normalizedCloudPath + '/')
) {
return true;
}
}
return false;
}
/**
* Result of sandbox compatibility check
*/
export interface SandboxCheckResult {
/** Whether sandbox should be enabled */
enabled: boolean;
/** If disabled, the reason why */
disabledReason?: 'cloud_storage' | 'user_setting';
/** Human-readable message for logging/UI */
message?: string;
}
/**
* Determine if sandbox mode should be enabled for a given configuration.
*
* Sandbox mode is automatically disabled for cloud storage paths because the
* Claude CLI sandbox feature is incompatible with virtual filesystem
* implementations used by cloud storage providers (Dropbox, Google Drive, etc.).
*
* @param cwd - The working directory
* @param enableSandboxMode - User's sandbox mode setting
* @returns SandboxCheckResult with enabled status and reason if disabled
*/
export function checkSandboxCompatibility(
cwd: string,
enableSandboxMode?: boolean
): SandboxCheckResult {
// User has explicitly disabled sandbox mode
if (enableSandboxMode === false) {
return {
enabled: false,
disabledReason: 'user_setting',
};
}
// Check for cloud storage incompatibility (applies when enabled or undefined)
if (isCloudStoragePath(cwd)) {
return {
enabled: false,
disabledReason: 'cloud_storage',
message: `Sandbox mode auto-disabled: Project is in a cloud storage location (${cwd}). The Claude CLI sandbox feature is incompatible with cloud storage filesystems. To use sandbox mode, move your project to a local directory.`,
};
}
// Sandbox is compatible and enabled (true or undefined defaults to enabled)
return {
enabled: true,
};
}
/**
* Tool presets for different use cases
*/
@@ -272,55 +138,31 @@ export function getModelForUseCase(
/**
* Base options that apply to all SDK calls
* AUTONOMOUS MODE: Always bypass permissions for fully autonomous operation
*/
function getBaseOptions(): Partial<Options> {
return {
permissionMode: 'acceptEdits',
permissionMode: 'bypassPermissions',
allowDangerouslySkipPermissions: true,
};
}
/**
* MCP permission options result
* MCP options result
*/
interface McpPermissionOptions {
/** Whether tools should be restricted to a preset */
shouldRestrictTools: boolean;
/** Options to spread when MCP bypass is enabled */
bypassOptions: Partial<Options>;
interface McpOptions {
/** Options to spread for MCP servers */
mcpServerOptions: Partial<Options>;
}
/**
* Build MCP-related options based on configuration.
* Centralizes the logic for determining permission modes and tool restrictions
* when MCP servers are configured.
*
* @param config - The SDK options config
* @returns Object with MCP permission settings to spread into final options
* @returns Object with MCP server settings to spread into final options
*/
function buildMcpOptions(config: CreateSdkOptionsConfig): McpPermissionOptions {
const hasMcpServers = config.mcpServers && Object.keys(config.mcpServers).length > 0;
// Default to true for autonomous workflow. Security is enforced when adding servers
// via the security warning dialog that explains the risks.
const mcpAutoApprove = config.mcpAutoApproveTools ?? true;
const mcpUnrestricted = config.mcpUnrestrictedTools ?? true;
// Determine if we should bypass permissions based on settings
const shouldBypassPermissions = hasMcpServers && mcpAutoApprove;
// Determine if we should restrict tools (only when no MCP or unrestricted is disabled)
const shouldRestrictTools = !hasMcpServers || !mcpUnrestricted;
function buildMcpOptions(config: CreateSdkOptionsConfig): McpOptions {
return {
shouldRestrictTools,
// Only include bypass options when MCP is configured and auto-approve is enabled
bypassOptions: shouldBypassPermissions
? {
permissionMode: 'bypassPermissions' as const,
// Required flag when using bypassPermissions mode
allowDangerouslySkipPermissions: true,
}
: {},
// Include MCP servers if configured
mcpServerOptions: config.mcpServers ? { mcpServers: config.mcpServers } : {},
};
@@ -422,18 +264,9 @@ export interface CreateSdkOptionsConfig {
/** Enable auto-loading of CLAUDE.md files via SDK's settingSources */
autoLoadClaudeMd?: boolean;
/** Enable sandbox mode for bash command isolation */
enableSandboxMode?: boolean;
/** MCP servers to make available to the agent */
mcpServers?: Record<string, McpServerConfig>;
/** Auto-approve MCP tool calls without permission prompts */
mcpAutoApproveTools?: boolean;
/** Allow unrestricted tools when MCP servers are enabled */
mcpUnrestrictedTools?: boolean;
/** Extended thinking level for Claude models */
thinkingLevel?: ThinkingLevel;
}
@@ -554,7 +387,6 @@ export function createSuggestionsOptions(config: CreateSdkOptionsConfig): Option
* - Full tool access for code modification
* - Standard turns for interactive sessions
* - Model priority: explicit model > session model > chat default
* - Sandbox mode controlled by enableSandboxMode setting (auto-disabled for cloud storage)
* - When autoLoadClaudeMd is true, uses preset mode and settingSources for CLAUDE.md loading
*/
export function createChatOptions(config: CreateSdkOptionsConfig): Options {
@@ -573,24 +405,12 @@ export function createChatOptions(config: CreateSdkOptionsConfig): Options {
// Build thinking options
const thinkingOptions = buildThinkingOptions(config.thinkingLevel);
// Check sandbox compatibility (auto-disables for cloud storage paths)
const sandboxCheck = checkSandboxCompatibility(config.cwd, config.enableSandboxMode);
return {
...getBaseOptions(),
model: getModelForUseCase('chat', effectiveModel),
maxTurns: MAX_TURNS.standard,
cwd: config.cwd,
// Only restrict tools if no MCP servers configured or unrestricted is disabled
...(mcpOptions.shouldRestrictTools && { allowedTools: [...TOOL_PRESETS.chat] }),
// Apply MCP bypass options if configured
...mcpOptions.bypassOptions,
...(sandboxCheck.enabled && {
sandbox: {
enabled: true,
autoAllowBashIfSandboxed: true,
},
}),
allowedTools: [...TOOL_PRESETS.chat],
...claudeMdOptions,
...thinkingOptions,
...(config.abortController && { abortController: config.abortController }),
@@ -605,7 +425,6 @@ export function createChatOptions(config: CreateSdkOptionsConfig): Options {
* - Full tool access for code modification and implementation
* - Extended turns for thorough feature implementation
* - Uses default model (can be overridden)
* - Sandbox mode controlled by enableSandboxMode setting (auto-disabled for cloud storage)
* - When autoLoadClaudeMd is true, uses preset mode and settingSources for CLAUDE.md loading
*/
export function createAutoModeOptions(config: CreateSdkOptionsConfig): Options {
@@ -621,24 +440,12 @@ export function createAutoModeOptions(config: CreateSdkOptionsConfig): Options {
// Build thinking options
const thinkingOptions = buildThinkingOptions(config.thinkingLevel);
// Check sandbox compatibility (auto-disables for cloud storage paths)
const sandboxCheck = checkSandboxCompatibility(config.cwd, config.enableSandboxMode);
return {
...getBaseOptions(),
model: getModelForUseCase('auto', config.model),
maxTurns: MAX_TURNS.maximum,
cwd: config.cwd,
// Only restrict tools if no MCP servers configured or unrestricted is disabled
...(mcpOptions.shouldRestrictTools && { allowedTools: [...TOOL_PRESETS.fullAccess] }),
// Apply MCP bypass options if configured
...mcpOptions.bypassOptions,
...(sandboxCheck.enabled && {
sandbox: {
enabled: true,
autoAllowBashIfSandboxed: true,
},
}),
allowedTools: [...TOOL_PRESETS.fullAccess],
...claudeMdOptions,
...thinkingOptions,
...(config.abortController && { abortController: config.abortController }),
@@ -656,7 +463,6 @@ export function createCustomOptions(
config: CreateSdkOptionsConfig & {
maxTurns?: number;
allowedTools?: readonly string[];
sandbox?: { enabled: boolean; autoAllowBashIfSandboxed?: boolean };
}
): Options {
// Validate working directory before creating options
@@ -671,22 +477,17 @@ export function createCustomOptions(
// Build thinking options
const thinkingOptions = buildThinkingOptions(config.thinkingLevel);
// For custom options: use explicit allowedTools if provided, otherwise use preset based on MCP settings
// For custom options: use explicit allowedTools if provided, otherwise default to readOnly
const effectiveAllowedTools = config.allowedTools
? [...config.allowedTools]
: mcpOptions.shouldRestrictTools
? [...TOOL_PRESETS.readOnly]
: undefined;
: [...TOOL_PRESETS.readOnly];
return {
...getBaseOptions(),
model: getModelForUseCase('default', config.model),
maxTurns: config.maxTurns ?? MAX_TURNS.maximum,
cwd: config.cwd,
...(effectiveAllowedTools && { allowedTools: effectiveAllowedTools }),
...(config.sandbox && { sandbox: config.sandbox }),
// Apply MCP bypass options if configured
...mcpOptions.bypassOptions,
allowedTools: effectiveAllowedTools,
...claudeMdOptions,
...thinkingOptions,
...(config.abortController && { abortController: config.abortController }),

View File

@@ -55,34 +55,6 @@ export async function getAutoLoadClaudeMdSetting(
}
}
/**
* Get the enableSandboxMode setting from global settings.
* Returns false if settings service is not available.
*
* @param settingsService - Optional settings service instance
* @param logPrefix - Prefix for log messages (e.g., '[AgentService]')
* @returns Promise resolving to the enableSandboxMode setting value
*/
export async function getEnableSandboxModeSetting(
settingsService?: SettingsService | null,
logPrefix = '[SettingsHelper]'
): Promise<boolean> {
if (!settingsService) {
logger.info(`${logPrefix} SettingsService not available, sandbox mode disabled`);
return false;
}
try {
const globalSettings = await settingsService.getGlobalSettings();
const result = globalSettings.enableSandboxMode ?? false;
logger.info(`${logPrefix} enableSandboxMode from global settings: ${result}`);
return result;
} catch (error) {
logger.error(`${logPrefix} Failed to load enableSandboxMode setting:`, error);
throw error;
}
}
/**
* Filters out CLAUDE.md from context files when autoLoadClaudeMd is enabled
* and rebuilds the formatted prompt without it.