Fix Codex hook config: rename to [features].hooks, drop async#496
Merged
Conversation
Codex v0.139.0+ renamed `[features].codex_hooks` to `[features].hooks` and silently skips any hook marked `async: true` because its runtime is sync-only. The deprecation warning was loud; the async-skip was silent, breaking Crow's session-state detection (auto-respond, card-color, etc.) on every Codex session because `PostToolUse` and `Stop` never reached `CodexSignalSource`. - Empty out `asyncEvents` in `CodexHookConfigWriter` so no entry is written with `async: true`. - Switch the TOML key from `codex_hooks` to `hooks`. - Add `removeTomlSectionLine` helper + a one-shot migration step in `installGlobalTomlConfig` that strips a legacy `codex_hooks` line under `[features]` so users converging from older installs end up with a single, current entry. Idempotent. - Update existing tests + add tests covering the migration path and the no-async invariant. Closes #494
dhilgaertner
approved these changes
Jun 11, 2026
dhilgaertner
left a comment
Contributor
There was a problem hiding this comment.
Code & Security Review
Critical Issues
None.
Security Review
Strengths:
- No untrusted input.
crowPathis escaped viaescapeTomlStringbefore being embedded in the TOMLnotifyline; the hooks.json command path flows throughJSONSerialization. - File writes are atomic (
write(toFile:atomically:true)/Data.write), avoiding partial-write corruption of user config. - The line-oriented merge preserves user-authored config rather than clobbering
config.toml/hooks.json.
Concerns:
- None.
Code Quality
- The migration ordering is correct:
removeTomlSectionLine(..., key: "codex_hooks")runs beforeupsertTomlSectionLine(..., key: "hooks"). SincelineKeytreatscodex_hooksandhooksas distinct keys, stripping the legacy line first is what prevents both keys lingering — verified byinstallGlobalTomlConfigMigratesLegacyCodexHooksKey, including the idempotency re-run assertion. removeTomlSectionLinemirrors the existingupsertTomlSectionLinesection-scan logic (exact[features]header match, bounded to the next section header), so it stays consistent with the file's established minimal-merge style and won't touch keys in other sections.- Emptying
asyncEventscleanly removes theasyncflag ingenerateHooks; becausePostToolUse/Stopare inallEvents,installGlobalConfigoverwrites any stale async entries left in an existinghooks.json, so the hooks.json side migrates implicitly.installGlobalConfigEmitsNoAsyncHooksguards the regression. - The rationale (Codex v0.139.0 sync-only, silent-skip) is captured in code comments, not just the PR body — good for the next reader.
- Migration is wired into launch at
Sources/Crow/App/AppDelegate.swift:440, and no othercodex_hooksreferences remain in source.
Verification
swift test --filter CodexHookConfigWriter→ 8/8 pass, including the 2 new tests.
Summary Table
| Color | Meaning | Verdict effect |
|---|---|---|
| Red | Must fix | Request changes |
| Yellow | Should fix | Request changes |
| Green | Consider | Approve allowed |
Recommendation: Approve — driven by [0 Red, 0 Yellow, 0 Green] findings. Focused, correct, well-tested config fix with a sound idempotent migration.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
~/.codex/config.tomlwriter from the deprecated[features].codex_hookskey to[features].hooks(Codex v0.139.0+).codex_hooks = …line under[features]so users converging from older Crow installs end up with a single, current entry. Idempotent.asyncEventsinCodexHookConfigWriterso no entry is written withasync: true— Codex's hook runtime is sync-only and silently skipped those, breaking Crow's session-state detection (auto-respond, card-color, etc.).Why this matters
The deprecation warning is loud; the async-skip was silent. With async hooks skipped,
PostToolUseandStopnever reachedCodexSignalSource, so Crow's UI never observed Codex sessions transitioning toworking/done— auto-respond gates and card-color updates were silently broken.Test plan
swift test --filter CodexHookConfigWriter— all 8 tests pass, including 2 new ones (installGlobalTomlConfigMigratesLegacyCodexHooksKey,installGlobalConfigEmitsNoAsyncHooks).swift testfor full CrowCodex package — 31 tests pass.~/.codex/config.tomlwith[features]\ncodex_hooks = true, launch Crow with a Codex session, confirm no deprecation/async-skip warnings and that the file ends up withhooks = trueonly.working → donetransitions.Closes #494