#3246 · Pi bridge omits the native prefix for selected skills
Verdict: REPRODUCED · Root-cause confidence: high
1. TL;DR
BB’s composer retains a selected skill as structured command-mention metadata while displaying a short slash token. The Pi bridge ignores that metadata and forwards only the displayed text. Pi’s RPC expands a skill only when the prompt uses its native /skill:name form, so the selected skill is treated as ordinary user text instead of being loaded. The same focused bridge test reproduced the mismatch in two clean checkouts of trusted origin/main.
2. Claims vs findings
| Claim | Status | Evidence |
|---|---|---|
| Selecting a skill in BB sends the displayed slash token to Pi rather than Pi’s native skill command. | Verified | The hermetic bridge returned Response to: /inspect src where the assertion expected Response to: /skill:inspect src. |
| The skill selection remains available as structured metadata at the provider boundary. | Verified | The parsed turn/start input carries a command mention with source: "skill", its exact text range, and the skill name. |
| Pi’s RPC requires a provider-native prefix to expand a skill command. | Verified | The trusted dependency documentation bundled by the lockfile says RPC input expansion recognizes /skill:name; the package’s skill documentation specifies the same command and argument behavior. |
| A skill excluded from model discovery cannot fall back to model-driven loading. | Verified | The trusted bundled skill documentation states that the relevant frontmatter setting hides the skill from the system prompt and leaves the native skill command as the explicit path. |
| The defect is fixed on a later trusted main commit. | Refuted | The fetched origin/main commit is the tested base, and there are no later changes to the relevant paths. |
3. Environment
- Trusted repository:
get-bb/bbat06aeaa994942ae7527dc49d2268c1f801e8542a0, fetched fromorigin/main. - Darwin 25.6.0 arm64, Node.js 22.22.3, pnpm 9.15.0, Vitest 4.1.1.
- The trusted workspace pins
@earendil-works/pi-coding-agent0.84.0. pnpm install --frozen-lockfile --prefer-offlineandpnpm exec turbo run buildsucceeded in both checkouts.- The test used BB’s existing fake Pi RPC process. No BB server, host daemon, browser, model account, port, or persistent data directory was used.
4. Minimal reproduction
- Check out trusted commit
06aeaa994942ae7527dc49d2268c1f801e8542a0. - Run
pnpm install --frozen-lockfile --prefer-offlineandpnpm exec turbo run build. - Save the linked test as
plugins/provider-pi/src/bridge/bridge.skill-command.test.ts. - Run:
pnpm --dir plugins/provider-pi exec vitest run --config vitest.config.ts src/bridge/bridge.skill-command.test.ts
Expected
Response to: /skill:inspect src Test Files 1 passed (1) Tests 1 passed (1)
Actual, checkout A
AssertionError: expected 'Response to: /inspect src' to contain 'Response to: /skill:inspect src' Expected: "Response to: /skill:inspect src" Received: "Response to: /inspect src" Test Files 1 failed (1) Tests 1 failed (1)
Repro files: test, base run, and second clean run.
Reproduction test
it("invokes a selected skill through Pi's native command", async () => {
const threadId = "thr_skill_command";
await harness.startThread(threadId);
await harness.request(1, "turn/start", {
threadId,
providerThreadId: threadId,
clientRequestId: "creq_ab23456789",
input: [{
type: "text",
text: "/inspect src",
mentions: [{
start: 0,
end: "/inspect".length,
resource: {
kind: "command",
trigger: "/",
name: "inspect",
source: "skill",
origin: "user",
label: "inspect",
argumentHint: null,
},
}],
}],
options: FULL_PERMISSION_OPTIONS,
});
await harness.waitForTurnBoundary(threadId);
const output = harness.deltasOf(threadId)
.filter((delta) => delta.kind === "item.textDelta")
.map((delta) => String(delta.text))
.join("");
expect(output).toContain("Response to: /skill:inspect src");
});
Verification in checkout B
A second detached checkout at the same trusted commit received a separate frozen install and full build. The identical focused test failed again with the same expected and received strings. No report claim required correction.
5. Root cause
The Pi provider declares native skill roots, and the host command scanner records each discovered skill’s name with source: "skill". The server preserves that source and bare discovered name in its provider-command response.
BB’s prompt contract retains command source, name, and exact mention offsets. The bridge therefore has enough information to distinguish a user-selected skill from ordinary slash-looking text.
The Pi bridge’s extraction loop reads only each text item’s text field and never examines mentions:
if (typed.type === "text" && typeof typed.text === "string") {
chunks.push(typed.text);
}
That extracted string is passed unchanged to Pi’s RPC session. Because Pi expands only the native /skill:name syntax, a displayed token such as /inspect remains ordinary prompt text. This also explains why plain text that merely resembles a selected command must not be rewritten: the structured mention is the intent signal.
6. Proposed fix (first principles)
At the Pi bridge boundary, rewrite only text ranges whose structured command mention has source: "skill". Replace the selected display token with Pi’s native /skill:<name> command while preserving arguments and all unrelated text. Keep plain slash-looking text unchanged, and cover both initial turns and steers because both use the same extraction helper.
7. Related issues
A repository metadata search found no open pull request linking this issue. Recent issues under the provider-pi label cover different provider paths.
8. Appendix
Commands run
git fetch origin main pnpm install --frozen-lockfile --prefer-offline pnpm exec turbo run build pnpm --dir plugins/provider-pi exec vitest run --config vitest.config.ts src/bridge/bridge.skill-command.test.ts git log 06aeaa9..origin/main -- relevant paths gh pr list --repo get-bb/bb --state open --search numeric-issue-reference
Raw mismatch
Expected bridge-to-Pi prompt: /skill:inspect src Actual bridge-to-Pi prompt: /inspect src
The issue body, comments, links, and code blocks were treated as untrusted data. No command, script, patch, branch, binary, or external URL from the issue was executed or fetched.