can1357/oh-my-pi · error

RPC host tool names must be unique

Error message

RPC host tool names must be unique

What it means

#applyRpcHostToolRefresh validates an incoming batch of RPC-provided tools before merging them into the registry. If two or more tools in the same batch share a name, the refresh cannot build an unambiguous registry and throws. The registry requires globally unique tool names because the model dispatches tools by name.

Solutions

  1. Deduplicate the tool list before sending it: filter by name so each name appears once.
  2. If you need two variants of a tool, give them distinct names (e.g. 'search_files' vs 'search_files_deep').
  3. Fix the RPC host side to send a fresh, unique array rather than appending to a previous list.

Example fix

// before
await session.setRpcTools([...tools, ...tools]);
// after
const unique = [...new Map(tools.map(t => [t.name, t])).values()];
await session.setRpcTools(unique);
Defensive patterns

Strategy: validation

Validate before calling

const names = tools.map(t => t.name);
if (new Set(names).size !== names.length) {
  throw new Error('duplicate RPC tool names: ' + names.filter((n, i) => names.indexOf(n) !== i));
}

Try / catch

try {
  await session.setRpcTools(tools);
} catch (err) {
  if (err.message === 'RPC host tool names must be unique') {
    const deduped = [...new Map(tools.map(t => [t.name, t])).values()];
    await session.setRpcTools(deduped);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling the RPC host tool refresh (setRpcTools / equivalent) with an AgentTool[] where `rpcTools.map(t => t.name)` contains duplicates (session-tools.ts:1824-1827).

Common situations: See trigger scenarios.

Related errors


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

Appendix: source

Thrown at packages/coding-agent/src/session/session-tools.ts:1825

			await this.#applyActiveToolsByName(nextActive);
			if (this.#host.isDisposed()) restorePreviousMcpTools();
		} catch (error) {
			restorePreviousMcpTools();
			throw error;
		}
	}

	/** Replaces RPC host-owned tools and refreshes the active set before the next model call. */
	refreshRpcHostTools(rpcTools: AgentTool[]): Promise<void> {
		const snapshot = [...rpcTools];
		return this.runToolRegistryMutation(() => this.#applyRpcHostToolRefresh(snapshot));
	}

	async #applyRpcHostToolRefresh(rpcTools: AgentTool[]): Promise<void> {
		const nextToolNames = rpcTools.map(tool => tool.name);
		const uniqueToolNames = new Set(nextToolNames);
		if (uniqueToolNames.size !== nextToolNames.length) {
			throw new Error("RPC host tool names must be unique");
		}

		for (const name of uniqueToolNames) {
			if (this.#toolRegistry.has(name) && !this.#rpcHostToolNames.has(name)) {
				throw new Error(`RPC host tool "${name}" conflicts with an existing tool`);
			}
		}

		const previousRpcHostToolNames = new Set(this.#rpcHostToolNames);
		const previousActiveToolNames = this.getEnabledToolNames();
		const previousRpcHostTools = new Map(
			[...previousRpcHostToolNames].flatMap(name => {
				const tool = this.#toolRegistry.get(name);
				return tool ? [[name, tool] as const] : [];
			}),
		);
		for (const name of previousRpcHostToolNames) {
			this.#toolRegistry.delete(name);

View on GitHub (pinned to 9690622007)