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
-
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
---
-
Start a BB thread with the Pi provider.
-
Select the example skill from BB's / command picker and send a prompt with any arguments.
-
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
Summary
Pi-native skills declared with
disable-model-invocation: truecannot 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
dba32a469fd820ff6106715db0aaf6ed297d79a5Steps to reproduce
Create or use a Pi-native skill in one of Pi's normal skill roots, such as
~/.pi/agent/skills/example/SKILL.md, with:Start a BB thread with the Pi provider.
Select the
exampleskill from BB's/command picker and send a prompt with any arguments.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
/exampleand the Pi bridge forwards only that plain text. Pi's native command syntax is/skill:example; therefore the skill is not loaded. Becausedisable-model-invocation: truealso hides it from the model-visible catalog, there is no fallback path: every affected skill is unavailable in BB Pi threads.Evidence
disable-model-invocation: trueas requiring the user to invoke/skill:name: https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/skills.md#skill-commandsname, rather than provider-native invocation syntax:bb/apps/server/src/services/threads/provider-command-typeahead.ts
Lines 49 to 58 in dba32a4
bb/plugins/provider-pi/src/bridge/bridge.ts
Lines 936 to 956 in dba32a4
bb/plugins/provider-pi/src/bridge/bridge.ts
Lines 1164 to 1187 in dba32a4
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/namedisplay token. Add regression coverage for a Pi-nativedisable-model-invocation: trueskill selected from the BB command picker.What was ruled out
/skill:namesyntax.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.