Skip to content

Commit 031b2ad

Browse files
fix(converters): always allow skill tool when emitting skill stubs under from-commands
applyPermissions only scanned plugin.commands for allowedTools, so a skill-only plugin (or any plugin whose commands don't list 'skill') would produce permission.skill = "deny", immediately breaking every generated stub. When hasSkillStubs is true, unconditionally add 'skill' to the enabled set in from-commands mode.
1 parent 65e623e commit 031b2ad

2 files changed

Lines changed: 32 additions & 1 deletion

File tree

src/converters/claude-to-opencode.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export function convertClaudeToOpenCode(
116116
mcp: mcp && Object.keys(mcp).length > 0 ? mcp : undefined,
117117
}
118118

119-
applyPermissions(config, plugin.commands, options.permissions)
119+
applyPermissions(config, plugin.commands, options.permissions, skillStubs.length > 0)
120120

121121
return {
122122
pluginName: plugin.manifest.name,
@@ -381,6 +381,7 @@ function applyPermissions(
381381
config: OpenCodeConfig,
382382
commands: ClaudeCommand[],
383383
mode: PermissionMode,
384+
hasSkillStubs = false,
384385
) {
385386
if (mode === "none") return
386387

@@ -419,6 +420,12 @@ function applyPermissions(
419420
}
420421
}
421422
}
423+
// Skill stubs require the `skill` tool to load the skill at invocation time.
424+
// If we're emitting stubs, ensure `skill` is allowed even when no explicit
425+
// command listed it in allowed-tools.
426+
if (hasSkillStubs) {
427+
enabled.add("skill")
428+
}
422429
}
423430

424431
const permission: Record<string, "allow" | "deny" | Record<string, "allow" | "deny">> = {}

tests/converter.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,30 @@ describe("convertClaudeToOpenCode", () => {
163163
expect(parsed.data.mode).toBe("subagent")
164164
})
165165

166+
test("from-commands mode: skill tool is allowed when skill stubs are emitted", async () => {
167+
// Regression: applyPermissions only scanned plugin.commands for allowedTools,
168+
// so a plugin with no explicit commands (or none listing "skill") would get
169+
// permission.skill = "deny", causing every generated stub to fail immediately.
170+
const plugin = await loadClaudePlugin(fixtureRoot)
171+
// Build a minimal plugin with skills but no explicit commands.
172+
const skillOnlyPlugin: ClaudePlugin = {
173+
...plugin,
174+
commands: [],
175+
}
176+
const bundle = convertClaudeToOpenCode(skillOnlyPlugin, {
177+
agentMode: "subagent",
178+
inferTemperature: false,
179+
permissions: "from-commands",
180+
})
181+
182+
// Skill stubs should be emitted (skill-one is opencode-eligible)
183+
expect(bundle.commandFiles.some((f) => f.name === "skill-one")).toBe(true)
184+
185+
// The skill tool must be allowed so the stubs can actually load the skill
186+
const permission = bundle.config.permission as Record<string, string>
187+
expect(permission.skill).toBe("allow")
188+
})
189+
166190
test("normalizes models and infers temperature", async () => {
167191
const plugin = await loadClaudePlugin(fixtureRoot)
168192
const bundle = convertClaudeToOpenCode(plugin, {

0 commit comments

Comments
 (0)