garrytan/gstack · warning · Error
Usage: browse useragent <string>
Error message
Usage: browse useragent <string>
What it means
Thrown by `browse useragent` when the joined args string is empty. The command joins all positional args with spaces to form the user agent string; if no args were supplied, `ua` is `''` which is falsy, and the command refuses to set an empty user agent (which Playwright would treat as a deliberate override to empty, breaking fingerprinting and likely site behavior).
Source
Thrown at browse/src/write-commands.ts:585
}]);
return `Cookie set: ${name}=****`;
}
case 'header': {
const headerStr = args[0];
if (!headerStr || !headerStr.includes(':')) throw new Error('Usage: browse header <name>:<value>');
const sep = headerStr.indexOf(':');
const name = headerStr.slice(0, sep).trim();
const value = headerStr.slice(sep + 1).trim();
await bm.setExtraHeader(name, value);
const sensitiveHeaders = ['authorization', 'cookie', 'set-cookie', 'x-api-key', 'x-auth-token'];
const redactedValue = sensitiveHeaders.includes(name.toLowerCase()) ? '****' : value;
return `Header set: ${name}: ${redactedValue}`;
}
case 'useragent': {
const ua = args.join(' ');
if (!ua) throw new Error('Usage: browse useragent <string>');
bm.setUserAgent(ua);
const error = await bm.recreateContext();
if (error) {
return `User agent set to "${ua}" but: ${error}`;
}
return `User agent set: ${ua}`;
}
case 'upload': {
const [selector, ...filePaths] = args;
if (!selector || filePaths.length === 0) throw new Error('Usage: browse upload <selector> <file1> [file2...]');
// Validate paths are within safe directories (same check as cookie-import)
for (const fp of filePaths) {
if (!fs.existsSync(fp)) throw new Error(`File not found: ${fp}`);
if (path.isAbsolute(fp)) {
let resolvedFp: string;
try { resolvedFp = fs.realpathSync(path.resolve(fp)); } catch (err: any) { if (err?.code !== 'ENOENT') throw err; resolvedFp = path.resolve(fp); }View on GitHub (pinned to 94993f7401)
Solutions
- Provide a full UA string, quoting it so the shell does not split it: `browse useragent "Mozilla/5.0 ..."`.
- If you want the default Playwright UA back, recreate the context without a custom UA via the appropriate BrowserManager path rather than passing empty.
- Capture the UA from `navigator.userAgent` BEFORE any context recreation so the value is populated.
- If scripting, assert the UA variable is non-empty before dispatching the command.
Example fix
// before
const ua = readUaMaybe(); // may be ''
await runBrowseCommand(['useragent', ua]);
// after
const ua = await page.evaluate(() => navigator.userAgent);
if (!ua) throw new Error('UA read failed');
await runBrowseCommand(['useragent', ua]); Defensive patterns
Strategy: validation
Validate before calling
function validateUserAgentArg(args: string[]): string {
const ua = args.join(' ').trim();
if (!ua) throw new Error('useragent requires a non-empty UA string');
return ua;
} Type guard
function isNonEmptyString(s: unknown): s is string {
return typeof s === 'string' && s.trim().length > 0;
} Prevention
- Quote the full UA string so the shell does not split it.
- Capture navigator.userAgent before context recreation, not after.
- There is no reset-to-default via this command — use the BrowserManager context path instead.
When it happens
Trigger: Calling `browse useragent` with no arguments; passing only whitespace tokens that join to an empty string; forwarding a UA variable that resolved to empty.
Common situations: An agent reads `navigator.userAgent`, intends to re-apply it after context recreation, but the read happened before navigation completed and returned empty; a user wants to RESET to the default UA and assumes omitting the value does that (it does not — there is no reset path through this command); copy-paste from a config where the UA string lived on the next line and was dropped.
Related errors
- Usage: browse viewport [<WxH>] [--scale <n>] (e.g. 375x812,
- Usage: browse cookie <name>=<value>
- Usage: browse header <name>:<value>
- Usage: browse upload <selector> <file1> [file2...]
- Usage: browse cookie-import <json-file>
AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12).
Data as JSON: /api/errors/0fde9f49ed3acd4d.
Report an issue: GitHub.