Skip to content

feat: add Codex Desktop hook tracking - #27

Merged
iamnotstatic merged 3 commits into
iamnotstatic:mainfrom
mandatedisrael:feat/codex-desktop-hooks
Sep 6, 2026
Merged

feat: add Codex Desktop hook tracking#27
iamnotstatic merged 3 commits into
iamnotstatic:mainfrom
mandatedisrael:feat/codex-desktop-hooks

Conversation

@mandatedisrael

Copy link
Copy Markdown
Contributor

Summary

Adds first-class Codex Desktop tracking through Codex lifecycle hooks.

The existing shell wrapper tracks the codex terminal command, but cannot see sessions started directly in the Codex Desktop app. Users can now enable Desktop tracking with:

vibe hooks install codex

The existing vibe hooks install behavior remains unchanged and continues to default to Claude Code Desktop.

Changes

  • Add Codex hook installation through ~/.codex/hooks.json
  • Preserve unrelated hooks and top-level configuration
  • Avoid duplicate hooks on repeated installation
  • Refresh stale absolute Node and CLI paths on reinstall
  • Map SessionStart, UserPromptSubmit, PostToolUse, Stop, and SessionEnd into the existing hook session engine
  • Record sessions with tool: "codex"
  • Return the required JSON response for Codex Stop hooks
  • Use the three-second SessionEnd timeout supported by Codex
  • Add vibe hooks uninstall codex
  • Document installation, hook review, privacy, and removal

Users review and trust the generated configuration through /hooks, then open a fresh Codex Desktop session.

Privacy

The integration reads only the session ID and working directory from the Codex hook payload. It does not read prompts, transcripts, model responses, or source-file contents.

Git scoring and opt-in leaderboard submission behavior are unchanged.

Testing

  • npm test — 17 tests passing
  • npm run build
  • npm pack --dry-run
  • Non-destructive hook merging
  • Idempotent reinstall
  • Stale Node and CLI path refresh
  • Uninstall preserves unrelated hooks
  • Invalid hooks.json is not overwritten
  • Codex sessions receive the codex tool label
  • Existing Claude hook sessions still default to claude
  • Codex Stop hook returns valid JSON
  • Local build installs all five events without duplication

@iamnotstatic

Copy link
Copy Markdown
Owner

Thanks for this, it's good work. The merging and uninstall behavior line up with how the claude hooks installer does it, and the /hooks trust step is a nice catch.

A few things before I merge.

Which Codex builds did you test on (desktop + CLI)? I want to put a minimum version in the README.

Can you paste a sample session_id from a real codex payload? hook.ts throws away anything that isn't a UUID, so if codex ids are some other format the whole thing silently does nothing.

The process.exit(0) -> process.exitCode = 0 swap in cli.ts worries me because it also changes the claude path. That exit was deliberate, a hook process should die the moment its work is done, and a leftover keep-alive socket can keep it alive otherwise. If you only needed it to flush the {} response, process.stdout.write('{}\n', () => process.exit(0)) does the same thing safely. Also, do you know if codex actually complains when a Stop hook exits 0 with no output? The docs read like that's fine, in which case we could drop --respond-json entirely.

One design change, and this is my call rather than a fault in the PR: I don't want a separate vibe hooks install codex. vibe init tracks every tool with one command and hooks should feel the same. So vibe hooks install with no argument sets up whatever is actually present, claude if ~/.claude exists, codex if ~/.codex exists, prints a line per tool including the codex trust step, and skips what's not installed. Same for uninstall. Happy to take that change in this PR if you're up for it, otherwise I can merge and rework it after.

Last thing, Windows: codex has commandWindows because quoting is different there, so this probably breaks on it. A tested-on-macOS/Linux note in the README is enough for now.

@iamnotstatic
iamnotstatic self-requested a review September 3, 2026 15:53
vibe hooks install now covers claude and codex in one go, gated on
which apps are actually present, mirroring how vibe init wraps every
tool. Drops the [tool] argument. Restores process.exit(0) in __hook,
flushing the codex Stop response via the write callback, so a hook
process can never outlive its work. README: fold the codex section
into Desktop apps, note the codex minimum version and macOS/Linux
support.
@iamnotstatic

Copy link
Copy Markdown
Owner

Went ahead and pushed the remaining changes myself since people have been asking for codex support. What changed on top of your commit:

  • vibe hooks install is now one command for both apps, no tool argument. It checks which apps are actually present (~/.claude, ~/.codex) and skips the rest, so it never writes config for an app that isn't installed. Uninstall mirrors it.
  • Restored process.exit(0) in __hook. The {} response for codex Stop is flushed through the write callback instead: process.stdout.write('{}\n', () => process.exit(0)). The exitCode version could leave the process hanging on a keep-alive socket after a submit.
  • README folded into one Desktop apps section, with the codex minimum version (hooks went GA May 2026) and a macOS/Linux note.

Also confirmed the session id question myself: codex session files on disk use UUIDv7 ids, so the UUID check in hook.ts is fine as is.

Build and all 17 tests pass, smoke tested install/uninstall against a scratch home. This will go out with the next release. Thanks again for the PR, it was a good base.

init's contract is one command and you're tracked, so it now runs the
presence-gated desktop hook install after the shell wrapper, silently
skipping apps that aren't installed. Re-running init retrofits desktop
hooks for existing users. vibe uninstall mirrors it and removes desktop
hooks too; vibe hooks install/uninstall stay for doing desktop alone.
@iamnotstatic

Copy link
Copy Markdown
Owner

One more on top: vibe init now runs the desktop hook install too (silently skipping apps that aren't present), and vibe uninstall removes desktop hooks as well. init should do the whole job. The hooks subcommands stay for doing desktop alone.

@iamnotstatic
iamnotstatic merged commit 11ac6e0 into iamnotstatic:main Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants