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

CmuxTab.uploadFile(selector, ...filePaths) is a variadic API that base64-encodes each path and ships the payloads to the cmux daemon. It throws this ToolError synchronously when zero file paths are supplied, because the underlying browser.uploadFile request would have nothing to set.

Source

Thrown at packages/coding-agent/src/tools/browser/cmux/cmux-tab.ts:716

				const dispatch = (type, point) => target.dispatchEvent(new MouseEvent(type, {
					bubbles: true,
					cancelable: true,
					view: window,
					clientX: point.x,
					clientY: point.y,
					buttons: type === "mouseup" ? 0 : 1,
				}));
				dispatch("mousemove", points.start);
				dispatch("mousedown", points.start);
				dispatch("mousemove", points.end);
				dispatch("mouseup", points.end);
				return true;
			})()`,
		);
	}

	async uploadFile(selector: string, ...filePaths: string[]): Promise<void> {
		if (!filePaths.length) throw new ToolError("tab.uploadFile() requires at least one file path");
		const files: FilePayload[] = [];
		for (const filePath of filePaths) {
			const absolute = resolveToCwd(filePath, this.#requireRunContext("tab.uploadFile()").session.cwd);
			const file = Bun.file(absolute);
			const data = Buffer.from(await file.arrayBuffer()).toString("base64");
			files.push({ name: path.basename(absolute), type: file.type || "application/octet-stream", data });
		}
		await this.#selectorAction(selector, "uploadFile", { files });
	}

	async waitForResponse(
		pattern: string | RegExp | ((response: CmuxResponse) => boolean | Promise<boolean>),
		opts?: { timeout?: number },
	): Promise<CmuxResponse> {
		const timeoutMs = opts?.timeout ?? this.#runContext?.timeoutMs ?? 30_000;
		const signal = this.#runContext?.signal;
		await this.#installResponseObserver();
		const startId = await this.#responseCursor();

View on GitHub (pinned to 9690622007)

Solutions

  1. Pass at least one file path: tab.uploadFile(selector, "/path/to/file.png").
  2. Guard the call site: check files.length > 0 before invoking uploadFile.
  3. If zero files is a legitimate state, skip the upload (or clear the input with tab.evaluate) instead of calling uploadFile.
  4. Verify the file-collection step actually produced paths (log/validate the array before spreading).

Example fix

// before: spreads possibly-empty array
await tab.uploadFile("#avatar", ...filePaths);
// after: guard the empty case
if (filePaths.length === 0) throw new Error("no files to upload");
await tab.uploadFile("#avatar", ...filePaths);
Defensive patterns

Strategy: validation

Validate before calling

if (!Array.isArray(filePaths) || filePaths.length === 0) {
  throw new Error("uploadFile needs at least one file path");
}

Type guard

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

Prevention

When it happens

Trigger: Calling tab.uploadFile('#input') with no path arguments — typically when the caller's file list came from an empty array spread (tab.uploadFile(selector, ...files)) or from a variable that was undefined/empty.

Common situations: Spreading a dynamically collected file list that the collection step failed to populate; tool/agent code passing an optional files parameter straight through; refactoring from a single-path signature to variadic and dropping the argument.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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