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
- Verify the target element with tab.evaluate/getElement that it really is a <select> (check tagName), or re-query with a more specific selector.
- For custom (div-based) dropdowns, use click actions to open the dropdown and click the option instead of the select action.
- 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
- Check tagName before element-specific actions.
- Treat component-library dropdowns as click targets, not native selects.
- Re-resolve refs after DOM updates to avoid stale-element mismatches.
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
- tab.uploadFile() requires an <input type=file> element
- tab.ariaSnapshot: selector ${__sel} matched no element
- Element handle selector no longer resolves
- No element matched ${spec.raw}
- tab.waitFor(${JSON.stringify(selector)}) timed out after ${t
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/e46d1f57c95f0152.
Report an issue: GitHub.