From ce38d44811ead7027c797cccc48d8edc1655ff8b Mon Sep 17 00:00:00 2001 From: kyle-the-dev Date: Sun, 2 Aug 2026 13:00:52 +0800 Subject: [PATCH] fix(#2777): remove stale codex local home metadata (#2831) * fix(#2777): remove stale codex local home metadata * chore(#2777): add changeset for codex local layout metadata --------- Co-authored-by: Tom Boucher --- .changeset/steady-eagles-cheer.md | 5 +++++ capabilities/codex/capability.json | 3 +-- .../host-integration-capability-matrix.md | 2 +- gsd-core/bin/lib/capability-registry.cjs | 6 ++---- gsd-core/bin/lib/capability-validator.cjs | 13 +++++++++++- tests/capability-registry.test.cjs | 20 +++++++++++++++++++ tests/runtime-artifact-layout.test.cjs | 14 +++++++++++++ 7 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 .changeset/steady-eagles-cheer.md diff --git a/.changeset/steady-eagles-cheer.md b/.changeset/steady-eagles-cheer.md new file mode 100644 index 000000000..9350230ab --- /dev/null +++ b/.changeset/steady-eagles-cheer.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 2831 +--- +**Codex local capability metadata now matches project-scoped installs** — Remove the inert user-home override from the local skills descriptor, document the global/local skill roots, and reject user-home overrides across all local artifact-layout entries. (#2777) diff --git a/capabilities/codex/capability.json b/capabilities/codex/capability.json index 3e5452250..e1bb66534 100644 --- a/capabilities/codex/capability.json +++ b/capabilities/codex/capability.json @@ -38,8 +38,7 @@ "prefix": "gsd-", "nesting": "flat", "recursive": false, - "converter": "convertClaudeCommandToCodexSkill", - "home": ".agents" + "converter": "convertClaudeCommandToCodexSkill" } ] }, diff --git a/docs/reference/host-integration-capability-matrix.md b/docs/reference/host-integration-capability-matrix.md index c6737c2fa..2cbe3a591 100644 --- a/docs/reference/host-integration-capability-matrix.md +++ b/docs/reference/host-integration-capability-matrix.md @@ -108,7 +108,7 @@ Sources consulted: **GSD integration status — Phase D dogfood complete (#2088, ADR-1239).** Codex installs through the `declarative` embedding adapter (`createDeclarativeAdapter` → `installRuntimeArtifacts`); the hardcoded `runtime === 'codex'`/`isCodex` projection is folded into descriptor-driven `runtime.hostBehaviors`, and install/uninstall output is byte-parity-gated at the time (`tests/fixtures/golden-install-parity/codex.json`; superseded by the differential attribution check, #2724). Three capability upgrades land, each with a test driving the user-reachable surface: -- **Skill root** — skills install to the canonical `$HOME/.agents/skills` (Codex core-skills `loader.rs` user-scope root), not the deprecated `$CODEX_HOME/skills` fallback. Declared via the skills-kind `home: ".agents"` override; pre-move installs are migrated (stale `~/.codex/skills/gsd-*` cleaned on both install and uninstall). +- **Skill root** — global skills install to the canonical `$HOME/.agents/skills` (Codex core-skills `loader.rs` user-scope root), not the deprecated `$CODEX_HOME/skills` fallback; local skills install to `/.codex/skills`. The global path is declared via the global skills-kind `home: ".agents"` override, while the local kind intentionally has no home override. Pre-move global installs are migrated (stale `~/.codex/skills/gsd-*` cleaned on both install and uninstall); local installs do not remove `$HOME/.agents/skills` because those skills may be intentionally global. - **Hook events** — GSD registers all documented `hooks.json` lifecycle events beyond `SessionStart`: `SubagentStart`, `Stop`, `PostToolUse` (#772), plus the six added in #2088 — `PreToolUse`, `PermissionRequest`, `PreCompact`, `PostCompact`, `SubagentStop`, `UserPromptSubmit` — all routed through `gsd-context-monitor.js`. (The descriptor `extendedHookEvents` field reflects the schema-valid cross-runtime subset `SubagentStop`/`Stop`/`PreCompact`; Codex's full event set is codex-hooks-json-native, registered directly in `hooks.json`.) - **Dispatch tuning** — `[agents] max_depth = 1` is written explicitly into the managed `config.toml` block, pinning the `dispatch.maxDepth: 1` axis instead of relying on codex-cli's implicit default. Because `maxDepth === 1`, `degradationFor` flattens GSD-hosted wave dispatch to single-level even though `dispatch.nested`/`background`/`backgroundDispatch` are all `true`. The block is a bare `[agents]` AgentsToml scalar table; it does **not** carry per-role `[agents.gsd-*]` sub-tables — those pointed `config_file` back at the standalone `agents/gsd-*.toml` files Codex already auto-discovers, so emitting them was a duplicate role registration (Codex logged "Ignoring malformed agent role definition: duplicate agent role name" once per agent) removed in #2406. `validateCodexConfigSchema` permits a known-scalar-only `[agents]` while still rejecting `[[agents]]` and unknown-key forms. diff --git a/gsd-core/bin/lib/capability-registry.cjs b/gsd-core/bin/lib/capability-registry.cjs index 9528b5f1d..b8671555f 100644 --- a/gsd-core/bin/lib/capability-registry.cjs +++ b/gsd-core/bin/lib/capability-registry.cjs @@ -1036,8 +1036,7 @@ const capabilities = { "prefix": "gsd-", "nesting": "flat", "recursive": false, - "converter": "convertClaudeCommandToCodexSkill", - "home": ".agents" + "converter": "convertClaudeCommandToCodexSkill" } ] }, @@ -5389,8 +5388,7 @@ const runtimes = { "prefix": "gsd-", "nesting": "flat", "recursive": false, - "converter": "convertClaudeCommandToCodexSkill", - "home": ".agents" + "converter": "convertClaudeCommandToCodexSkill" } ] }, diff --git a/gsd-core/bin/lib/capability-validator.cjs b/gsd-core/bin/lib/capability-validator.cjs index 3fbc21b1e..8f2365335 100644 --- a/gsd-core/bin/lib/capability-validator.cjs +++ b/gsd-core/bin/lib/capability-validator.cjs @@ -1075,7 +1075,18 @@ function validateArtifactLayout(capId, layout) { errors.push(ctx + '.' + scope + ' must be an array'); } else { for (let i = 0; i < arr.length; i++) { - errors.push(...validateArtifactKindEntry(capId, arr[i], 'artifactLayout.' + scope + '[' + i + ']')); + const entry = arr[i]; + errors.push(...validateArtifactKindEntry(capId, entry, 'artifactLayout.' + scope + '[' + i + ']')); + if ( + scope === 'local' && + typeof entry === 'object' && entry !== null && !Array.isArray(entry) && + Object.prototype.hasOwnProperty.call(entry, 'home') + ) { + errors.push( + ctx + '.local[' + i + '].home is not allowed; ' + + 'local artifact layout entries must remain project-scoped', + ); + } } } } diff --git a/tests/capability-registry.test.cjs b/tests/capability-registry.test.cjs index faae0ea78..318ff00fc 100644 --- a/tests/capability-registry.test.cjs +++ b/tests/capability-registry.test.cjs @@ -3720,6 +3720,26 @@ describe('ADR-1016 phase 5a: validateArtifactLayout unit tests', () => { }); assert.deepEqual(errors, []); }); + + test('#2777: home override is accepted globally and rejected locally', () => { + const entry = { + kind: 'skills', + destSubpath: 'skills', + prefix: 'gsd-', + nesting: 'flat', + recursive: false, + converter: null, + home: '.agents', + }; + + assert.deepEqual(validateArtifactLayout('test', { global: [entry], local: [] }), []); + + const errors = validateArtifactLayout('test', { global: [], local: [entry] }); + assert.ok( + errors.some((error) => error.includes('artifactLayout.local[0].home') && error.includes('project-scoped')), + 'Expected local home override rejection, got: ' + JSON.stringify(errors), + ); + }); }); // ── 24d-extra. FIX 3: tightened validateRuntimeBody / validateConfigHome ────── diff --git a/tests/runtime-artifact-layout.test.cjs b/tests/runtime-artifact-layout.test.cjs index 3a1ca172e..d3c44ce7f 100644 --- a/tests/runtime-artifact-layout.test.cjs +++ b/tests/runtime-artifact-layout.test.cjs @@ -23,6 +23,7 @@ const fs = require('fs'); const path = require('path'); const { resolveRuntimeArtifactLayout, findInstallSourceRoot } = require('../gsd-core/bin/lib/runtime-artifact-layout.cjs'); +const capabilityRegistry = require('../gsd-core/bin/lib/capability-registry.cjs'); const installProfiles = require('../gsd-core/bin/lib/install-profiles.cjs'); const { install } = require('../bin/install.js'); const { createTempDir, cleanup } = require('./helpers.cjs'); @@ -121,6 +122,19 @@ describe('resolveRuntimeArtifactLayout — codex', () => { }); }); +test('keeps every built-in local artifact layout project-scoped (#2777)', () => { + for (const [runtime, descriptor] of Object.entries(capabilityRegistry.runtimes)) { + const localEntries = descriptor.runtime?.artifactLayout?.local ?? []; + for (const entry of localEntries) { + assert.strictEqual( + Object.prototype.hasOwnProperty.call(entry, 'home'), + false, + `${runtime} local artifact layout entry '${entry.kind}' must not declare home`, + ); + } + } +}); + describe('resolveRuntimeArtifactLayout — copilot', () => { test('returns correct layout for copilot', () => { const layout = resolveRuntimeArtifactLayout('copilot', FAKE_DIR);