can1357/oh-my-pi · error · Error

tab.select() requires a <select> element

Error message

tab.select() requires a <select> element

What it means

The cmux tab's selector-action bridge (evaluated in the page) implements `select` only for native `<select>` elements. Before iterating `element.options` and setting `option.selected`, it checks `element.tagName !== "SELECT"` and throws this Error otherwise. It exists because callers sometimes pass an input, div-based dropdown, or wrong ref, and the browser would otherwise fail cryptically (no `.options`).

Source

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

					inputEvent(element);
					return true;
				case "uncheck":
					element.checked = false;
					inputEvent(element);
					return true;
				case "type":
					if (typeof element.focus === "function") element.focus();
					setValue(element, String(args.text || ""), true);
					return true;
				case "fill":
					if (typeof element.focus === "function") element.focus();
					setValue(element, String(args.value || ""), false);
					return true;
				case "scrollIntoView":
					return true;
				case "select": {
					const values = Array.isArray(args.values) ? args.values.map(String) : [String(args.value || "")];
					if (element.tagName !== "SELECT") throw new Error("tab.select() requires a <select> element");
					const wanted = new Set(values);
					const selected = [];
					for (const option of Array.from(element.options)) {
						option.selected = wanted.has(option.value);
						if (option.selected) selected.push(option.value);
					}
					inputEvent(element);
					return selected;
				}
				case "uploadFile": {
					if (element.tagName !== "INPUT" || element.type !== "file") {
						throw new Error("tab.uploadFile() requires an <input type=file> element");
					}
					const transfer = new DataTransfer();
					for (const file of args.files || []) {
						const bytes = Uint8Array.from(atob(file.data), char => char.charCodeAt(0));
						transfer.items.add(new File([bytes], file.name, { type: file.type || "application/octet-stream" }));
					}

View on GitHub (pinned to 9690622007)

Solutions

  1. Verify the target element with tab.evaluate/getElement that it really is a <select> (check tagName), or re-query with a more specific selector.
  2. For custom (div-based) dropdowns, use click actions to open the dropdown and click the option instead of the select action.
  3. If using `<input list>` comboboxes, set `.value` directly via the setValue path instead of the select action.

Example fix

// before
tab.select('div.dropdown-menu', ['option-a']);
// after
const tag = await tab.evaluate(`document.querySelector('div.dropdown-menu').tagName`);
if (tag === 'SELECT') tab.select('div.dropdown-menu', ['option-a']);
else { await tab.click('div.dropdown-menu'); await tab.click('text=Option A'); }
Defensive patterns

Strategy: validation

Validate before calling

const tag = await tab.evaluate(`document.querySelector(${JSON.stringify(sel)})?.tagName`);
if (tag !== 'SELECT') throw new Error(`select() needs a <select>, got <${tag ?? 'none'}>`);

Type guard

function isSelectElement(el): el is HTMLSelectElement { return el instanceof HTMLSelectElement || el?.tagName === 'SELECT'; }

Prevention

When it happens

Trigger: Calling the select action with a selector/ref that resolves to a non-<select> element: a custom JS dropdown, an `<input list=...>`, or a stale ref that now points at a wrapper element.

Common situations: Automating pages that use component-library dropdowns (React-Select, Material UI) instead of native selects; refs resolved before the DOM hydrated and now stale; copying a working tab.select() call to a page with a different control.

Related errors


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