can1357/oh-my-pi · error · ToolError
multiple windows match
Error message
multiple windows match ${JSON.stringify(selector)}:\n${candidates} What it means
Thrown by the computer tool's desktop.window(selector) API when the selector matches more than one open window. Because a Win handle must refer to exactly one window, the tool refuses to guess and lists every matching candidate (id, app, title) in the message so the caller can disambiguate.
Solutions
- Narrow the selector: add the exact window title or use the specific window id from the candidate list printed in the error.
- Call desktop.windows(filter) to enumerate matches and pick one programmatically (e.g. the focused one or by title substring).
- Add more filter criteria (app plus title) until exactly one window matches.
- If operating on any matching window is fine, take matches[0] from windows() instead of using window().
Example fix
// before
const win = await desktop.window({app: 'Chrome'});
// after
const chromeWins = await desktop.windows({app: 'Chrome'});
const win = await desktop.window(chromeWins.find(w => w.title.includes('Docs'))!.id); Defensive patterns
Strategy: validation
Validate before calling
const matches = await desktop.windows(selector);
if (matches.length > 1) throw new Error(`ambiguous: ${matches.length} windows match`);
if (matches.length === 0) throw new Error('no match');
const win = await desktop.window(matches[0].id); Try / catch
try {
const win = await desktop.window(selector);
} catch (err) {
if (String(err?.message).startsWith('multiple windows match')) {
const matches = await desktop.windows(selector);
const win = await desktop.window(matches[0].id); // or pick by title
} else throw err;
} Prevention
- Filter by unique window id, not app or partial title.
- Combine app + exact title filters to guarantee uniqueness.
- Enumerate with windows() first and assert length === 1.
- Avoid selectors that are valid on single-window dev machines but ambiguous in real sessions.
When it happens
Trigger: Calling desktop.window({app: 'Chrome'}) with multiple Chrome windows open; calling desktop.window({title: 'Untitled'}) matching several documents; any filter (or id-less selection) that is not unique across the current window list.
Common situations: Browsers and editors commonly have many windows with similar titles; automation written against a single-window machine breaks on a multi-window desktop; partial title filters that were unique during development match several windows in production.
Related errors
- no window matches
- agent:// URL cannot combine path extraction with ?q=
- Assertion failed
- Computer session is closed
- Computer session is closed
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/e1e7d25550b05ab1.
Report an issue: GitHub.
Appendix: source
Thrown at packages/coding-agent/src/tools/computer/worker.ts:685
windows: async (filter?: WindowFilter): Promise<DesktopWindow[]> => {
const { signal } = getContext();
return (await nativeCall(signal, () => session.listWindows())).filter(window =>
matchesFilter(window, filter),
);
},
window: async (selector: string | WindowFilter): Promise<Win> => {
const { signal } = getContext();
const windows = await nativeCall(signal, () => session.listWindows());
const matches =
typeof selector === "string"
? windows.filter(window => window.id === selector)
: windows.filter(window => matchesFilter(window, selector));
if (matches.length === 0) throw new ToolError(`no window matches ${JSON.stringify(selector)}`);
if (matches.length > 1) {
const candidates = matches
.map(window => `${window.id} ${window.app} ${JSON.stringify(window.title)}`)
.join("\n");
throw new ToolError(`multiple windows match ${JSON.stringify(selector)}:\n${candidates}`);
}
return makeWin(matches[0]!);
},
focusedWindow: async (): Promise<Win | null> => {
const { signal } = getContext();
const window = (await nativeCall(signal, () => session.listWindows())).find(candidate => candidate.focused);
return window ? makeWin(window) : null;
},
screenshot: (options?: ScreenshotOptions) => captureScreenshot(session, getContext, "desktop", options),
click: desktopTarget.click.bind(desktopTarget),
doubleClick: desktopTarget.doubleClick.bind(desktopTarget),
move: desktopTarget.move.bind(desktopTarget),
drag: desktopTarget.drag.bind(desktopTarget),
scroll: desktopTarget.scroll.bind(desktopTarget),
type: desktopTarget.type.bind(desktopTarget),
press: desktopTarget.press.bind(desktopTarget),
elementAt: async (x: number, y: number): Promise<El | null> => {
const { signal } = getContext();View on GitHub (pinned to 9690622007)