Skip to content

Pi native skills with disable-model-invocation cannot be invoked from BB #3246

Description

@IlyaM

Summary

Pi-native skills declared with disable-model-invocation: true cannot be invoked through BB's Pi provider. The flag intentionally hides a skill from Pi's model-visible catalog, so an explicit invocation is the only supported way to load it; BB's command picker emits /name, but Pi requires /skill:name.

This differs from #2318: the affected skills are Pi-native skills discovered by Pi, not BB-managed skills injected through BB's skill system.

Versions and environment

  • bb 0.42.1, run from source at dba32a469fd820ff6106715db0aaf6ed297d79a5
  • Pi 0.85.0
  • Linux (kernel 7.0.0); Node 22.22.1
  • Pi provider, OpenAI Codex model

Steps to reproduce

  1. Create or use a Pi-native skill in one of Pi's normal skill roots, such as ~/.pi/agent/skills/example/SKILL.md, with:

    ---
    name: example
    description: Test explicit skill invocation.
    disable-model-invocation: true
    ---
  2. Start a BB thread with the Pi provider.

  3. Select the example skill from BB's / command picker and send a prompt with any arguments.

  4. Inspect the sent input with bb thread log <thread-id> --format json --all.

Expected vs actual

Expected: Selecting a Pi-native skill in BB invokes Pi's explicit skill command, loading the skill and passing the remaining text as its arguments. For a hidden skill, this is necessary because Pi intentionally excludes it from the model prompt.

Actual: BB serializes the selected command as /example and the Pi bridge forwards only that plain text. Pi's native command syntax is /skill:example; therefore the skill is not loaded. Because disable-model-invocation: true also hides it from the model-visible catalog, there is no fallback path: every affected skill is unavailable in BB Pi threads.

Evidence

  • Pi documents disable-model-invocation: true as requiring the user to invoke /skill:name: https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/skills.md#skill-commands
  • BB's command catalog represents a skill with its bare name, rather than provider-native invocation syntax:
    function toSkillCommand(entry: ResolvedSkillCatalogEntry): ProviderCommand {
    const { provenance, runtimeSource } = entry;
    return {
    name: runtimeSource.name,
    source: "skill",
    origin: provenance.kind === "project" ? "project" : "user",
    description: runtimeSource.description,
    argumentHint: null,
    ...(provenance.kind === "plugin" ? { pluginId: provenance.pluginId } : {}),
    };
  • The Pi bridge discards structured command-mention metadata while extracting input and forwards only text to Pi:
    function startPiPrompt(
    threadSession: ThreadSession,
    threadId: string,
    text: string,
    images: ImageContent[],
    ): Promise<void> {
    const dispatch = threadSession.session.prompt(
    text,
    images.length > 0 ? images : undefined,
    );
    void dispatch.settled.then((outcome) => {
    if (outcome === null) {
    return;
    }
    reportPromptSettled({
    ...(outcome.error !== undefined ? { error: outcome.error } : {}),
    sessionSerial: threadSession.sessionSerial,
    threadId,
    });
    });
    return dispatch.consumed;
    and
    function extractInput(input: TurnStartParams["input"]): ExtractedInput {
    const chunks: string[] = [];
    const images: ImageContent[] = [];
    for (const item of input) {
    if (!item || typeof item !== "object") continue;
    const typed = item as {
    type?: string;
    text?: string;
    path?: string;
    mimeType?: string;
    };
    if (typed.type === "text" && typeof typed.text === "string") {
    chunks.push(typed.text);
    } else if (typed.type === "localImage" && typeof typed.path === "string") {
    try {
    const data = readFileSync(typed.path).toString("base64");
    const mimeType = typed.mimeType ?? mimeTypeFromExtension(typed.path);
    images.push({ type: "image", data, mimeType });
    } catch {}
    } else if (typed.type === "localFile" && typeof typed.path === "string") {
    chunks.push(`[Attached file: ${typed.path}]`);
    }
    }
    return { text: chunks.length > 0 ? chunks.join("\n") : undefined, images };
  • I did not run a separate live reproduction during this diagnosis-only report; the report is based on the reported behavior and read-only source inspection. No public-safe investigation-thread link is available.

Suggested fix

Preserve a selected skill command as structured invocation data until the Pi adapter. For Pi-native skills, translate the selected skill into Pi's /skill:<name> syntax (with the remaining prompt text as arguments), rather than forwarding BB's provider-neutral /name display token. Add regression coverage for a Pi-native disable-model-invocation: true skill selected from the BB command picker.

What was ruled out

Suggested priority and effort

High — this blocks all Pi-native skills that intentionally require explicit invocation, including safety-sensitive workflows. No in-BB workaround is known beyond removing the flag, which changes the skill's intended invocation policy.

AGENT GENERATED

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    confirmed-reproBug reproduced again from a clean trusted checkout; see linked reportprovider-piBuilt-in plugin: provider-piprovidersCross-provider bridges, models, login

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions