fix: enforce namespaced AoT for Codex hooks (#2760, #2727)#2802
fix: enforce namespaced AoT for Codex hooks (#2760, #2727)#2802JOHNNYMACONNY wants to merge 4 commits intogsd-build:mainfrom
Conversation
…d#2727) Ensures that GSD always emits the namespaced [[hooks.SessionStart]] format which is required by Codex 0.124.0+. Previously, it defaulted to a flat [[hooks]] format which caused schema validation errors. - Updated bin/install.js to always use namespaced hooks. - Updated tests/codex-config.test.cjs to expect namespaced hooks. - Updated CHANGELOG.md.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughRefactors Codex hook detection: Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
tests/codex-config.test.cjs (1)
1240-1240: Prefer structural TOML assertions over raw text includes for hook shape.These checks are string-fragment assertions and are brittle; please assert via
parseTomlToObject(content)thathooks.SessionStartis an AoT entry (and command exists), instead of matching serialized text.♻️ Suggested assertion update
- assert.ok(content.includes('# GSD Hooks\n[[hooks.SessionStart]]\n'), 'writes GSD SessionStart hook block'); + const parsed = parseTomlToObject(content); + assert.ok(parsed.hooks && Array.isArray(parsed.hooks.SessionStart), 'writes GSD SessionStart hook block'); + assert.strictEqual(parsed.hooks.SessionStart.length, 1, 'writes one SessionStart hook entry'); + assert.ok( + typeof parsed.hooks.SessionStart[0].command === 'string' && + parsed.hooks.SessionStart[0].command.includes('gsd-check-update.js'), + 'SessionStart hook points to gsd-check-update.js' + );- assert.ok(content.includes('# GSD Hooks\n[[hooks.SessionStart]]\n'), 'writes the GSD hook block using the first newline style'); + const parsed = parseTomlToObject(content); + assert.ok(parsed.hooks && Array.isArray(parsed.hooks.SessionStart), 'writes the GSD hook block using the first newline style'); + assert.strictEqual(parsed.hooks.SessionStart.length, 1, 'remains idempotent for SessionStart hook entry');Based on learnings: “For this repository, follow the CONTRIBUTING.md ‘no-source-grep’ testing standard… assert on the parsed/structured representation of the data rather than raw text fragments.”
Also applies to: 1849-1849
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/codex-config.test.cjs` at line 1240, Replace the brittle string-fragment assertion that checks for '# GSD Hooks\n[[hooks.SessionStart]]\n' with a structural TOML assertion: parse the file via parseTomlToObject(content), then assert that the resulting object has a hooks property containing a SessionStart entry that is an array (AoT) and that the first element includes a non-empty command field; apply the same change to the other similar assertion (the one referenced at lines near 1849) so tests validate parsed structure not raw text.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@tests/codex-config.test.cjs`:
- Line 1240: Replace the brittle string-fragment assertion that checks for '#
GSD Hooks\n[[hooks.SessionStart]]\n' with a structural TOML assertion: parse the
file via parseTomlToObject(content), then assert that the resulting object has a
hooks property containing a SessionStart entry that is an array (AoT) and that
the first element includes a non-empty command field; apply the same change to
the other similar assertion (the one referenced at lines near 1849) so tests
validate parsed structure not raw text.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 6c26ab39-924d-41bc-b49f-be8f367377d4
📒 Files selected for processing (3)
CHANGELOG.mdbin/install.jstests/codex-config.test.cjs
Applied CodeRabbit review suggestions: - Replaced brittle string-fragment assertions with parseTomlToObject calls. - Verified 106 tests pass with structured checks.
|
This PR serves as the definitive follow-up to the schema issues discussed in #2763 and #2637. It specifically addresses Position 2 from the maintainer review in #2763:
I have verified on a live system (desktop app environment) that the flat shape produced by #2747 causes validation failures, whereas the nested namespaced shape Furthermore:
|
Adversarial Review — PR #2802The Premise is FalseThe PR's stated reason for existence: "flat That error message is about agents, not hooks. Issue #2727 surfaced that The The Smoking Gun: A Direct Test Contradiction
test('selects top-level [[hooks]] form when user has no namespaced hooks (status-quo behavior)', () => {
writeCodexConfig(codexHome, '');
runCodexInstall(codexHome);
const parsed = parseTomlToObject(content);
assert.ok(
Array.isArray(parsed.hooks), // ← THIS MUST BE TRUE
'fresh install must produce top-level [[hooks]] AoT ...'
);
assert.ok(
parsed.hooks.some((h) => h && h.event === 'SessionStart'), // ← WITH event FIELD
...
);
});The PR changes the emitted format to always The PR author claims "Verified all 106 Codex-related tests pass." That claim is false. Either Gall's Law ViolationThe original conditional in
Gall's Law: you cannot replace a working complex system with a simpler one by removing complexity. You have to understand every invariant the complexity protects. This PR removes the conditional without understanding what it guarded. The "simpler" version — always namespaced — immediately fails for any user whose existing config has flat Specifically: Dead Code Introduced
Knuth's Optimization: Premature SimplificationThe PR simplifies a conditional ( Root Cause AnalysisWhat they think the bug is: Flat Trace the claim: Where is it rejected? The error message cited — What the actual pre-existing code does: The conditional correctly selects flat or namespaced based on what the user already has, so GSD's entry coexists cleanly with user hooks in either form. This shipped in rc.3. It works. There is no bug here. Secondary and Tertiary Defects Being Introduced
What This PR Needs to Be Mergeable
This PR should not merge in its current state. |
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@bin/install.js`:
- Around line 6995-7001: Compute the hook style after removing GSD-managed
blocks: instead of calling hasUserNamespacedAotHooks(configContent) directly,
first strip out the managed gsd-check-update block(s) from configContent (the
same logic used later to remove managed blocks) and then call
hasUserNamespacedAotHooks on that cleaned content; update the construction of
hookBlock (and any use of updateCheckScript/gsd-check-update detection) so it
bases the namespaced vs flat decision on the cleaned config to avoid re-emitting
namespaced sections when only the previous GSD-managed namespace exists.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: c96e0967-77de-4e88-8a94-0207f74abf2f
📒 Files selected for processing (3)
bin/install.jstests/bug-2760-codex-install-defensive.test.cjstests/codex-config.test.cjs
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/codex-config.test.cjs
trek-e
left a comment
There was a problem hiding this comment.
Adversarial review — PR 2802 (first-time contributor, maximum scrutiny)
Applying Gall's Law, Goodhart's Law, Kernighan's Law, Knuth's optimization principle, and Peter Principle. Every line traced.
Conflict status
PR is CONFLICTING with main. Must rebase before any merge consideration.
Relationship to competing PRs and the already-merged fix
PR #2809 (fix(#2773)) is already merged to main. It moves the GSD Codex hook to hooks.json (two-level nested schema) and handles defensive install, schema validation, and migration from legacy TOML flat hooks. This PR (#2802) still writes to config.toml inline — it does not migrate to hooks.json. It is solving an older version of the problem.
Concretely: the merged fix in #2809 makes GSD emit:
{ "hooks": { "SessionStart": [{ "hooks": [{ "type": "command", "command": "..." }] }] } }This PR makes GSD emit in config.toml:
[[hooks]]
event = "SessionStart"
command = "node ..."or (when user already uses namespaced AoT):
[[hooks.SessionStart]]
command = "node ..."Those are the two shapes #2809 was written to eliminate. This PR would overwrite the #2809 fix if merged. That is a fix collision — exactly the class of defect prohibited by the Fix Collision Guard.
Gall's Law: style-sensing logic is more complex than necessary
The core change in this PR is: detect whether the user uses namespaced AoT hooks by scanning user content after stripping GSD-managed blocks, then emit GSD's hook in the matching style.
The problem: this sensing step now has to be correct across:
- Empty
config.toml config.tomlwith only GSD-managed blocks (stripped → empty → no user style → flat)config.tomlwith user namespaced blocks and GSD-managed blocks (stripped → namespaced → emit namespaced)config.tomlafter PR #2809 migration where the hook is inhooks.json(not inconfig.tomlat all)
Case 4 is the post-#2809 state for all users who ran a GSD update since #2809 merged. For those users, configContent has no GSD hook at all, stripGsdFromCodexConfig(configContent) strips nothing, user style is flat → GSD emits flat TOML → back to the shape Codex 0.124.0+ rejects.
This is a regression against #2809.
Goodhart: the two structural test assertions validate the wrong invariant
tests/codex-config.test.cjs line 1243 (new):
assert.ok(
Array.isArray(parsed.hooks),
'fresh install must produce top-level [[hooks]] AoT, got: ' + typeof parsed.hooks
);parsed.hooks being an array is the TOML [[hooks]] (flat) shape. After PR #2809, the intended shape is parsed.hooks being an object (keyed by event), not an array. These assertions are asserting that the wrong shape was written — they would fail against the post-#2809 hooks.json path and succeed against the pre-#2809 TOML inline path.
The tests validate the approach this PR takes (TOML inline), not the approach #2809 shipped (hooks.json). If this PR's approach is wrong, the tests are wrong with it.
Kernighan's Law: hasUserNamespacedAotHooks signature change is a breaking API change without a full audit
Removing the event parameter from hasUserNamespacedAotHooks changes:
- Before:
sections.some((s) => s.array && s.path ===hooks.${event}) - After:
sections.some((s) => s.array && s.path.startsWith('hooks.'))
The original scoped the check to the specific event (SessionStart). The new version matches any namespaced hook section. This is a broader match — a user with [[hooks.PostToolUse]] but not [[hooks.SessionStart]] would now incorrectly trigger namespaced mode for the GSD SessionStart block.
The test suite only updates the three hasUserNamespacedAotHooks(content, 'SessionStart') calls to hasUserNamespacedAotHooks(content) — it does not add a test case for the new over-broad match scenario. Goodhart: the test suite passes because the assertions don't cover the new failure mode.
Required before this can be reconsidered
- Resolve the fix-collision against #2809 — either this PR adopts the
hooks.jsonapproach from #2809, or it documents a clear technical reason the TOML approach is preferred and #2809 should be reverted. - Fix the
eventparameter removal — either restore the specific-event check or add a test case that proves over-broad matching doesn't produce wrong output. - Rebase onto main.
- Address the remaining CodeRabbit finding: style-sensing should be computed after stripping GSD-managed blocks (this PR does this) — but only if the TOML approach is retained at all.
@trek-e — fix collision identified against merged #2809. This PR needs maintainer decision before any path forward is clear.
|
closing in favor of maintainer fix. please install the RC5 as mentioned in the issue and test, provide results in a new issue if not resolved. |
Fixes the Codex configuration schema error where flat
[[hooks]]were being emitted by default, which is rejected by Codex 0.124.0+ withinvalid type: sequence, expected struct AgentsToml.Changes:
bin/install.jsto default to[[hooks.SessionStart]]namespaced AoT for GSD-managed hooks.tests/codex-config.test.cjsto expect the correct namespaced format.CHANGELOG.md.Summary by CodeRabbit
Bug Fixes
Refactor