diff --git a/src/utils/browser/browser-settings.ts b/src/utils/browser/browser-settings.ts index 89faa134..1c7fcc25 100644 --- a/src/utils/browser/browser-settings.ts +++ b/src/utils/browser/browser-settings.ts @@ -232,6 +232,8 @@ export function getEffectiveClaudeBrowserAttachConfig( resolveBrowserUserDataDir(config.claude.user_data_dir) ?? getRecommendedBrowserUserDataDir(); const configPort = normalizeDevtoolsPort(config.claude.devtools_port); const configEvalMode = config.claude.eval_mode ?? 'readonly'; + const envEvalMode = parseBrowserEvalMode(env.CCS_BROWSER_EVAL_MODE); + const effectiveEvalMode = envEvalMode ?? configEvalMode; if (override.userDataDir) { return { @@ -241,7 +243,7 @@ export function getEffectiveClaudeBrowserAttachConfig( userDataDir: override.userDataDir, devtoolsPort: override.devtoolsPort ?? configPort, hasExplicitDevtoolsPort: override.devtoolsPort !== undefined, - evalMode: configEvalMode, + evalMode: effectiveEvalMode, }; } @@ -252,7 +254,7 @@ export function getEffectiveClaudeBrowserAttachConfig( userDataDir: configUserDataDir, devtoolsPort: configPort, hasExplicitDevtoolsPort: true, - evalMode: configEvalMode, + evalMode: effectiveEvalMode, }; } @@ -304,6 +306,15 @@ function parseDevtoolsPort(value?: string): number | undefined { return normalizeDevtoolsPort(Number.parseInt(value.trim(), 10)); } +function parseBrowserEvalMode(value?: string): BrowserEvalMode | undefined { + const trimmed = value?.trim(); + if (trimmed === 'disabled' || trimmed === 'readonly' || trimmed === 'readwrite') { + return trimmed; + } + + return undefined; +} + function normalizeDevtoolsPort(value: number | undefined): number { if (!Number.isFinite(value)) { return 9222; diff --git a/tests/unit/utils/browser/browser-status.test.ts b/tests/unit/utils/browser/browser-status.test.ts index 9add8441..6033eecf 100644 --- a/tests/unit/utils/browser/browser-status.test.ts +++ b/tests/unit/utils/browser/browser-status.test.ts @@ -22,6 +22,7 @@ describe('browser status', () => { let originalBrowserUserDataDir: string | undefined; let originalBrowserProfileDir: string | undefined; let originalBrowserDevtoolsPort: string | undefined; + let originalBrowserEvalMode: string | undefined; beforeEach(() => { tempHome = mkdtempSync(join(tmpdir(), 'ccs-browser-status-')); @@ -29,11 +30,13 @@ describe('browser status', () => { originalBrowserUserDataDir = process.env.CCS_BROWSER_USER_DATA_DIR; originalBrowserProfileDir = process.env.CCS_BROWSER_PROFILE_DIR; originalBrowserDevtoolsPort = process.env.CCS_BROWSER_DEVTOOLS_PORT; + originalBrowserEvalMode = process.env.CCS_BROWSER_EVAL_MODE; process.env.CCS_HOME = tempHome; delete process.env.CCS_BROWSER_USER_DATA_DIR; delete process.env.CCS_BROWSER_PROFILE_DIR; delete process.env.CCS_BROWSER_DEVTOOLS_PORT; + delete process.env.CCS_BROWSER_EVAL_MODE; }); afterEach(() => { @@ -60,6 +63,11 @@ describe('browser status', () => { } else { delete process.env.CCS_BROWSER_DEVTOOLS_PORT; } + if (originalBrowserEvalMode !== undefined) { + process.env.CCS_BROWSER_EVAL_MODE = originalBrowserEvalMode; + } else { + delete process.env.CCS_BROWSER_EVAL_MODE; + } rmSync(tempHome, { recursive: true, force: true }); }); @@ -290,6 +298,29 @@ describe('browser status', () => { }); }); + it('honors CCS_BROWSER_EVAL_MODE over configured eval_mode', () => { + mutateUnifiedConfig((config) => { + config.browser = { + claude: { + enabled: true, + policy: 'manual', + user_data_dir: '/config-browser', + devtools_port: 9333, + eval_mode: 'readonly', + }, + codex: { + enabled: false, + policy: 'manual', + }, + }; + }); + process.env.CCS_BROWSER_EVAL_MODE = 'disabled'; + + const effective = getEffectiveClaudeBrowserAttachConfig(getBrowserConfig()); + + expect(effective.evalMode).toBe('disabled'); + }); + it('returns the same managed attach warning when the configured DevTools port is unreachable', async () => { const managedDir = join(tempHome, '.ccs', 'browser', 'chrome-user-data'); mkdirSync(managedDir, { recursive: true });