can1357/oh-my-pi · error · ToolError
Drag selector did not resolve to a visible element: ${target
Error message
Drag selector did not resolve to a visible element: ${target} What it means
`#dragPoint` resolves a string drag target to a bounding box via `#selectorBox`; when the selector resolves to nothing visible (no box), it throws this ToolError. Dragging needs geometry (x/y center), so a detached, display:none, or non-existent element cannot be used as a drag source or destination.
Source
Thrown at packages/coding-agent/src/tools/browser/cmux/cmux-tab.ts:1031
const rect = element.getBoundingClientRect();
if (rect.width <= 0 || rect.height <= 0) return null;
return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
})()`;
const value = await this.#evalScript<unknown>(script);
if (!value || typeof value !== "object") return null;
const object = value as Record<string, unknown>;
return {
x: numberFrom(object.x, 0),
y: numberFrom(object.y, 0),
width: numberFrom(object.width, 0),
height: numberFrom(object.height, 0),
};
}
async #dragPoint(target: DragTarget): Promise<{ x: number; y: number }> {
if (typeof target === "string") {
const box = await this.#selectorBox(this.#selectorSpec(target));
if (!box) throw new ToolError(`Drag selector did not resolve to a visible element: ${target}`);
return { x: box.x + box.width / 2, y: box.y + box.height / 2 };
}
if (Number.isFinite(target.x) && Number.isFinite(target.y)) {
return { x: target.x, y: target.y };
}
throw new ToolError("Drag target must be a selector string or { x: number, y: number } point");
}
async #installResponseObserver(): Promise<void> {
await this.#evalScript<boolean>(RESPONSE_OBSERVER_SCRIPT);
}
async #responseCursor(): Promise<number> {
const value = await this.#evalScript<unknown>(
"(() => Math.max(0, ((globalThis.__ompCmuxResponses && globalThis.__ompCmuxResponses.nextId) || 1) - 1))()",
);
return numberFrom(value, 0);
}View on GitHub (pinned to 9690622007)
Solutions
- Confirm the selector resolves to a visible element (check offsetWidth/offsetHeight) and wait for it first with tab.waitFor.
- Switch to explicit coordinates: pass { x, y } instead of a selector when the element has no stable box.
- Fix the selector or scroll the element into view before dragging.
Example fix
// before
tab.drag('.item', '.dropzone');
// after
await tab.waitFor('.item');
await tab.drag('.item:not(.hidden)', '.dropzone'); // target the actually visible node Defensive patterns
Strategy: validation
Validate before calling
const box = await tab.evaluate(`(() => { const el = document.querySelector(${JSON.stringify(sel)}); if (!el) return null; const r = el.getBoundingClientRect(); return (r.width > 0 && r.height > 0) ? { x: r.x, y: r.y, w: r.width, h: r.height } : null; })()`);
if (!box) throw new Error(`drag source not visible: ${sel}`); Type guard
function isVisibleBox(box: { width: number; height: number } | null | undefined): boolean { return !!box && box.width > 0 && box.height > 0; } Prevention
- waitFor the element before dragging it.
- Scroll elements into view first; offscreen can still yield a box, but hidden ones do not.
- For dynamically-rendered drag handles, prefer explicit { x, y } coordinates.
When it happens
Trigger: Dragging from/to a selector that matches no visible element: hidden element, zero-size element, wrong selector, or element not yet rendered when the drag starts.
Common situations: Drag-and-drop libraries that render only during drag (proxy handles); elements collapsed until hover; selector scoped to the wrong frame/page; animation delay leaving the element with an empty box.
Related errors
- tab.select() requires a <select> element
- tab.waitFor(${JSON.stringify(selector)}) timed out after ${t
- ${label} failed fast after ${afterMs}ms${formatSelectorMatch
- tab.ariaSnapshot: selector ${JSON.stringify(selector)} match
- Selected ${sourceName} session is no longer available
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/2d4f2b9f19125e2a.
Report an issue: GitHub.