Merge pull request #1418 from behruznassre/fix/1394-gemini-skill-tool-exclusion

fix(#1394): exclude Skill/SlashCommand from the Gemini agent tool converter
This commit is contained in:
Tom Boucher
2026-06-22 13:04:42 -04:00
committed by GitHub
4 changed files with 82 additions and 2 deletions

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 1418
---
**All GSD agents load on Gemini again** — the Claude `Skill`/`SlashCommand` tools were converted to an invalid `skill` tool that Gemini rejects, aborting the load of 22 of 34 agents. They are now excluded from the Gemini and Gemini-backed Antigravity agent `tools:` frontmatter, the same way `AskUserQuestion` already is. (#1394)

View File

@@ -1517,11 +1517,17 @@ function convertGeminiToolName(claudeTool) {
// Task/Agent: exclude — agents are auto-registered as callable tools.
// AskUserQuestion: exclude — Gemini CLI does not expose an ask_user tool;
// emitting it causes frontmatter validation errors (#3362).
// Skill/SlashCommand: exclude — Gemini CLI has no 'skill' built-in tool;
// the lowercase fallback would emit an invalid 'skill'/'slashcommand' name
// that fails frontmatter validation (tools.N: Invalid tool name) and aborts
// the entire agent load (#1394).
if (
claudeTool === 'Task' ||
claudeTool === 'Agent' ||
claudeTool === 'AskUserQuestion' ||
claudeTool === 'ask_user'
claudeTool === 'ask_user' ||
claudeTool === 'Skill' ||
claudeTool === 'SlashCommand'
) {
return null;
}

View File

@@ -1855,11 +1855,17 @@ function convertGeminiToolName(claudeTool) {
// Task/Agent: exclude — agents are auto-registered as callable tools.
// AskUserQuestion: exclude — Gemini CLI does not expose an ask_user tool;
// emitting it causes frontmatter validation errors (#3362).
// Skill/SlashCommand: exclude — Gemini CLI has no 'skill' built-in tool;
// the lowercase fallback would emit an invalid 'skill'/'slashcommand' name
// that fails frontmatter validation (tools.N: Invalid tool name) and aborts
// the entire agent load (#1394).
if (
claudeTool === 'Task' ||
claudeTool === 'Agent' ||
claudeTool === 'AskUserQuestion' ||
claudeTool === 'ask_user'
claudeTool === 'ask_user' ||
claudeTool === 'Skill' ||
claudeTool === 'SlashCommand'
) {
return null;
}

View File

@@ -18,6 +18,7 @@ const {
convertClaudeToOpencodeFrontmatter,
convertClaudeToKiloFrontmatter,
convertClaudeToGeminiAgent,
convertClaudeAgentToAntigravityAgent,
convertClaudeCommandToOpencodeSkill,
convertClaudeCommandToKiloSkill,
neutralizeAgentReferences,
@@ -292,6 +293,68 @@ Offer choices via AskUserQuestion when user input is needed.
assert.ok(!result.includes('AskUserQuestion'), 'does not leave Claude-only tool references in the body');
assert.ok(result.includes('conversational prompting'), 'uses runtime-neutral body wording for user prompts');
});
describe('#1394 regression: excludes Skill/SlashCommand from Gemini frontmatter', () => {
// Skill/SlashCommand are Claude-only tools with no Gemini built-in equivalent.
// Without explicit exclusion they hit the lowercase fallback and emit an
// invalid 'skill'/'slashcommand' tool name, which fails Gemini frontmatter
// validation (tools.N: Invalid tool name) and aborts the entire agent load —
// previously killing 22 of 34 GSD agents on Gemini.
// Agent-level assertion against the live install path (criterion 2/3).
// Asserts the emitted frontmatter rather than the internal converter so the
// test exercises the public, install-path-exported API (convertGeminiToolName
// is an internal helper, deliberately not exported per #1559). The Skill,
// SlashCommand, and AskUserQuestion inputs all exercise the exclusion if-block;
// Read/WebFetch exercise the mapped-tool path that must survive.
test('Skill/SlashCommand/AskUserQuestion are dropped from emitted Gemini frontmatter', () => {
const input = `---
name: gsd-planner
description: Creates executable phase plans.
tools: Read, Write, Bash, Glob, Grep, Skill, WebFetch, SlashCommand, AskUserQuestion
---
<role>
Plan the phase.
</role>`;
const result = convertClaudeToGeminiAgent(input);
const frontmatter = result.split('---')[1] || '';
// Mapped tools still convert (the exclusion must not break the happy path).
assert.ok(frontmatter.includes(' - read_file'), 'maps Read -> read_file');
assert.ok(frontmatter.includes(' - web_fetch'), 'maps WebFetch -> web_fetch');
// Claude-only tools with no Gemini equivalent are excluded, not lowercased
// into invalid names that would fail frontmatter validation (#1394 / #3362).
assert.ok(!frontmatter.includes(' - skill'), 'does not emit invalid Gemini skill tool');
assert.ok(!frontmatter.includes(' - slashcommand'), 'does not emit invalid Gemini slashcommand tool');
assert.ok(!frontmatter.includes(' - ask_user'), 'AskUserQuestion remains excluded');
assert.ok(!frontmatter.includes(' - askuserquestion'), 'AskUserQuestion is not lowercased into an invalid tool');
});
// Antigravity reuses convertGeminiToolName (it runs on the Gemini backend),
// so the exclusion intentionally applies there too. Antigravity surfaces GSD
// skills through the skill surface (SKILL.md), not the agent tools: allowlist,
// so dropping the invalid 'skill' tool name does not remove skill access —
// this locks that cross-runtime behavior (criterion 4).
test('Antigravity conversion also excludes Skill/SlashCommand (shared Gemini backend)', () => {
const input = `---
name: gsd-planner
description: Creates executable phase plans.
tools: Read, Write, Bash, Skill, WebFetch, SlashCommand
---
<role>Plan the phase.</role>`;
const result = convertClaudeAgentToAntigravityAgent(input);
const toolsLine = result.split('\n').find(l => l.startsWith('tools:')) || '';
assert.ok(toolsLine.includes('read_file'), 'maps Read -> read_file');
assert.ok(toolsLine.includes('web_fetch'), 'maps WebFetch -> web_fetch');
assert.ok(!/\bskill\b/.test(toolsLine), 'no invalid skill tool in Antigravity frontmatter');
assert.ok(!/\bslashcommand\b/.test(toolsLine), 'no invalid slashcommand tool in Antigravity frontmatter');
});
});
});
// ─── neutralizeAgentReferences (#766) ─────────────────────────────────────────