can1357/oh-my-pi · error · ToolError

tab.uploadFile() requires at least one file path

Error message

tab.uploadFile() requires at least one file path

What it means

tab.uploadFile() validates its filePaths array before doing any work; an empty array cannot identify any file, so it throws this ToolError immediately. This is a pre-condition check guarding the subsequent uploadFile call.

Source

Thrown at packages/coding-agent/src/tools/browser/tab-worker.ts:2068

					}
					select.dispatchEvent(new EventCtor("input", { bubbles: true }));
					select.dispatchEvent(new EventCtor("change", { bubbles: true }));
					return selected;
				}, values),
			)) as string[];
		} finally {
			await handle.dispose().catch(() => undefined);
		}
	}

	async #uploadFile(
		selector: string,
		filePaths: string[],
		timeoutMs: number,
		signal: AbortSignal,
		session: SessionSnapshot,
	): Promise<void> {
		if (!filePaths.length) throw new ToolError("tab.uploadFile() requires at least one file path");
		const handle = await this.#resolveActionHandle(selector, timeoutMs, signal);
		try {
			const absolute = filePaths.map(filePath => resolveToCwd(filePath, session.cwd));
			const upload = handle as unknown as { uploadFile: (...paths: string[]) => Promise<void> };
			const tagName = (await untilAborted(signal, () =>
				handle.evaluate(el => (el as unknown as { tagName: string }).tagName),
			)) as string;
			if (tagName !== "INPUT")
				throw new ToolError(
					`tab.uploadFile() requires an <input type="file"> element (got <${tagName.toLowerCase()}>)`,
				);
			await untilAborted(signal, () => upload.uploadFile(...absolute));
		} finally {
			await handle.dispose().catch(() => undefined);
		}
	}

	async #waitForUrl(pattern: string | RegExp, timeout: number, signal: AbortSignal): Promise<string> {

View on GitHub (pinned to 9690622007)

Solutions

  1. Pass at least one existing file path in filePaths.
  2. Check whatever produced the list (glob, readdir) and assert it is non-empty before calling.
  3. If the upload is optional, skip the call entirely when the list is empty instead of invoking it.

Example fix

// before
const files = fs.readdirSync(dir).filter(f => f.endsWith(".csv"));
await tab.uploadFile("#file", files); // throws when dir has no .csv
// after
const files = fs.readdirSync(dir).filter(f => f.endsWith(".csv"));
if (files.length > 0) await tab.uploadFile("#file", files.map(f => path.join(dir, f)));
Defensive patterns

Strategy: validation

Validate before calling

if (!filePaths?.length) {
  throw new Error("uploadFile called with no file paths; check the file list source");
}

Type guard

function hasFiles(paths: unknown): paths is [string, ...string[]] {
  return Array.isArray(paths) && paths.length > 0 && paths.every(p => typeof p === "string");
}

Try / catch

try {
  await tab.uploadFile(selector, filePaths);
} catch (err) {
  if (err.message.includes("at least one file path")) {
    return skipUpload(); // upload was optional
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling tab.uploadFile(selector, []) — e.g. an upload list built from zero matched files, or a caller forwarding an optional paths array that was undefined-normalized to [].

Common situations: A glob/glob-like expansion produced no matches before the call; an agent omitted the files argument; config pointed at a directory with no files so the collected list was empty.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/9eec22591b74422e. Report an issue: GitHub.