refactor: selective catches in write-commands, cdp-inspector, meta-commands, snapshot

Convert ~27 empty/obscuring catches to selective error handling across 4
browse source files. CDP ops check for closed/Target/detached messages,
DOM ops check TypeError/DOMException, filesystem ops check ENOENT/EACCES,
JSON parsing checks SyntaxError. Remove dead code in cdp-inspector where
try/catch wrapped synchronous no-ops.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan 2026-04-09 04:54:32 -10:00
parent fb24bb4a1c
commit b8c4e703c6
No known key found for this signature in database
GPG Key ID: C1F69E85C74EFE1D
4 changed files with 74 additions and 62 deletions

View File

@ -98,8 +98,9 @@ async function getOrCreateSession(page: Page): Promise<any> {
try { try {
await session.send('DOM.getDocument', { depth: 0 }); await session.send('DOM.getDocument', { depth: 0 });
return session; return session;
} catch { } catch (err: any) {
// Session is stale — recreate // Session is stale — recreate (CDP disconnects throw on closed/Target errors)
if (!err?.message?.includes('closed') && !err?.message?.includes('Target') && !err?.message?.includes('detached')) throw err;
cdpSessions.delete(page); cdpSessions.delete(page);
initializedPages.delete(page); initializedPages.delete(page);
} }
@ -117,7 +118,9 @@ async function getOrCreateSession(page: Page): Promise<any> {
page.once('framenavigated', () => { page.once('framenavigated', () => {
try { try {
session.detach().catch(() => {}); session.detach().catch(() => {});
} catch {} } catch (err: any) {
if (!err?.message?.includes('closed') && !err?.message?.includes('Target') && !err?.message?.includes('detached')) throw err;
}
cdpSessions.delete(page); cdpSessions.delete(page);
initializedPages.delete(page); initializedPages.delete(page);
}); });
@ -258,8 +261,9 @@ export async function inspectElement(
left: border[0] - margin[0], left: border[0] - margin[0],
}, },
}; };
} catch { } catch (err: any) {
// Element may not have a box model (e.g., display:none) // Element may not have a box model (e.g., display:none) — CDP returns "Could not compute box model"
if (!err?.message?.includes('box model') && !err?.message?.includes('Could not compute')) throw err;
} }
// Get matched styles // Get matched styles
@ -315,10 +319,8 @@ export async function inspectElement(
if (rule.styleSheetId) { if (rule.styleSheetId) {
styleSheetId = rule.styleSheetId; styleSheetId = rule.styleSheetId;
try { // Resolve stylesheet source name
// Try to resolve stylesheet URL source = rule.origin === 'regular' ? (rule.styleSheetId || 'stylesheet') : rule.origin;
source = rule.origin === 'regular' ? (rule.styleSheetId || 'stylesheet') : rule.origin;
} catch {}
} }
if (rule.style?.range) { if (rule.style?.range) {
@ -328,15 +330,7 @@ export async function inspectElement(
} }
// Try to get a friendly source name from stylesheet // Try to get a friendly source name from stylesheet
if (styleSheetId) { // (styleSheetId metadata is available via CDP — see stylesheet URL resolution below)
try {
// Stylesheet URL might be embedded in the rule data
// CDP provides sourceURL in some cases
if (rule.style?.cssText) {
// Parse source from the styleSheetId metadata
}
} catch {}
}
// Get media query if present // Get media query if present
let media: string | undefined; let media: string | undefined;
@ -433,15 +427,9 @@ export async function inspectElement(
} }
// Resolve stylesheet URLs for better source info // Resolve stylesheet URLs for better source info
for (const rule of matchedRules) { // Note: CSS.getStyleSheetText is called per-rule but result is unused — the styleSheetId
if (rule.styleSheetId && rule.source !== 'inline') { // is opaque and CDP doesn't expose a direct URL lookup. Left as a placeholder for future
try { // enhancement (e.g., CSS.styleSheetAdded event tracking).
const sheetMeta = await session.send('CSS.getStyleSheetText', { styleSheetId: rule.styleSheetId }).catch(() => null);
// Try to get the stylesheet header for URL info
// The styleSheetId itself is opaque, but we can try to get source URL
} catch {}
}
}
return { return {
selector, selector,
@ -531,8 +519,9 @@ export async function modifyStyle(
method = 'setStyleTexts'; method = 'setStyleTexts';
source = `${targetRule.source}:${targetRule.sourceLine}`; source = `${targetRule.source}:${targetRule.sourceLine}`;
sourceLine = targetRule.sourceLine; sourceLine = targetRule.sourceLine;
} catch { } catch (err: any) {
// Fall back to inline // Fall back to inline — setStyleTexts fails on immutable stylesheets or stale ranges
if (!err?.message?.includes('style') && !err?.message?.includes('range') && !err?.message?.includes('closed') && !err?.message?.includes('Target')) throw err;
} }
} }
@ -591,8 +580,9 @@ export async function undoModification(page: Page, index?: number): Promise<void
await modifyStyle(page, mod.selector, mod.property, mod.oldValue); await modifyStyle(page, mod.selector, mod.property, mod.oldValue);
// Remove the undo modification from history (it's a restore, not a new mod) // Remove the undo modification from history (it's a restore, not a new mod)
modificationHistory.pop(); modificationHistory.pop();
} catch { } catch (err: any) {
// Fall back to inline restore // Fall back to inline restore — CDP may have disconnected or stylesheet changed
if (!err?.message?.includes('closed') && !err?.message?.includes('Target') && !err?.message?.includes('style') && !err?.message?.includes('not found') && !err?.message?.includes('Element')) throw err;
await page.evaluate( await page.evaluate(
([sel, prop, val]) => { ([sel, prop, val]) => {
const el = document.querySelector(sel); const el = document.querySelector(sel);
@ -652,8 +642,9 @@ export async function resetModifications(page: Page): Promise<void> {
}, },
[mod.selector, mod.property, mod.oldValue] [mod.selector, mod.property, mod.oldValue]
); );
} catch { } catch (err: any) {
// Best effort // Best effort — page may have navigated or element may be gone
if (!err?.message?.includes('closed') && !err?.message?.includes('Target') && !err?.message?.includes('Execution context')) throw err;
} }
} }
modificationHistory.length = 0; modificationHistory.length = 0;
@ -757,7 +748,7 @@ export function detachSession(page?: Page): void {
if (page) { if (page) {
const session = cdpSessions.get(page); const session = cdpSessions.get(page);
if (session) { if (session) {
try { session.detach().catch(() => {}); } catch {} try { session.detach().catch(() => {}); } catch (err: any) { if (!err?.message?.includes('closed') && !err?.message?.includes('Target') && !err?.message?.includes('detached')) throw err; }
cdpSessions.delete(page); cdpSessions.delete(page);
initializedPages.delete(page); initializedPages.delete(page);
} }

View File

@ -248,8 +248,9 @@ export async function handleMetaCommand(
try { try {
commands = JSON.parse(jsonStr); commands = JSON.parse(jsonStr);
if (!Array.isArray(commands)) throw new Error('not array'); if (!Array.isArray(commands)) throw new Error('not array');
} catch { } catch (err: any) {
// Fallback: pipe-delimited format "goto url | click @e5 | snapshot -ic" // Fallback: pipe-delimited format "goto url | click @e5 | snapshot -ic"
if (!(err instanceof SyntaxError) && err?.message !== 'not array') throw err;
commands = jsonStr.split(' | ') commands = jsonStr.split(' | ')
.filter(seg => seg.trim().length > 0) .filter(seg => seg.trim().length > 0)
.map(seg => tokenizePipeSegment(seg.trim())); .map(seg => tokenizePipeSegment(seg.trim()));
@ -291,7 +292,7 @@ export async function handleMetaCommand(
} else { } else {
// Parse error from JSON result // Parse error from JSON result
let errMsg = cr.result; let errMsg = cr.result;
try { errMsg = JSON.parse(cr.result).error || cr.result; } catch {} try { errMsg = JSON.parse(cr.result).error || cr.result; } catch (err: any) { if (!(err instanceof SyntaxError)) throw err; }
results.push(`[${name}] ERROR: ${errMsg}`); results.push(`[${name}] ERROR: ${errMsg}`);
} }
lastWasWrite = WRITE_COMMANDS.has(name); lastWasWrite = WRITE_COMMANDS.has(name);
@ -431,8 +432,9 @@ export async function handleMetaCommand(
execSync(`osascript -e 'tell application "${appName}" to activate'`, { stdio: 'pipe', timeout: 3000 }); execSync(`osascript -e 'tell application "${appName}" to activate'`, { stdio: 'pipe', timeout: 3000 });
activated = true; activated = true;
break; break;
} catch { } catch (err: any) {
// Try next browser // Try next browser — osascript fails if app not found or AppleScript errors
if (err?.status === undefined && !err?.message?.includes('Command failed')) throw err;
} }
} }
@ -448,8 +450,9 @@ export async function handleMetaCommand(
await resolved.locator.scrollIntoViewIfNeeded({ timeout: 5000 }); await resolved.locator.scrollIntoViewIfNeeded({ timeout: 5000 });
return `Browser activated. Scrolled ${args[0]} into view.`; return `Browser activated. Scrolled ${args[0]} into view.`;
} }
} catch { } catch (err: any) {
// Ref not found — still activated the browser // Ref not found or element gone — still activated the browser
if (!err?.message?.includes('not found') && !err?.message?.includes('closed') && !err?.message?.includes('Target') && !err?.message?.includes('timeout')) throw err;
} }
} }
@ -491,7 +494,9 @@ export async function handleMetaCommand(
let gitRoot: string; let gitRoot: string;
try { try {
gitRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim(); gitRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
} catch { } catch (err: any) {
// execSync throws with exit status on non-git directories
if (err?.status === undefined && !err?.message?.includes('Command failed')) throw err;
return 'Not in a git repository — cannot locate inbox.'; return 'Not in a git repository — cannot locate inbox.';
} }
@ -514,8 +519,9 @@ export async function handleMetaCommand(
url: data.page?.url || 'unknown', url: data.page?.url || 'unknown',
userMessage: data.userMessage || '', userMessage: data.userMessage || '',
}); });
} catch { } catch (err: any) {
// Skip malformed files // Skip malformed JSON or unreadable files
if (!(err instanceof SyntaxError) && err?.code !== 'ENOENT' && err?.code !== 'EACCES') throw err;
} }
} }
@ -537,7 +543,7 @@ export async function handleMetaCommand(
// Handle --clear flag // Handle --clear flag
if (args.includes('--clear')) { if (args.includes('--clear')) {
for (const file of files) { for (const file of files) {
try { fs.unlinkSync(path.join(inboxDir, file)); } catch {} try { fs.unlinkSync(path.join(inboxDir, file)); } catch (err: any) { if (err?.code !== 'ENOENT') throw err; }
} }
lines.push(`Cleared ${files.length} message${files.length === 1 ? '' : 's'}.`); lines.push(`Cleared ${files.length} message${files.length === 1 ? '' : 's'}.`);
} }

View File

@ -331,7 +331,9 @@ export async function handleSnapshot(
output.push(`@${ref} [${elem.reason}] "${elem.text}"`); output.push(`@${ref} [${elem.reason}] "${elem.text}"`);
} }
} }
} catch { } catch (err: any) {
// Cursor scan fails on pages with strict CSP or when page has navigated
if (!err?.message?.includes('Execution context') && !err?.message?.includes('closed') && !err?.message?.includes('Target') && !err?.message?.includes('Content Security')) throw err;
output.push(''); output.push('');
output.push('(cursor scan failed — CSP restriction)'); output.push('(cursor scan failed — CSP restriction)');
} }
@ -355,7 +357,7 @@ export async function handleSnapshot(
const nodeFs = require('fs') as typeof import('fs'); const nodeFs = require('fs') as typeof import('fs');
const absolute = nodePath.resolve(screenshotPath); const absolute = nodePath.resolve(screenshotPath);
const safeDirs = [TEMP_DIR, process.cwd()].map((d: string) => { const safeDirs = [TEMP_DIR, process.cwd()].map((d: string) => {
try { return nodeFs.realpathSync(d); } catch { return d; } try { return nodeFs.realpathSync(d); } catch (err: any) { if (err?.code !== 'ENOENT') throw err; return d; }
}); });
let realPath: string; let realPath: string;
try { try {
@ -365,7 +367,8 @@ export async function handleSnapshot(
try { try {
const dir = nodeFs.realpathSync(nodePath.dirname(absolute)); const dir = nodeFs.realpathSync(nodePath.dirname(absolute));
realPath = nodePath.join(dir, nodePath.basename(absolute)); realPath = nodePath.join(dir, nodePath.basename(absolute));
} catch { } catch (err2: any) {
if (err2?.code !== 'ENOENT') throw err2;
realPath = absolute; realPath = absolute;
} }
} else { } else {
@ -385,8 +388,9 @@ export async function handleSnapshot(
if (box) { if (box) {
boxes.push({ ref: `@${ref}`, box }); boxes.push({ ref: `@${ref}`, box });
} }
} catch { } catch (err: any) {
// Element may be offscreen or hidden — skip // Element may be offscreen, hidden, or page navigated — skip
if (!err?.message?.includes('Timeout') && !err?.message?.includes('timeout') && !err?.message?.includes('closed') && !err?.message?.includes('Target') && !err?.message?.includes('Execution context')) throw err;
} }
} }
@ -418,13 +422,16 @@ export async function handleSnapshot(
output.push(''); output.push('');
output.push(`[annotated screenshot: ${screenshotPath}]`); output.push(`[annotated screenshot: ${screenshotPath}]`);
} catch { } catch (err: any) {
// Remove overlays even on screenshot failure // Remove overlays even on screenshot failure — but only swallow page/browser errors
if (!err?.message?.includes('closed') && !err?.message?.includes('Target') && !err?.message?.includes('Execution context') && !err?.message?.includes('screenshot')) throw err;
try { try {
await page.evaluate(() => { await page.evaluate(() => {
document.querySelectorAll('.__browse_annotation__').forEach(el => el.remove()); document.querySelectorAll('.__browse_annotation__').forEach(el => el.remove());
}); });
} catch {} } catch (err2: any) {
if (!err2?.message?.includes('closed') && !err2?.message?.includes('Target') && !err2?.message?.includes('Execution context')) throw err2;
}
} }
} }

View File

@ -399,7 +399,7 @@ export async function handleWriteCommand(
if (!fs.existsSync(fp)) throw new Error(`File not found: ${fp}`); if (!fs.existsSync(fp)) throw new Error(`File not found: ${fp}`);
if (path.isAbsolute(fp)) { if (path.isAbsolute(fp)) {
let resolvedFp: string; let resolvedFp: string;
try { resolvedFp = fs.realpathSync(path.resolve(fp)); } catch { resolvedFp = path.resolve(fp); } try { resolvedFp = fs.realpathSync(path.resolve(fp)); } catch (err: any) { if (err?.code !== 'ENOENT') throw err; resolvedFp = path.resolve(fp); }
if (!SAFE_DIRECTORIES.some(dir => isPathWithin(resolvedFp, dir))) { if (!SAFE_DIRECTORIES.some(dir => isPathWithin(resolvedFp, dir))) {
throw new Error(`Path must be within: ${SAFE_DIRECTORIES.join(', ')}`); throw new Error(`Path must be within: ${SAFE_DIRECTORIES.join(', ')}`);
} }
@ -455,7 +455,7 @@ export async function handleWriteCommand(
if (!fs.existsSync(filePath)) throw new Error(`File not found: ${filePath}`); if (!fs.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
const raw = fs.readFileSync(filePath, 'utf-8'); const raw = fs.readFileSync(filePath, 'utf-8');
let cookies: any[]; let cookies: any[];
try { cookies = JSON.parse(raw); } catch { throw new Error(`Invalid JSON in ${filePath}`); } try { cookies = JSON.parse(raw); } catch (err: any) { throw new Error(`Invalid JSON in ${filePath}: ${err?.message || err}`); }
if (!Array.isArray(cookies)) throw new Error('Cookie file must contain a JSON array'); if (!Array.isArray(cookies)) throw new Error('Cookie file must contain a JSON array');
// Auto-fill domain from current page URL when missing (consistent with cookie command) // Auto-fill domain from current page URL when missing (consistent with cookie command)
@ -520,8 +520,9 @@ export async function handleWriteCommand(
const pickerUrl = `http://127.0.0.1:${port}/cookie-picker?code=${code}`; const pickerUrl = `http://127.0.0.1:${port}/cookie-picker?code=${code}`;
try { try {
Bun.spawn(['open', pickerUrl], { stdout: 'ignore', stderr: 'ignore' }); Bun.spawn(['open', pickerUrl], { stdout: 'ignore', stderr: 'ignore' });
} catch { } catch (err: any) {
// open may fail silently — URL is in the message below // open may fail on non-macOS or if 'open' binary is missing — URL is in the message below
if (err?.code !== 'ENOENT' && !err?.message?.includes('spawn')) throw err;
} }
return `Cookie picker opened at http://127.0.0.1:${port}/cookie-picker\nDetected browsers: ${browsers.map(b => b.name).join(', ')}\nSelect domains to import, then close the picker when done.`; return `Cookie picker opened at http://127.0.0.1:${port}/cookie-picker\nDetected browsers: ${browsers.map(b => b.name).join(', ')}\nSelect domains to import, then close the picker when done.`;
@ -606,7 +607,10 @@ export async function handleWriteCommand(
(el as HTMLElement).style.setProperty('display', 'none', 'important'); (el as HTMLElement).style.setProperty('display', 'none', 'important');
removed++; removed++;
}); });
} catch {} } catch (err: any) {
// querySelectorAll throws DOMException on invalid CSS selectors — skip those
if (!(err instanceof DOMException)) throw err;
}
} }
return removed; return removed;
}, selectors); }, selectors);
@ -815,7 +819,9 @@ export async function handleWriteCommand(
document.querySelectorAll(sel).forEach(el => { document.querySelectorAll(sel).forEach(el => {
(el as HTMLElement).style.display = 'none'; (el as HTMLElement).style.display = 'none';
}); });
} catch {} } catch (err: any) {
if (!(err instanceof DOMException)) throw err;
}
} }
// Also hide fixed/sticky (except nav) // Also hide fixed/sticky (except nav)
for (const el of document.querySelectorAll('*')) { for (const el of document.querySelectorAll('*')) {
@ -838,7 +844,9 @@ export async function handleWriteCommand(
document.querySelectorAll(sel).forEach(el => { document.querySelectorAll(sel).forEach(el => {
(el as HTMLElement).style.display = 'none'; (el as HTMLElement).style.display = 'none';
}); });
} catch {} } catch (err: any) {
if (!(err instanceof DOMException)) throw err;
}
} }
}, hideSelectors); }, hideSelectors);
} }
@ -950,13 +958,13 @@ export async function handleWriteCommand(
reader.onerror = () => reject('Failed to read blob'); reader.onerror = () => reject('Failed to read blob');
reader.readAsDataURL(blob); reader.readAsDataURL(blob);
}); });
} catch { } catch (err: any) {
return 'ERROR:EXPIRED'; return `ERROR:EXPIRED:${err?.message || 'unknown'}`;
} }
}, url); }, url);
if (dataUrl === 'ERROR:TOO_LARGE') throw new Error('Blob too large (>100MB). Use a different approach.'); if (dataUrl === 'ERROR:TOO_LARGE') throw new Error('Blob too large (>100MB). Use a different approach.');
if (dataUrl === 'ERROR:EXPIRED') throw new Error('Blob URL expired or inaccessible.'); if (dataUrl.startsWith('ERROR:EXPIRED')) throw new Error(`Blob URL expired or inaccessible: ${dataUrl.slice('ERROR:EXPIRED:'.length)}`);
const match = dataUrl.match(/^data:([^;]+);base64,(.+)$/); const match = dataUrl.match(/^data:([^;]+);base64,(.+)$/);
if (!match) throw new Error('Failed to decode blob data'); if (!match) throw new Error('Failed to decode blob data');