From 4512a83f4966fb73d6538bdd3ca2a3073ced3412 Mon Sep 17 00:00:00 2001 From: Behruz Nassre Esfahani Date: Wed, 17 Jun 2026 20:45:08 -0700 Subject: [PATCH 1/5] fix(#1394): exclude Skill/SlashCommand from the Gemini agent tool converter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit convertGeminiToolName lowercased any unmapped Claude tool, so Skill and SlashCommand became an invalid `skill`/`slashcommand` tool name. Gemini CLI has no such built-in tool, so frontmatter validation failed (tools.N: Invalid tool name) and aborted the entire agent load — 22 of 34 GSD agents were dead on Gemini. Add Skill and SlashCommand to the same `return null` exclusion branch that already handles AskUserQuestion, in both the canonical src converter and the hand-maintained bin/install.js copy that runs on the live --gemini install path. Antigravity reuses this converter (it runs on the Gemini backend) and is intentionally covered by the same exclusion — it surfaces GSD skills via the skill surface (SKILL.md), not the agent tools: allowlist — locked by an added Antigravity regression test. Co-Authored-By: Claude Opus 4.8 (1M context) --- bin/install.js | 8 +++- src/runtime-artifact-conversion.cts | 8 +++- tests/runtime-converters.test.cjs | 64 +++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/bin/install.js b/bin/install.js index 994d723ae..b37c0a83d 100755 --- a/bin/install.js +++ b/bin/install.js @@ -1545,11 +1545,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; } diff --git a/src/runtime-artifact-conversion.cts b/src/runtime-artifact-conversion.cts index fd9a8072a..024802045 100644 --- a/src/runtime-artifact-conversion.cts +++ b/src/runtime-artifact-conversion.cts @@ -1812,11 +1812,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; } diff --git a/tests/runtime-converters.test.cjs b/tests/runtime-converters.test.cjs index 3edccd6e6..1808213c3 100644 --- a/tests/runtime-converters.test.cjs +++ b/tests/runtime-converters.test.cjs @@ -18,6 +18,7 @@ const { convertClaudeToOpencodeFrontmatter, convertClaudeToKiloFrontmatter, convertClaudeToGeminiAgent, + convertClaudeAgentToAntigravityAgent, convertClaudeCommandToOpencodeSkill, convertClaudeCommandToKiloSkill, neutralizeAgentReferences, @@ -292,6 +293,69 @@ 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. + + // Direct unit assertion against the canonical converter (criterion 1). + test('convertGeminiToolName returns null for Skill and SlashCommand', () => { + const { convertGeminiToolName } = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs'); + assert.equal(convertGeminiToolName('Skill'), null, 'Skill is excluded, not lowercased to "skill"'); + assert.equal(convertGeminiToolName('SlashCommand'), null, 'SlashCommand is excluded, not lowercased to "slashcommand"'); + // Existing AskUserQuestion exclusion must still hold. + assert.equal(convertGeminiToolName('AskUserQuestion'), null, 'AskUserQuestion remains excluded'); + // Sanity: a mapped tool still converts. + assert.equal(convertGeminiToolName('Read'), 'read_file', 'mapped tools still convert'); + }); + + // Agent-level assertion against the live install path (criterion 2/3). + test('a Skill tools entry produces no skill/slashcommand in emitted frontmatter', () => { + const input = `--- +name: gsd-planner +description: Creates executable phase plans. +tools: Read, Write, Bash, Glob, Grep, Skill, WebFetch, SlashCommand +--- + + +Plan the phase. +`; + + const result = convertClaudeToGeminiAgent(input); + const frontmatter = result.split('---')[1] || ''; + + assert.ok(frontmatter.includes(' - read_file'), 'maps Read -> read_file'); + assert.ok(frontmatter.includes(' - web_fetch'), 'maps WebFetch -> web_fetch'); + assert.ok(!frontmatter.includes(' - skill'), 'does not emit invalid Gemini skill tool'); + assert.ok(!frontmatter.includes(' - slashcommand'), 'does not emit invalid Gemini slashcommand 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 +--- + +Plan the phase.`; + + 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) ───────────────────────────────────────── From ba5625bf79fa55fe9e57aca9a62d506dc4a1f7c6 Mon Sep 17 00:00:00 2001 From: Behruz Nassre Esfahani Date: Wed, 17 Jun 2026 20:45:12 -0700 Subject: [PATCH 2/5] chore(#1394): add changeset for the Gemini Skill-tool exclusion fix Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/eager-wolves-run.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/eager-wolves-run.md diff --git a/.changeset/eager-wolves-run.md b/.changeset/eager-wolves-run.md new file mode 100644 index 000000000..f14d6a010 --- /dev/null +++ b/.changeset/eager-wolves-run.md @@ -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) From a2511c132d39ba9d978f5ab6cf5f928a8889bbe5 Mon Sep 17 00:00:00 2001 From: Behruz Nassre Esfahani Date: Thu, 18 Jun 2026 07:06:00 -0700 Subject: [PATCH 3/5] test(#1394): exercise the live bin/install.js convertGeminiToolName, add ask_user assertion Addresses review: the regression unit test required the tsc build artifact (gsd-core/bin/lib/runtime-artifact-conversion.cjs) instead of the live install path. Destructure convertGeminiToolName from the existing ../bin/install.js import so a stale build can't false-green while the live copy is broken, and assert the full AskUserQuestion/ask_user exclusion group. Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/runtime-converters.test.cjs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/runtime-converters.test.cjs b/tests/runtime-converters.test.cjs index 1808213c3..bb0cad3d9 100644 --- a/tests/runtime-converters.test.cjs +++ b/tests/runtime-converters.test.cjs @@ -18,6 +18,7 @@ const { convertClaudeToOpencodeFrontmatter, convertClaudeToKiloFrontmatter, convertClaudeToGeminiAgent, + convertGeminiToolName, convertClaudeAgentToAntigravityAgent, convertClaudeCommandToOpencodeSkill, convertClaudeCommandToKiloSkill, @@ -301,13 +302,15 @@ Offer choices via AskUserQuestion when user input is needed. // validation (tools.N: Invalid tool name) and aborts the entire agent load — // previously killing 22 of 34 GSD agents on Gemini. - // Direct unit assertion against the canonical converter (criterion 1). + // Direct unit assertion against the LIVE install path (bin/install.js copy — + // the one that actually generates agents), not the tsc build artifact, so a + // stale build can't give a false-green while the live copy is broken. test('convertGeminiToolName returns null for Skill and SlashCommand', () => { - const { convertGeminiToolName } = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs'); assert.equal(convertGeminiToolName('Skill'), null, 'Skill is excluded, not lowercased to "skill"'); assert.equal(convertGeminiToolName('SlashCommand'), null, 'SlashCommand is excluded, not lowercased to "slashcommand"'); - // Existing AskUserQuestion exclusion must still hold. + // Existing AskUserQuestion/ask_user exclusion (the same if-block this PR extends) must still hold. assert.equal(convertGeminiToolName('AskUserQuestion'), null, 'AskUserQuestion remains excluded'); + assert.equal(convertGeminiToolName('ask_user'), null, 'ask_user remains excluded'); // Sanity: a mapped tool still converts. assert.equal(convertGeminiToolName('Read'), 'read_file', 'mapped tools still convert'); }); From 531aa01bc29dad632dd582e84f834f84ced189ca Mon Sep 17 00:00:00 2001 From: Behruz Nassre Esfahani Date: Sat, 20 Jun 2026 01:28:52 -0700 Subject: [PATCH 4/5] chore(#1394): reword changeset to drop product-name parenthetical MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit product-name-purity (#1777) rejects 'Gemini (…)' parentheticals that render verbatim into CHANGELOG.md. Reword to 'Gemini and Gemini-backed Antigravity'; no behavior change. (Reword was left uncommitted in the prior rebase push.) Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/eager-wolves-run.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/eager-wolves-run.md b/.changeset/eager-wolves-run.md index f14d6a010..50a4616d3 100644 --- a/.changeset/eager-wolves-run.md +++ b/.changeset/eager-wolves-run.md @@ -2,4 +2,4 @@ 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) +**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) From cc362e474f625c6cab59f0c33b7b76a45e43dbee Mon Sep 17 00:00:00 2001 From: Behruz Nassre Esfahani Date: Mon, 22 Jun 2026 08:01:28 -0700 Subject: [PATCH 5/5] test(#1394): assert Gemini tool exclusion via exported converter, not internal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The back-merge of next carried #1559/#1565 (audit installer compatibility exports), which removed convertGeminiToolName from bin/install.js's module.exports. This PR's regression test destructured it from require('../bin/install.js'), so after the merge it was undefined → "TypeError: convertGeminiToolName is not a function" across all test lanes. Rewrite the regression to assert the user-visible behavior through the still-exported convertClaudeToGeminiAgent: a Skill/SlashCommand/AskUserQuestion tools entry must not appear in the emitted Gemini frontmatter (the lowercase fallback would emit invalid tool names that abort agent load — #1394/#3362), while mapped tools (Read→read_file, WebFetch→web_fetch) survive. Folds the dropped AskUserQuestion/ask_user coverage into the behavior test and removes the internal-function import, so the test no longer depends on a private export #1559 intentionally pruned. The exclusion fix itself is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/runtime-converters.test.cjs | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/tests/runtime-converters.test.cjs b/tests/runtime-converters.test.cjs index bb0cad3d9..667ec290c 100644 --- a/tests/runtime-converters.test.cjs +++ b/tests/runtime-converters.test.cjs @@ -18,7 +18,6 @@ const { convertClaudeToOpencodeFrontmatter, convertClaudeToKiloFrontmatter, convertClaudeToGeminiAgent, - convertGeminiToolName, convertClaudeAgentToAntigravityAgent, convertClaudeCommandToOpencodeSkill, convertClaudeCommandToKiloSkill, @@ -302,25 +301,17 @@ Offer choices via AskUserQuestion when user input is needed. // validation (tools.N: Invalid tool name) and aborts the entire agent load — // previously killing 22 of 34 GSD agents on Gemini. - // Direct unit assertion against the LIVE install path (bin/install.js copy — - // the one that actually generates agents), not the tsc build artifact, so a - // stale build can't give a false-green while the live copy is broken. - test('convertGeminiToolName returns null for Skill and SlashCommand', () => { - assert.equal(convertGeminiToolName('Skill'), null, 'Skill is excluded, not lowercased to "skill"'); - assert.equal(convertGeminiToolName('SlashCommand'), null, 'SlashCommand is excluded, not lowercased to "slashcommand"'); - // Existing AskUserQuestion/ask_user exclusion (the same if-block this PR extends) must still hold. - assert.equal(convertGeminiToolName('AskUserQuestion'), null, 'AskUserQuestion remains excluded'); - assert.equal(convertGeminiToolName('ask_user'), null, 'ask_user remains excluded'); - // Sanity: a mapped tool still converts. - assert.equal(convertGeminiToolName('Read'), 'read_file', 'mapped tools still convert'); - }); - // Agent-level assertion against the live install path (criterion 2/3). - test('a Skill tools entry produces no skill/slashcommand in emitted frontmatter', () => { + // 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 +tools: Read, Write, Bash, Glob, Grep, Skill, WebFetch, SlashCommand, AskUserQuestion --- @@ -330,10 +321,15 @@ Plan the phase. 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),