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 <trekkie@nomorestars.com>
This commit is contained in:
kyle-the-dev
2026-08-02 13:00:52 +08:00
committed by GitHub
parent 137f3fbb9c
commit ce38d44811
7 changed files with 55 additions and 8 deletions

View File

@@ -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)

View File

@@ -38,8 +38,7 @@
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCodexSkill",
"home": ".agents"
"converter": "convertClaudeCommandToCodexSkill"
}
]
},

View File

@@ -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 `<project>/.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.

View File

@@ -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"
}
]
},

View File

@@ -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',
);
}
}
}
}

View File

@@ -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 ──────

View File

@@ -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);