← reports

#3246 · Pi bridge omits the native prefix for selected skills

Bug Medium Effort: Low providers provider-pi open on GitHub 2026-09-08 · base 06aeaa9

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

ClaimStatusEvidence
Selecting a skill in BB sends the displayed slash token to Pi rather than Pi’s native skill command.VerifiedThe 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.VerifiedThe 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.VerifiedThe 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.VerifiedThe 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.RefutedThe fetched origin/main commit is the tested base, and there are no later changes to the relevant paths.

3. Environment

4. Minimal reproduction

  1. Check out trusted commit 06aeaa994942ae7527dc49d2268c1f801e8542a0.
  2. Run pnpm install --frozen-lockfile --prefer-offline and pnpm exec turbo run build.
  3. Save the linked test as plugins/provider-pi/src/bridge/bridge.skill-command.test.ts.
  4. 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.