fix(browser): address platform and port review

This commit is contained in:
Tam Nhu Tran
2026-04-16 18:49:24 -04:00
parent 06f6f5485f
commit 8a17410f96
7 changed files with 87 additions and 18 deletions
+10
View File
@@ -90,6 +90,16 @@ CCS still supports environment-variable overrides for backward compatibility.
If an override is active, Browser status surfaces should report that the current session is being If an override is active, Browser status surfaces should report that the current session is being
managed externally by environment variables. managed externally by environment variables.
Override precedence is:
1. `CCS_BROWSER_USER_DATA_DIR`
2. `CCS_BROWSER_PROFILE_DIR`
3. the persisted `browser.claude.user_data_dir` config value
Config-backed Browser Attach always passes an explicit DevTools port to the runtime, even when the
effective value is the default `9222`. Metadata-based port discovery is preserved only for the
legacy `CCS_BROWSER_PROFILE_DIR` flow when `CCS_BROWSER_DEVTOOLS_PORT` is not set.
## Managed Runtime Files ## Managed Runtime Files
- `~/.claude.json` -> CCS manages `mcpServers.ccs-browser` for Claude Browser Attach - `~/.claude.json` -> CCS manages `mcpServers.ccs-browser` for Claude Browser Attach
+3 -9
View File
@@ -1,14 +1,9 @@
import { getBrowserStatus, type BrowserStatusPayload } from '../utils/browser'; import { getBrowserStatus, type BrowserStatusPayload } from '../utils/browser';
import { getNodePlatformKey } from '../utils/browser/platform';
import { color, dim, header, initUI, subheader } from '../utils/ui'; import { color, dim, header, initUI, subheader } from '../utils/ui';
type HelpWriter = (line: string) => void; type HelpWriter = (line: string) => void;
function currentPlatform(): 'darwin' | 'linux' | 'win32' {
if (process.platform === 'darwin') return 'darwin';
if (process.platform === 'win32') return 'win32';
return 'linux';
}
function summarizeBrowserHealth(status: BrowserStatusPayload): { function summarizeBrowserHealth(status: BrowserStatusPayload): {
label: 'ready' | 'partial' | 'action required'; label: 'ready' | 'partial' | 'action required';
exitCode: 0 | 1; exitCode: 0 | 1;
@@ -63,9 +58,8 @@ function writeClaudeStatus(
writeLine(` Detail: ${status.detail}`); writeLine(` Detail: ${status.detail}`);
writeLine(` Next step: ${status.nextStep}`); writeLine(` Next step: ${status.nextStep}`);
if (includeLaunchGuidance && status.enabled && status.state !== 'ready') { if (includeLaunchGuidance && status.enabled && status.state !== 'ready') {
writeLine( const platform = getNodePlatformKey();
` Launch command (${currentPlatform()}): ${status.launchCommands[currentPlatform()]}` writeLine(` Launch command (${platform}): ${status.launchCommands[platform]}`);
);
} }
writeLine(''); writeLine('');
} }
+3
View File
@@ -74,6 +74,9 @@ export function getEffectiveClaudeBrowserAttachConfig(
overrideActive: false, overrideActive: false,
userDataDir: configUserDataDir, userDataDir: configUserDataDir,
devtoolsPort: configPort, devtoolsPort: configPort,
// Config-backed browser attach always keeps an explicit port so launches
// stay aligned with Settings > Browser, even when the effective value is
// the default 9222.
hasExplicitDevtoolsPort: true, hasExplicitDevtoolsPort: true,
}; };
} }
+4 -9
View File
@@ -2,6 +2,7 @@ import { getBrowserConfig } from '../../config/unified-config-loader';
import { getCodexBinaryInfo } from '../../targets/codex-detector'; import { getCodexBinaryInfo } from '../../targets/codex-detector';
import { type BrowserRuntimeEnv, resolveBrowserRuntimeEnv } from './chrome-reuse'; import { type BrowserRuntimeEnv, resolveBrowserRuntimeEnv } from './chrome-reuse';
import { getBrowserMcpServerName, getBrowserMcpServerPath } from './mcp-installer'; import { getBrowserMcpServerName, getBrowserMcpServerPath } from './mcp-installer';
import { getNodePlatformKey } from './platform';
import { import {
getEffectiveClaudeBrowserAttachConfig, getEffectiveClaudeBrowserAttachConfig,
getRecommendedBrowserUserDataDir, getRecommendedBrowserUserDataDir,
@@ -107,7 +108,7 @@ async function buildClaudeBrowserStatus(
state: 'path_missing', state: 'path_missing',
title: 'Claude Browser Attach path is missing.', title: 'Claude Browser Attach path is missing.',
detail: message, detail: message,
nextStep: `Create or choose a Chrome user-data directory, then launch Chrome with attach mode enabled. Example: ${launchCommands[platformKey()]}`, nextStep: `Create or choose a Chrome user-data directory, then launch Chrome with attach mode enabled. Example: ${launchCommands[getNodePlatformKey()]}`,
}; };
} }
@@ -117,7 +118,7 @@ async function buildClaudeBrowserStatus(
state: 'browser_not_running', state: 'browser_not_running',
title: 'Claude Browser Attach could not find a running browser session.', title: 'Claude Browser Attach could not find a running browser session.',
detail: message, detail: message,
nextStep: `Start Chrome with remote debugging and the configured user-data dir. Example: ${launchCommands[platformKey()]}`, nextStep: `Start Chrome with remote debugging and the configured user-data dir. Example: ${launchCommands[getNodePlatformKey()]}`,
}; };
} }
@@ -126,7 +127,7 @@ async function buildClaudeBrowserStatus(
state: 'endpoint_unreachable', state: 'endpoint_unreachable',
title: 'Claude Browser Attach could not reach the DevTools endpoint.', title: 'Claude Browser Attach could not reach the DevTools endpoint.',
detail: message, detail: message,
nextStep: `Restart the attach browser session or confirm the configured port. Example: ${launchCommands[platformKey()]}`, nextStep: `Restart the attach browser session or confirm the configured port. Example: ${launchCommands[getNodePlatformKey()]}`,
}; };
} }
} }
@@ -185,9 +186,3 @@ function buildLaunchCommands(userDataDir: string, devtoolsPort: number): Browser
win32: `chrome.exe --remote-debugging-port=${devtoolsPort} --user-data-dir=${quotedPath}`, win32: `chrome.exe --remote-debugging-port=${devtoolsPort} --user-data-dir=${quotedPath}`,
}; };
} }
function platformKey(): keyof BrowserLaunchCommands {
if (process.platform === 'darwin') return 'darwin';
if (process.platform === 'win32') return 'win32';
return 'linux';
}
+9
View File
@@ -0,0 +1,9 @@
export type BrowserPlatformKey = 'darwin' | 'linux' | 'win32';
export function getNodePlatformKey(
platform: NodeJS.Platform = process.platform
): BrowserPlatformKey {
if (platform === 'darwin') return 'darwin';
if (platform === 'win32') return 'win32';
return 'linux';
}
@@ -199,4 +199,45 @@ describe('browser status', () => {
codexSpy.mockRestore(); codexSpy.mockRestore();
} }
}); });
it('always forwards an explicit port for config-backed browser attach sessions', async () => {
mutateUnifiedConfig((config) => {
config.browser = {
claude: {
enabled: true,
user_data_dir: '/tmp/config-browser',
devtools_port: 9222,
},
codex: {
enabled: true,
},
};
});
const runtimeSpy = spyOn(chromeReuse, 'resolveBrowserRuntimeEnv').mockResolvedValue({
CCS_BROWSER_USER_DATA_DIR: '/tmp/config-browser',
CCS_BROWSER_DEVTOOLS_HOST: '127.0.0.1',
CCS_BROWSER_DEVTOOLS_PORT: '9222',
CCS_BROWSER_DEVTOOLS_HTTP_URL: 'http://127.0.0.1:9222',
CCS_BROWSER_DEVTOOLS_WS_URL: 'ws://127.0.0.1/devtools/browser/config',
});
const codexSpy = spyOn(codexDetector, 'getCodexBinaryInfo').mockReturnValue({
path: '/usr/local/bin/codex',
needsShell: false,
version: 'codex-cli 0.120.0',
features: ['config-overrides'],
});
try {
await getBrowserStatus();
expect(runtimeSpy.mock.calls[0]?.[0]).toEqual({
profileDir: '/tmp/config-browser',
devtoolsPort: '9222',
});
} finally {
runtimeSpy.mockRestore();
codexSpy.mockRestore();
}
});
}); });
+17
View File
@@ -0,0 +1,17 @@
import { describe, expect, it } from 'bun:test';
import { getNodePlatformKey } from '../../../../src/utils/browser/platform';
describe('browser platform helper', () => {
it('maps darwin explicitly', () => {
expect(getNodePlatformKey('darwin')).toBe('darwin');
});
it('maps win32 explicitly', () => {
expect(getNodePlatformKey('win32')).toBe('win32');
});
it('falls back to linux for other node platforms', () => {
expect(getNodePlatformKey('linux')).toBe('linux');
expect(getNodePlatformKey('freebsd')).toBe('linux');
});
});