can1357/oh-my-pi · error · ToolError
Drag ${role} must be a selector string or { x: number, y: nu
Error message
Drag ${role} must be a selector string or { x: number, y: number } point. Got: ${typeof target} What it means
The drag point resolver accepts only a selector string or a { x, y } numeric point object. Anything else (number, null, array, object missing x/y) fails this ToolError, which reports the offending typeof. This is an argument-shape validation error thrown before any mouse interaction.
Source
Thrown at packages/coding-agent/src/tools/browser/tab-worker.ts:2000
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 {
end = await resolveDragPoint(to, "to");
await untilAborted(signal, () => page.mouse.move(start.x, start.y));
await untilAborted(signal, () => page.mouse.down());
await untilAborted(signal, () => page.mouse.move(end!.x, end!.y, { steps: 12 }));
await untilAborted(signal, () => page.mouse.up());
} finally {
if (start.handle) await start.handle.dispose().catch(() => undefined);
if (end?.handle) await end.handle.dispose().catch(() => undefined);
}
}
async #select(selector: string, values: string[], timeoutMs: number, signal: AbortSignal): Promise<string[]> {View on GitHub (pinned to 9690622007)
Solutions
- Pass each endpoint as a selector string or as { x: number, y: number } with both fields numeric.
- Convert other shapes yourself: arrays to { x: a[0], y: a[1] }, bbox objects already satisfy x/y.
- Check the tab.drag() signature in the tool schema and match it exactly.
- If coordinates come from computed values, validate typeof x === "number" && typeof y === "number" before the call.
Example fix
// before
await tab.drag({ from: [120, 40], to: "#drop" });
// after
await tab.drag({ from: { x: 120, y: 40 }, to: "#drop" }); Defensive patterns
Strategy: validation
Validate before calling
function assertDragPoint(p: unknown): asserts p is string | { x: number; y: number } {
if (typeof p === "string") return;
if (p && typeof p === "object" && typeof (p as any).x === "number" && typeof (p as any).y === "number") return;
throw new TypeError(`drag endpoint must be selector string or {x,y}; got ${typeof p}`);
}
assertDragPoint(from); assertDragPoint(to); Type guard
function isPoint(p: unknown): p is { x: number; y: number } {
return !!p && typeof p === "object" && typeof (p as any).x === "number" && typeof (p as any).y === "number";
} Try / catch
try {
await tab.drag({ from, to });
} catch (err) {
if (err.message.includes("must be a selector string or")) {
throw new Error(`bad drag args: from=${JSON.stringify(from)} to=${JSON.stringify(to)}`);
}
throw err;
} Prevention
- Validate endpoint shapes at the call-site boundary
- Convert arrays/other shapes to { x, y } before calling
- Match the tool schema exactly when constructing args programmatically
- Add unit tests for arg-building code paths
When it happens
Trigger: tab.drag({ from: 123, to: ... }), from: null, { from: { x: 10 } } (missing y), or passing DOM element handles/position objects from another API instead of plain { x, y }.
Common situations: Passing a bounding-box object (x,y,width,height) — that is accepted, but a point array like [10, 20] or a string of "x,y" is not; JSON args built by an LLM with the wrong shape; copying options from a different library's drag API.
Related errors
- tab.uploadFile() requires at least one file path
- Expected --${name} to be a positive integer, got ${value}
- Cache flags require --cache
- --alias requires --profile <name> or OMP_PROFILE
- goal token_budget must be a positive integer when provided
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/9d0064e83186d320.
Report an issue: GitHub.