Skip to content

Extension tool registered with a built-in tool's name never replaces it in the final tool list #9071

Description

@Qihuanxishini

Extension tool with the same name as a built-in tool never replaces it in the final tool list

Summary

On pi 0.84.4, when an extension registers a tool via pi.registerTool() using a name that collides with a built-in tool (grep, find), the registration is accepted (the definition lands in the extension's tools map), but the built-in definition stays in effect: runtime.getAllTools() and session.agent.state.tools keep returning the built-in definition, and the LLM's system prompt therefore contains the built-in tool. A tool registered by the same extension under a distinct name works fine, so the failure condition is specifically the name collision with a built-in tool.

Environment

  • pi 0.84.4 (@earendil-works/pi-coding-agent), Windows 11, Node 24.18.0
  • Extension used for reproduction: @ff-labs/pi-fff 0.10.6 (an extension that registers FFF-powered grep/find replacements)
  • Reproduced with both the interactive TUI and the SDK (createAgentSession)

Repro steps

  1. pi install npm:@ff-labs/pi-fff
  2. Set the extension to its override mode, which registers tools under the built-in names — create ~/.pi/agent/pi-fff.json:
    { "mode": "override" }
    (In this mode the extension calls pi.registerTool() with name: "grep" and name: "find", plus pi.setActiveTools().)
  3. Start a session and inspect the tool list the LLM receives.

Observed

  • The LLM's tool list contains the built-in grep (description: "Search file contents for a pattern...", params pattern,path,glob,ignoreCase,literal,context,limit) — the extension's definition never reaches it.
  • The extension's own registration did succeed: inspecting the loaded extension instance shows its tools map contains "grep" and "find" with the correct extension definitions (description: "Grep file contents. Smart-case...", params pattern,path,exclude,caseSensitive,context,limit,cursor).
  • runtime.getAllTools() (which reads the merged _toolDefinitions registry) returns the built-in grep definition.
  • Switching the same extension to its tools-and-ui mode (registers the identical tool definitions under distinct names ffgrep/fffind) makes them appear in state.tools immediately — the registration code path is otherwise identical, only the tool names differ.

SDK probe that shows the mismatch:

import { createAgentSession } from "@earendil-works/pi-coding-agent";

const result = await createAgentSession();
const { session, extensionsResult } = result;

// Trigger the extension's registration path (its session_start/before_agent_start handler)
try { await session.prompt("ok"); } catch {}
await new Promise((r) => setTimeout(r, 3000));

const fffExt = extensionsResult.extensions.find((e) => String(e.path).includes("pi-fff"));
for (const [name, entry] of fffExt.tools) {
  console.log(`extension registered [${name}]: ${entry.definition.description.slice(0, 60)}`);
}
const g = session.agent.state.tools.find((x) => x.name === "grep");
console.log("state.tools grep:", g.description.slice(0, 60));

Output with mode: "override":

extension registered [grep]: Grep file contents. Smart-case, auto-detects regex vs litera
extension registered [find]: Fuzzy path search and glob search. Matches against the whole
state.tools grep: Search file contents for a pattern. Returns matching lines with file p

The extension's definitions are present in the extension's tools map, but the merged registry and state.tools keep the built-in versions.

Expected

Based on the merge logic in _refreshToolRegistry — toolRegistry is built from built-in definitions first, then extension tools via toolRegistry.set(tool.name, tool) — an extension tool registered later under the same name should replace the built-in one in state.tools. That is also what this extension (and its documented "override" mode) relies on.

Notes

  • No other extension on this machine registers tools named grep/find, so this is not a first-registered-wins collision between extensions.
  • The extension registers its tools from session_start / before_agent_start handlers (deferred registration), which per the extensions docs should be a supported registration point.
  • Happy to provide more traces (e.g. of _refreshToolRegistry / setActiveToolsByName invocations) if useful.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

no-actionThis issue has been rejected after triage

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions