From 365340596d7edb7aa891f17a66aa36c57cc974e9 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Fri, 15 May 2026 13:31:31 -0400 Subject: [PATCH 1/4] fix(3562): generate Codex skill surface; $gsd-* commands discoverable after install MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #3562 Codex CLI 0.130.0 only registers commands from skills//SKILL.md; it does NOT auto-discover from get-shit-done/workflows/*.md or agents/*.md. Prior installer logic (#3427/#3433) removed the gsd-* skill copies under the assumption that Codex would discover the official skills directly. That assumption does not hold — users ended up with workflows on disk and zero $gsd-* entrypoints after restart. Fix: re-wire copyCommandsAsCodexSkills() (line 5519, already present) into the Codex install dispatch path (line 8090). Generates one ~/.codex/skills/gsd-/SKILL.md per commands/gsd/*.md — same shape the Copilot/Antigravity/Cursor/Windsurf/Augment/Trae installs use. Behaviour change: the pre-existing test in bug-3427-3433-codex-install-shape asserted "does not regenerate gsd-* skill copies". Updated it to assert the new behaviour (regenerate gsd-* with refreshed body, preserve non-gsd user skills). Tests: - New tests/bug-3562-codex-install-skill-surface.test.cjs (4 cases): - skills/gsd-help/SKILL.md exists - SKILL.md has YAML frontmatter with name: gsd-help - >= 10 gsd-* skill directories produced (lower-bound, currently 67) - Pre-existing custom-user-skill directory preserved - tests/bug-3427-3433-codex-install-shape.test.cjs: updated to assert regeneration + body refresh + unrelated-skill preservation. Verified by re-running the issue's repro: `node bin/install.js --codex --global --config-dir ` now produces 67 gsd-* skills and the target ~/.codex/skills/gsd-help/SKILL.md exists with valid frontmatter. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../3562-codex-install-skill-surface.md | 6 + bin/install.js | 30 ++-- ...bug-3427-3433-codex-install-shape.test.cjs | 14 +- ...-3562-codex-install-skill-surface.test.cjs | 128 ++++++++++++++++++ 4 files changed, 163 insertions(+), 15 deletions(-) create mode 100644 .changeset/3562-codex-install-skill-surface.md create mode 100644 tests/bug-3562-codex-install-skill-surface.test.cjs diff --git a/.changeset/3562-codex-install-skill-surface.md b/.changeset/3562-codex-install-skill-surface.md new file mode 100644 index 000000000..1e8306328 --- /dev/null +++ b/.changeset/3562-codex-install-skill-surface.md @@ -0,0 +1,6 @@ +--- +type: Fixed +pr: 0 +--- + +**Codex global install now produces discoverable `$gsd-*` skill surface** — `npx get-shit-done-cc@latest --codex --global` was leaving Codex CLI users with `get-shit-done/workflows/*.md` and `agents/gsd-*` on disk but no `~/.codex/skills/gsd-*/SKILL.md` files, so Codex 0.130.0 silently exposed zero `$gsd-*` commands after restart. The installer had been bypassing skill generation under the assumption that Codex auto-discovers from workflow/agent files; that assumption does not hold for the current Codex CLI. Re-wired the existing `copyCommandsAsCodexSkills()` helper into the Codex install dispatch path so it produces the same skill-shape as the Claude / Copilot / Antigravity / Cursor / Windsurf / Augment / Trae installs already do (one `skills/gsd-/SKILL.md` per `commands/gsd/*.md`). Pre-existing user-owned non-`gsd-*` skill directories are preserved. Closes #3562. diff --git a/bin/install.js b/bin/install.js index 2f807fb2f..8feba1034 100755 --- a/bin/install.js +++ b/bin/install.js @@ -8088,23 +8088,27 @@ function install(isGlobal, runtime = 'claude', options = {}) { failures.push('command/gsd-*'); } } else if (isCodex) { + // Codex CLI (0.130.0 at time of #3562) does NOT auto-discover commands + // from get-shit-done/workflows/*.md or agents/*.md. It only registers + // commands from skills//SKILL.md. The earlier "Codex discovers + // official skills directly" branch left users with workflows on disk and + // no $gsd-* entrypoints. Regenerate the skill surface the same way the + // other runtimes do — copyCommandsAsCodexSkills() rewrites each + // commands/gsd/*.md as ~/.codex/skills/gsd-/SKILL.md and converts + // Claude-flavored command frontmatter into Codex skill frontmatter. const skillsDir = path.join(targetDir, 'skills'); - // Codex now discovers repo/user/admin/system skills from .agents/skills and - // warns if a layer mixes redundant hook/skill representations. Legacy - // gsd-* copies under ~/.codex/skills are therefore removed and no longer - // regenerated. - let removedLegacyCodexSkills = 0; + const gsdSrc = _stageSkills(_commandsDir); + copyCommandsAsCodexSkills(gsdSrc, skillsDir, 'gsd', pathPrefix, runtime); if (fs.existsSync(skillsDir)) { - for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) { - if (!entry.isDirectory() || !entry.name.startsWith('gsd-')) continue; - fs.rmSync(path.join(skillsDir, entry.name), { recursive: true, force: true }); - removedLegacyCodexSkills += 1; + const count = fs.readdirSync(skillsDir, { withFileTypes: true }) + .filter(e => e.isDirectory() && e.name.startsWith('gsd-')).length; + if (count > 0) { + console.log(` ${green}✓${reset} Installed ${count} skills to skills/`); + } else { + failures.push('skills/gsd-*'); } - } - if (removedLegacyCodexSkills > 0) { - console.log(` ${green}✓${reset} Removed ${removedLegacyCodexSkills} legacy Codex gsd-* skill copies from skills/`); } else { - console.log(` ${dim}↳${reset} Skipped Codex skill-copy generation (Codex discovers official skills directly)`); + failures.push('skills/gsd-*'); } } else if (isCopilot) { const skillsDir = path.join(targetDir, 'skills'); diff --git a/tests/bug-3427-3433-codex-install-shape.test.cjs b/tests/bug-3427-3433-codex-install-shape.test.cjs index 6d4c5d60e..fbcccf286 100644 --- a/tests/bug-3427-3433-codex-install-shape.test.cjs +++ b/tests/bug-3427-3433-codex-install-shape.test.cjs @@ -55,7 +55,9 @@ describe('#3427 + #3433 — Codex installer avoids duplicate skills and mixed ho cleanup(tmpRoot); }); - test('removes legacy gsd-* copies from ~/.codex/skills and does not regenerate them', () => { + test('regenerates managed gsd-* skill copies and preserves unrelated user skills (#3562 reverses prior #3427/#3433 behaviour)', () => { + // Stale legacy body — fresh install must overwrite this so Codex sees the + // current SKILL.md, not whatever was last on disk. const legacySkillBody = '# old managed\n'; fs.mkdirSync(path.join(codexHome, 'skills', 'gsd-help'), { recursive: true }); fs.writeFileSync(path.join(codexHome, 'skills', 'gsd-help', 'SKILL.md'), legacySkillBody); @@ -77,7 +79,15 @@ describe('#3427 + #3433 — Codex installer avoids duplicate skills and mixed ho ? fs.readdirSync(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name) : []; - assert.equal(entries.some((name) => name.startsWith('gsd-')), false); + // #3562: $gsd-* commands are discoverable only when skills/gsd-*/SKILL.md + // exists. The installer must regenerate (not remove) the managed gsd-* + // directories. + assert.equal(entries.includes('gsd-help'), true); + const refreshedBody = fs.readFileSync(path.join(skillsDir, 'gsd-help', 'SKILL.md'), 'utf8'); + assert.notEqual(refreshedBody, legacySkillBody, 'stale legacy body must be overwritten'); + assert.ok(refreshedBody.startsWith('---'), 'refreshed SKILL.md must be valid frontmatter shape'); + + // Unrelated user skills are preserved — the regen scope is `gsd-*` only. assert.equal(entries.includes('custom-user-skill'), true); }); diff --git a/tests/bug-3562-codex-install-skill-surface.test.cjs b/tests/bug-3562-codex-install-skill-surface.test.cjs new file mode 100644 index 000000000..2f1f18c58 --- /dev/null +++ b/tests/bug-3562-codex-install-skill-surface.test.cjs @@ -0,0 +1,128 @@ +'use strict'; + +process.env.GSD_TEST_MODE = '1'; + +/** + * Regression test for bug #3562 — Codex global install must create a + * discoverable $gsd-* skill surface. + * + * Codex CLI 0.130.0 (the version in the issue report) does NOT auto-discover + * commands from get-shit-done/workflows/*.md or agents/*.md. It only registers + * commands from skills//SKILL.md. Prior installer logic ("Codex now + * discovers official skills from .agents/skills") was based on an assumption + * that does not match the shipping Codex CLI behavior, leaving users with + * workflows on disk and no $gsd-* entrypoints after `npx get-shit-done-cc + * --codex --global`. + * + * Fix: re-wire copyCommandsAsCodexSkills() back into the install dispatch path + * so the same skill-shape that Claude / Copilot / Antigravity / Cursor / + * Windsurf / Augment / Trae installs produce is also produced for Codex. + */ + +const { describe, test, beforeEach, afterEach } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const { execFileSync } = require('node:child_process'); + +const { install } = require('../bin/install.js'); +const { createTempDir, cleanup } = require('./helpers.cjs'); + +const HOOKS_DIST = path.join(__dirname, '..', 'hooks', 'dist'); +const BUILD_HOOKS_SCRIPT = path.join(__dirname, '..', 'scripts', 'build-hooks.js'); + +function withCodexHome(codexHome, fn) { + const prev = process.env.CODEX_HOME; + process.env.CODEX_HOME = codexHome; + try { + return fn(); + } finally { + if (prev == null) delete process.env.CODEX_HOME; + else process.env.CODEX_HOME = prev; + } +} + +describe('#3562 — Codex install produces discoverable $gsd-* skill surface', { concurrency: false }, () => { + let tmpRoot; + let codexHome; + + beforeEach(() => { + if (!fs.existsSync(HOOKS_DIST) || fs.readdirSync(HOOKS_DIST).length === 0) { + execFileSync(process.execPath, [BUILD_HOOKS_SCRIPT], { stdio: 'pipe' }); + } + tmpRoot = createTempDir('gsd-3562-'); + codexHome = path.join(tmpRoot, '.codex'); + fs.mkdirSync(codexHome, { recursive: true }); + }); + + afterEach(() => { + cleanup(tmpRoot); + }); + + test('global install creates skills/gsd-help/SKILL.md', () => { + withCodexHome(codexHome, () => install(true, 'codex')); + + const skillPath = path.join(codexHome, 'skills', 'gsd-help', 'SKILL.md'); + assert.ok( + fs.existsSync(skillPath), + `Codex install must create ${skillPath} so $gsd-help is discoverable. ` + + 'Without this, Codex CLI 0.130.0 does not expose any $gsd-* command.', + ); + }); + + test('SKILL.md content has frontmatter expected by Codex skill discovery', () => { + withCodexHome(codexHome, () => install(true, 'codex')); + + const skillPath = path.join(codexHome, 'skills', 'gsd-help', 'SKILL.md'); + assert.ok(fs.existsSync(skillPath), 'precondition: SKILL.md exists'); + + const content = fs.readFileSync(skillPath, 'utf8'); + // SKILL.md is YAML frontmatter then body; convertClaudeCommandToCodexSkill + // emits at minimum a `name:` field on the leading frontmatter block. + assert.ok( + content.startsWith('---'), + 'SKILL.md must begin with YAML frontmatter (---) for Codex discovery', + ); + assert.ok( + /^name:\s*["']?gsd-help["']?\s*$/m.test(content), + 'SKILL.md frontmatter must declare name: gsd-help so $gsd-help resolves', + ); + }); + + test('multiple core $gsd-* skills are produced (not just gsd-help)', () => { + withCodexHome(codexHome, () => install(true, 'codex')); + + const skillsDir = path.join(codexHome, 'skills'); + assert.ok(fs.existsSync(skillsDir), 'skills/ directory must exist after install'); + + const gsdSkills = fs + .readdirSync(skillsDir, { withFileTypes: true }) + .filter((e) => e.isDirectory() && e.name.startsWith('gsd-')) + .map((e) => e.name); + + // Lower bound — exact count depends on the current command surface. The + // commands/gsd/ directory holds dozens of *.md files; expecting more than + // 10 generated skills is a conservative floor that catches "we generated + // nothing" or "we only generated one accidentally" regressions. + assert.ok( + gsdSkills.length >= 10, + `Expected >= 10 generated gsd-* skill directories, found ${gsdSkills.length}: ${gsdSkills.join(', ')}`, + ); + }); + + test('install preserves existing user skills (does not remove unrelated dirs)', () => { + fs.mkdirSync(path.join(codexHome, 'skills', 'custom-user-skill'), { recursive: true }); + fs.writeFileSync( + path.join(codexHome, 'skills', 'custom-user-skill', 'SKILL.md'), + '---\nname: custom-user-skill\n---\n# user skill\n', + ); + + withCodexHome(codexHome, () => install(true, 'codex')); + + const userSkill = path.join(codexHome, 'skills', 'custom-user-skill', 'SKILL.md'); + assert.ok( + fs.existsSync(userSkill), + 'Codex install must preserve existing non-gsd user skill directories', + ); + }); +}); From 08848df839d98214de2b97f2473117290a9a92d1 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Fri, 15 May 2026 13:46:12 -0400 Subject: [PATCH 2/4] docs(3562): pin minimum Codex CLI version (0.130.0) and explain the seam MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rationale for the version pin (the timeline that produced the oscillation): 2026-05-08 Codex CLI 0.130.0 ships, dropping extra-skills-roots discovery via openai/codex#21485 (scans only ~/.codex/skills, cwd .codex/skills, and registered plugin roots). 2026-05-14 GSD PR #3512 lands, removing ~/.codex/skills/gsd-* under the assumption Codex would auto-discover from extra roots. That assumption was already obsolete in shipped Codex. 2026-05-15 #3562 filed — Codex CLI 0.130.0 users have zero $gsd-* commands after install. The previous fix (#3427) was for Codex Desktop's official-skills surface, which is a different product; that surface still exists on Desktop and remains harmless duplication when both root scans see the gsd-* dirs. Documents the supported version inline at the Codex sections of both USER-GUIDE.md and CONFIGURATION.md, plus a one-line note in README's Troubleshooting block. No runtime version-detection added — out of scope and brittle against future Codex changes. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 2 ++ docs/CONFIGURATION.md | 4 ++++ docs/USER-GUIDE.md | 6 ++++++ tests/install-minimal-all-runtimes.test.cjs | 8 ++++++-- tests/installer-migration-install-integration.test.cjs | 10 +++++----- 5 files changed, 23 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4275b2a0c..5972e97ce 100644 --- a/README.md +++ b/README.md @@ -222,6 +222,8 @@ For the full configuration reference — all settings, git branching strategies, **Commands not showing up?** Restart your runtime after install. GSD installs to `~/.claude/skills/gsd-*/` (Claude Code), `~/.codex/skills/gsd-*/` (Codex), or the equivalent for your runtime. +**Codex users — minimum supported CLI version is `0.130.0`.** Codex CLI 0.130.0 ([release notes](https://github.com/openai/codex/releases/tag/rust-v0.130.0)) removed extra-skill-roots discovery via [openai/codex#21485](https://github.com/openai/codex/pull/21485); from that version onward Codex only discovers skills from `~/.codex/skills//SKILL.md`. GSD installs there directly. Earlier Codex CLI versions may show duplicate `gsd-*` entries (one from extra-roots discovery, one from `~/.codex/skills/`); restart Codex after install and either upgrade or accept the duplicate listing. + **Something broken?** Re-run the installer — it's idempotent: ```bash npx get-shit-done-cc@latest diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 73c7d8a0a..6ffe77e70 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -968,6 +968,10 @@ The `dynamic_routing` block is **disabled by default** — `enabled: false` (or ### Non-Claude Runtimes (Codex, OpenCode, Gemini CLI, Kilo) +> **Codex CLI minimum supported version: `0.130.0`** (issue [#3562](https://github.com/gsd-build/get-shit-done/issues/3562)). +> +> [Codex CLI 0.130.0](https://github.com/openai/codex/releases/tag/rust-v0.130.0) (released 2026-05-08) removed extra-skills-roots discovery via [openai/codex#21485](https://github.com/openai/codex/pull/21485). From this version forward, Codex CLI only scans `~/.codex/skills//SKILL.md`, `/.codex/skills/`, and registered plugin roots for invocable skills. GSD installs the `$gsd-*` surface as `~/.codex/skills/gsd-/SKILL.md` so commands resolve after a Codex restart. Earlier Codex CLI versions can show a duplicate listing (the legacy extra-roots scan plus the user-root copies) — restart Codex and either upgrade to ≥ 0.130.0 or accept the duplicates until you do. + When GSD is installed for a non-Claude runtime, the installer automatically sets `resolve_model_ids: "omit"` in `~/.gsd/defaults.json`. This causes GSD to return an empty model parameter for all agents, so each agent uses whatever model the runtime is configured with. No additional setup is needed for the default case. If you want different agents to use different models, use `model_overrides` with fully-qualified model IDs that your runtime recognizes: diff --git a/docs/USER-GUIDE.md b/docs/USER-GUIDE.md index 7d1fa0f1d..695f767dc 100644 --- a/docs/USER-GUIDE.md +++ b/docs/USER-GUIDE.md @@ -1219,6 +1219,12 @@ For the full audit, harness reference, and the composition note with `model_prof ### Using Non-Claude Runtimes (Codex, OpenCode, Gemini CLI, Kilo) +> **Codex CLI minimum supported version: `0.130.0`** (issue [#3562](https://github.com/gsd-build/get-shit-done/issues/3562)). +> +> Codex CLI [0.130.0](https://github.com/openai/codex/releases/tag/rust-v0.130.0) (released 2026-05-08) removed extra-skills-roots discovery via [openai/codex#21485](https://github.com/openai/codex/pull/21485). From that version onward, Codex only discovers commands from `~/.codex/skills//SKILL.md` (user root), `/.codex/skills/` (cwd root), and registered plugin roots. The GSD installer writes `~/.codex/skills/gsd-/SKILL.md` directly so `$gsd-help`, `$gsd-new-project`, etc. are discoverable after restart. +> +> **Earlier Codex CLI versions** (pre-0.130.0) had additional skill-root scanning that discovered the GSD agent/workflow files in alternate locations. GSD still installs the `~/.codex/skills/gsd-*` copies on those versions, which can show a duplicate listing alongside the legacy auto-discovered surface — restart Codex after install and either upgrade to ≥ 0.130.0 or accept the duplicate entries until you do. + If you installed GSD for a non-Claude runtime, the installer already configured model resolution so all agents use the runtime's default model. No manual setup is needed. Specifically, the installer sets `resolve_model_ids: "omit"` in your config, which tells GSD to skip Anthropic model ID resolution and let the runtime choose its own default model. To assign different models to different agents on a non-Claude runtime, add `model_overrides` to `.planning/config.json` with fully-qualified model IDs that your runtime recognizes: diff --git a/tests/install-minimal-all-runtimes.test.cjs b/tests/install-minimal-all-runtimes.test.cjs index b3ea99a70..5e5495ad0 100644 --- a/tests/install-minimal-all-runtimes.test.cjs +++ b/tests/install-minimal-all-runtimes.test.cjs @@ -180,8 +180,12 @@ function expectedSkillSet() { } function expectedManifestSkillSet(runtime) { - // Codex no longer materializes gsd-* skill files in minimal mode. - if (runtime === 'codex') return new Set(); + // Codex CLI 0.130.0 does not auto-discover commands from workflow / agent + // files (#3562) — it only registers commands from skills//SKILL.md. + // Codex installs therefore materialize the same minimal-allowlist skill + // surface as the other runtimes; the prior "Codex discovers official + // skills directly" assumption (which led to an empty Codex skill set + // here) does not hold in practice. return expectedSkillSet(); } diff --git a/tests/installer-migration-install-integration.test.cjs b/tests/installer-migration-install-integration.test.cjs index f9ca22a72..9fb0aeba9 100644 --- a/tests/installer-migration-install-integration.test.cjs +++ b/tests/installer-migration-install-integration.test.cjs @@ -209,11 +209,11 @@ function assertFreshInstallContract(runtime, targetDir) { ); if (contract.surface === 'flat-skills') { - if (runtime === 'codex') { - assertNoGsdDirectoryEntries(targetDir, 'skills'); - } else { - assertHasGsdDirectory(targetDir, 'skills'); - } + // Pre-#3562: codex was special-cased to expect zero gsd-* skill dirs + // (assumption: Codex auto-discovers from workflows). That assumption + // does not hold for Codex CLI 0.130.0 — fresh installs now materialize + // the same flat-skills surface as the other runtimes. + assertHasGsdDirectory(targetDir, 'skills'); } else if (contract.surface === 'hermes-skills') { assertHasGsdDirectory(targetDir, path.join('skills', 'gsd')); assert.ok( From 4471459ea13d8be89de0ea82d8906ae7bc62fa79 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Fri, 15 May 2026 13:47:04 -0400 Subject: [PATCH 3/4] chore(3562): backfill PR number in changeset Co-Authored-By: Claude Opus 4.7 (1M context) --- .changeset/3562-codex-install-skill-surface.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/3562-codex-install-skill-surface.md b/.changeset/3562-codex-install-skill-surface.md index 1e8306328..3f93b90b9 100644 --- a/.changeset/3562-codex-install-skill-surface.md +++ b/.changeset/3562-codex-install-skill-surface.md @@ -1,6 +1,6 @@ --- type: Fixed -pr: 0 +pr: 3568 --- **Codex global install now produces discoverable `$gsd-*` skill surface** — `npx get-shit-done-cc@latest --codex --global` was leaving Codex CLI users with `get-shit-done/workflows/*.md` and `agents/gsd-*` on disk but no `~/.codex/skills/gsd-*/SKILL.md` files, so Codex 0.130.0 silently exposed zero `$gsd-*` commands after restart. The installer had been bypassing skill generation under the assumption that Codex auto-discovers from workflow/agent files; that assumption does not hold for the current Codex CLI. Re-wired the existing `copyCommandsAsCodexSkills()` helper into the Codex install dispatch path so it produces the same skill-shape as the Claude / Copilot / Antigravity / Cursor / Windsurf / Augment / Trae installs already do (one `skills/gsd-/SKILL.md` per `commands/gsd/*.md`). Pre-existing user-owned non-`gsd-*` skill directories are preserved. Closes #3562. From 9a04332953451161123c17746f847a406f244cd9 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Fri, 15 May 2026 14:44:40 -0400 Subject: [PATCH 4/4] docs+test(3562): align codex roots wording and parse SKILL frontmatter --- README.md | 2 +- tests/bug-3427-3433-codex-install-shape.test.cjs | 5 +++-- .../bug-3562-codex-install-skill-surface.test.cjs | 14 +++----------- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 5972e97ce..63766fc3a 100644 --- a/README.md +++ b/README.md @@ -222,7 +222,7 @@ For the full configuration reference — all settings, git branching strategies, **Commands not showing up?** Restart your runtime after install. GSD installs to `~/.claude/skills/gsd-*/` (Claude Code), `~/.codex/skills/gsd-*/` (Codex), or the equivalent for your runtime. -**Codex users — minimum supported CLI version is `0.130.0`.** Codex CLI 0.130.0 ([release notes](https://github.com/openai/codex/releases/tag/rust-v0.130.0)) removed extra-skill-roots discovery via [openai/codex#21485](https://github.com/openai/codex/pull/21485); from that version onward Codex only discovers skills from `~/.codex/skills//SKILL.md`. GSD installs there directly. Earlier Codex CLI versions may show duplicate `gsd-*` entries (one from extra-roots discovery, one from `~/.codex/skills/`); restart Codex after install and either upgrade or accept the duplicate listing. +**Codex users — minimum supported CLI version is `0.130.0`.** Codex CLI 0.130.0 ([release notes](https://github.com/openai/codex/releases/tag/rust-v0.130.0)) removed extra-skill-roots discovery via [openai/codex#21485](https://github.com/openai/codex/pull/21485); from that version onward Codex discovers skills from standard roots (including `~/.codex/skills//SKILL.md`). GSD installs there directly. Earlier Codex CLI versions may still discover additional roots, which can surface duplicate `gsd-*` entries (one from extra-roots discovery, one from `~/.codex/skills/`); restart Codex after install and either upgrade or accept the duplicate listing. **Something broken?** Re-run the installer — it's idempotent: ```bash diff --git a/tests/bug-3427-3433-codex-install-shape.test.cjs b/tests/bug-3427-3433-codex-install-shape.test.cjs index fbcccf286..e4fd522b8 100644 --- a/tests/bug-3427-3433-codex-install-shape.test.cjs +++ b/tests/bug-3427-3433-codex-install-shape.test.cjs @@ -10,7 +10,7 @@ const crypto = require('node:crypto'); const { execFileSync } = require('node:child_process'); const { install, uninstall, parseTomlToObject } = require('../bin/install.js'); -const { createTempDir, cleanup } = require('./helpers.cjs'); +const { createTempDir, cleanup, parseFrontmatter } = require('./helpers.cjs'); const HOOKS_DIST = path.join(__dirname, '..', 'hooks', 'dist'); const BUILD_HOOKS_SCRIPT = path.join(__dirname, '..', 'scripts', 'build-hooks.js'); @@ -85,7 +85,8 @@ describe('#3427 + #3433 — Codex installer avoids duplicate skills and mixed ho assert.equal(entries.includes('gsd-help'), true); const refreshedBody = fs.readFileSync(path.join(skillsDir, 'gsd-help', 'SKILL.md'), 'utf8'); assert.notEqual(refreshedBody, legacySkillBody, 'stale legacy body must be overwritten'); - assert.ok(refreshedBody.startsWith('---'), 'refreshed SKILL.md must be valid frontmatter shape'); + const frontmatter = parseFrontmatter(refreshedBody); + assert.equal(frontmatter.name, 'gsd-help', 'refreshed SKILL.md frontmatter must declare name: gsd-help'); // Unrelated user skills are preserved — the regen scope is `gsd-*` only. assert.equal(entries.includes('custom-user-skill'), true); diff --git a/tests/bug-3562-codex-install-skill-surface.test.cjs b/tests/bug-3562-codex-install-skill-surface.test.cjs index 2f1f18c58..a0875922c 100644 --- a/tests/bug-3562-codex-install-skill-surface.test.cjs +++ b/tests/bug-3562-codex-install-skill-surface.test.cjs @@ -26,7 +26,7 @@ const path = require('node:path'); const { execFileSync } = require('node:child_process'); const { install } = require('../bin/install.js'); -const { createTempDir, cleanup } = require('./helpers.cjs'); +const { createTempDir, cleanup, parseFrontmatter } = require('./helpers.cjs'); const HOOKS_DIST = path.join(__dirname, '..', 'hooks', 'dist'); const BUILD_HOOKS_SCRIPT = path.join(__dirname, '..', 'scripts', 'build-hooks.js'); @@ -77,16 +77,8 @@ describe('#3562 — Codex install produces discoverable $gsd-* skill surface', { assert.ok(fs.existsSync(skillPath), 'precondition: SKILL.md exists'); const content = fs.readFileSync(skillPath, 'utf8'); - // SKILL.md is YAML frontmatter then body; convertClaudeCommandToCodexSkill - // emits at minimum a `name:` field on the leading frontmatter block. - assert.ok( - content.startsWith('---'), - 'SKILL.md must begin with YAML frontmatter (---) for Codex discovery', - ); - assert.ok( - /^name:\s*["']?gsd-help["']?\s*$/m.test(content), - 'SKILL.md frontmatter must declare name: gsd-help so $gsd-help resolves', - ); + const frontmatter = parseFrontmatter(content); + assert.equal(frontmatter.name, 'gsd-help', 'SKILL.md frontmatter must declare name: gsd-help so $gsd-help resolves'); }); test('multiple core $gsd-* skills are produced (not just gsd-help)', () => {