can1357/oh-my-pi · error · ToolError

Drag ${role} element has no bounding box (likely not visible

Error message

Drag ${role} element has no bounding box (likely not visible): ${target}

What it means

After resolving a drag endpoint selector to an element, the worker calls boundingBox(). A null box means the element is not rendered in layout (display:none, detached, or zero-size), so no drag point can be computed. The handle is disposed and this ToolError names the failing endpoint and target.

Source

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

		const resolveDragPoint = async (
			target: DragTarget,
			role: "from" | "to",
		): Promise<{ x: number; y: number; handle?: ElementHandle }> => {
			if (typeof target === "string") {
				const handle =
					parseAriaRefSelector(target) !== null
						? await this.#resolveAriaRef(target)
						: asElementHandle(await untilAborted(signal, () => page.$(normalizeSelector(target))));
				if (!handle) throw new ToolError(`Drag ${role} selector did not resolve: ${target}`);
				const box = (await untilAborted(signal, () => handle.boundingBox())) as {
					x: number;
					y: number;
					width: number;
					height: number;
				} | null;
				if (!box) {
					await handle.dispose().catch(() => undefined);
					throw new ToolError(`Drag ${role} element has no bounding box (likely not visible): ${target}`);
				}
				return { x: box.x + box.width / 2, y: box.y + box.height / 2, handle };
			}
			if (
				target !== null &&
				typeof target === "object" &&
				typeof (target as { x: unknown }).x === "number" &&
				typeof (target as { y: unknown }).y === "number"
			) {
				return { x: (target as { x: number }).x, y: (target as { y: number }).y };
			}
			throw new ToolError(
				`Drag ${role} must be a selector string or { x: number, y: number } point. Got: ${typeof target}`,
			);
		};
		const start = await resolveDragPoint(from, "from");
		let end: { x: number; y: number; handle?: ElementHandle } | undefined;
		try {

View on GitHub (pinned to 9690622007)

Solutions

  1. Make the element visible before dragging (open its container/accordion, remove display:none).
  2. Verify the element is attached and has size via getBoundingClientRect() before calling drag.
  3. Use { x, y } coordinates of a visible proxy element instead of the hidden one.
  4. For offscreen elements, scroll the container first, re-observe, then drag with the fresh ref.

Example fix

// before
await tab.drag({ from: "#hidden-handle", to: "#drop" }); // #hidden-handle is display:none
// after
await tab.evaluate(() => { document.querySelector("#panel").classList.add("open"); });
await tab.drag({ from: "#hidden-handle", to: "#drop" });
Defensive patterns

Strategy: validation

Validate before calling

const visible = await tab.evaluate((sel) => {
  const el = document.querySelector(sel);
  return !!el && !!el.getBoundingClientRect().width && !!el.getBoundingClientRect().height;
}, selector);
if (!visible) throw new Error(`drag endpoint not visible: ${selector}`);

Try / catch

try {
  await tab.drag({ from, to });
} catch (err) {
  if (err.message.includes("no bounding box")) {
    await tab.evaluate((sel) => (document.querySelector(sel))?.scrollIntoView(), targetSelector);
    return tab.drag({ from, to });
  }
  throw err;
}

Prevention

When it happens

Trigger: tab.drag() with a selector endpoint whose element has no layout box: hidden via display:none/visibility:hidden, detached from the document, inside a collapsed container, or the element exists only in another frame so its box cannot be computed.

Common situations: Dragging from a hidden template node; target inside a closed accordion/modal; element scrolled far offscreen in a virtualized list and recycled; CSS media/state (e.g. hover-only menus) hiding the element at call time.

Related errors


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