garrytan/gstack · warning · Error
Usage: browse upload <selector> <file1> [file2...]
Error message
Usage: browse upload <selector> <file1> [file2...]
What it means
Thrown by `browse upload` when no selector is supplied OR when no file paths remain after the selector. The command destructures `[selector, ...filePaths]` from args; both a non-empty selector and at least one file path are mandatory because Playwright's `setInputFiles` needs a target `<input type=file>` and the files to set.
Source
Thrown at browse/src/write-commands.ts:596
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); }
if (!SAFE_DIRECTORIES.some(dir => isPathWithin(resolvedFp, dir))) {
throw new Error(`Path must be within: ${SAFE_DIRECTORIES.join(', ')}`);
}
}
if (path.normalize(fp).includes('..')) {
throw new Error('Path traversal sequences (..) are not allowed');
}
}
const resolved = await session.resolveRef(selector);
if ('locator' in resolved) {View on GitHub (pinned to 94993f7401)
Solutions
- Provide both a selector and at least one file: `browse upload input[type=file] /tmp/errlookup-0o5UJv/upload.png`.
- If the selector contains spaces, quote it so it stays a single arg: `browse upload "input[data-uploader]" file.png`.
- For multi-file inputs, pass multiple paths: `browse upload #files a.png b.png`.
- Confirm the selector targets an `<input type=file>` — `setInputFiles` only works on file inputs.
Example fix
// before await runBrowseCommand(['upload', '/tmp/errlookup-0o5UJv/upload.png']); // after await runBrowseCommand(['upload', 'input[type=file]', '/tmp/errlookup-0o5UJv/upload.png']);
Defensive patterns
Strategy: validation
Validate before calling
function validateUploadArgs(args: string[]): { selector: string; filePaths: string[] } {
const [selector, ...filePaths] = args;
if (!selector || filePaths.length === 0) {
throw new Error('upload requires <selector> <file1> [file2...]');
}
return { selector, filePaths };
} Type guard
function isUploadArgs(args: unknown): args is [string, string, ...string[]] {
return Array.isArray(args) && typeof args[0] === 'string' && args.length >= 2;
} Prevention
- Quote selectors containing spaces so they stay a single arg.
- Target an <input type=file> — setInputFiles only works on file inputs.
- Pass multiple file paths for multi-file inputs.
When it happens
Trigger: Calling `browse upload` with no args; `browse upload file.txt` (selector omitted, so the file is misread as the selector and filePaths is empty); `browse upload input#file` (selector present but no files).
Common situations: An agent resolves the file input selector from a snapshot but the file path argument was dropped during JSON marshalling; a user expects `browse upload file.txt` to auto-locate the input element; the selector was resolved but contained a space and got split, consuming the file path as part of the selector.
Related errors
- Usage: browse viewport [<WxH>] [--scale <n>] (e.g. 375x812,
- Usage: browse cookie <name>=<value>
- Usage: browse header <name>:<value>
- Usage: browse useragent <string>
- Usage: browse cookie-import <json-file>
AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12).
Data as JSON: /api/errors/7d739486aeacfec3.
Report an issue: GitHub.