Skip to content

Commit f0fb38c

Browse files
committed
fix(onboard): scope installSelected gate to skills actually presented in multiselect
Copilot review on PR #74891 noted that the new installSelected gate at src/commands/onboard-skills.ts:219 silently skipped the API-key prompt for skills that have missing bins + primaryEnv but no install options. Such skills are never offered in the install multiselect (they're filtered out of installable because install.length === 0, and out of envOnlyConfigurable because missing.bins.length > 0), so they can never be in installSelected — the gate was unconditional for them. Track presentableNames as a Set of skills that actually appeared in the multiselect, then only skip when the skill was both presented AND not picked. Skills that were never presented (the documented bin-only no-install pattern in docs/tools/skills.md) keep getting prompted for their required env key. Add a regression test that builds such a skill (install: []) and asserts the API-key prompt still fires.
1 parent f259048 commit f0fb38c

2 files changed

Lines changed: 54 additions & 1 deletion

File tree

src/commands/onboard-skills.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,4 +310,45 @@ describe("setupSkills", () => {
310310
// Install loop must not run installSkill for env-only entries.
311311
expect(mocks.installSkill).not.toHaveBeenCalled();
312312
});
313+
314+
it("still prompts for an API key for a skill with missing bins + primaryEnv but no install options (codex follow-up on PR #74891)", async () => {
315+
if (process.platform === "win32") {
316+
return;
317+
}
318+
319+
// Documented pattern in docs/tools/skills.md: a skill can require bins +
320+
// primaryEnv without an install block. Such skills are NEVER offered in
321+
// the install multiselect (they're filtered out of `installable` because
322+
// install.length === 0, and out of `envOnlyConfigurable` because
323+
// missing.bins.length > 0). The previous gate `!installSelected.has(...)`
324+
// silently skipped the API-key prompt for those skills. After the fix,
325+
// the gate only fires when the skill was actually presented in the
326+
// multiselect, so non-installable bin+env skills still get prompted for
327+
// their required env key.
328+
const noInstallSkill = createBundledSkill({
329+
name: "openai-whisper-no-install",
330+
description: "Skill that requires bins + an API key but has no install options",
331+
bins: ["nonexistent-bin"],
332+
installLabel: "unused",
333+
primaryEnv: "OPENAI_API_KEY",
334+
envMissing: ["OPENAI_API_KEY"],
335+
});
336+
// Override the install array to be empty — the documented bin-only
337+
// no-install pattern. The helper always sets a brew install entry.
338+
(noInstallSkill as { install: unknown[] }).install = [];
339+
340+
mockMissingBrewStatus([noInstallSkill]);
341+
342+
// No multiselect interaction expected — when configurable is empty the
343+
// prompter never gets called for it; the API-key prompt should still run
344+
// for the unpresentable skill below it.
345+
const { prompter } = createPrompter({ multiselect: [] });
346+
await setupSkills({} as OpenClawConfig, "/tmp/ws", runtime, prompter);
347+
348+
const confirmCalls = (
349+
prompter.confirm as unknown as { mock: { calls: Array<[{ message: string }]> } }
350+
).mock.calls;
351+
const askedForApiKey = confirmCalls.some(([arg]) => arg.message.includes("Set OPENAI_API_KEY"));
352+
expect(askedForApiKey).toBe(true);
353+
});
313354
});

src/commands/onboard-skills.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ export async function setupSkills(
8989
Boolean(skill.primaryEnv) && skill.missing.env.length > 0 && skill.missing.bins.length === 0,
9090
);
9191
const configurable = [...installable, ...envOnlyConfigurable];
92+
// Track names of skills the user actually saw in the install multiselect so
93+
// the API-key gate below only fires for skills that were genuinely offered.
94+
// Skills with missing bins + primaryEnv but no install options
95+
// (`install.length === 0` so they aren't in `installable`, and
96+
// `missing.bins.length > 0` so they aren't in `envOnlyConfigurable`) never
97+
// appear in the multiselect — those should still get API-key prompts.
98+
const presentableNames = new Set(configurable.map((skill) => skill.name));
9299
let next: OpenClawConfig = cfg;
93100
const installSelected = new Set<string>();
94101
if (configurable.length > 0) {
@@ -216,7 +223,12 @@ export async function setupSkills(
216223
if (!skill.primaryEnv || skill.missing.env.length === 0) {
217224
continue;
218225
}
219-
if (!installSelected.has(skill.name)) {
226+
// Only skip the API-key prompt when the skill was actually presented in
227+
// the install multiselect and the user did not pick it. Skills that were
228+
// never presented (bins required + primaryEnv but no install options)
229+
// still get prompted — otherwise we silently stop asking for required
230+
// env keys for a documented pattern (see docs/tools/skills.md).
231+
if (presentableNames.has(skill.name) && !installSelected.has(skill.name)) {
220232
continue;
221233
}
222234
const wantsKey = await prompter.confirm({

0 commit comments

Comments
 (0)