can1357/oh-my-pi · error · ToolError

Drag ${role} selector did not resolve: ${target}

Error message

Drag ${role} selector did not resolve: ${target}

What it means

The drag helper resolves each endpoint (from/to) to a point: string targets are resolved through ARIA-ref or CSS page.$. If the selector matches no element, this ToolError names which endpoint (from or to) failed and echoes the target. The drag is aborted before any mouse events are dispatched.

Source

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

			});
			output.push({ type: "text", text: lines.join("\n") });
			output.push({ type: "image", data: resized.data, mimeType: resized.mimeType });
		}
		return dest;
	}

	async #drag(from: DragTarget, to: DragTarget, signal: AbortSignal): Promise<void> {
		const page = this.#requirePage();
		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"
			) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-run tab.observe() to refresh aria refs, then retry the drag with current refs.
  2. Validate both selectors resolve (document.querySelector) on the live page before dragging.
  3. If the target is inside an iframe or shadow DOM, scope the selector correctly or use explicit { x, y } coordinates.
  4. Use coordinate points { x, y } from a fresh screenshot when selectors are unstable.

Example fix

// before
await tab.drag({ from: "#item-3", to: "#trash" }); // #item-3 removed after re-render
// after
await tab.observe();
const box = await tab.evaluate(() => document.querySelector("#trash")?.getBoundingClientRect());
await tab.drag({ from: "#item-3", to: { x: box.x + box.width / 2, y: box.y + box.height / 2 } });
Defensive patterns

Strategy: validation

Validate before calling

for (const endpoint of [from, to]) {
  if (typeof endpoint === "string") {
    const ok = await tab.evaluate((sel) => !!document.querySelector(sel), endpoint);
    if (!ok) throw new Error(`drag endpoint selector missing: ${endpoint}`);
  }
}

Type guard

function isDragTarget(t: unknown): t is string | { x: number; y: number } {
  return typeof t === "string"
    || (!!t && typeof t === "object" && typeof (t as any).x === "number" && typeof (t as any).y === "number");
}

Try / catch

try {
  await tab.drag({ from, to });
} catch (err) {
  if (err.message.includes("Drag ") && err.message.includes("did not resolve")) {
    await tab.observe();
    return tab.drag({ from: resolveFresh(from), to: resolveFresh(to) });
  }
  throw err;
}

Prevention

When it happens

Trigger: tab.drag({ from: "#src", to: "#dst" }) where either selector matches nothing, or an expired aria-ref id is supplied for either endpoint.

Common situations: Drag source/target removed by a re-render between observe and drag; selector typo or wrong document/frame; HTML5 drag targets that exist visually but are re-created dynamically on hover; stale aria ref after page navigation.

Related errors


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