Skip to content

[hooks] add OpenAI Codex hook integration alongside Claude Code#220

Merged
NiveditJain merged 8 commits into
mainfrom
luv-220
Apr 28, 2026
Merged

[hooks] add OpenAI Codex hook integration alongside Claude Code#220
NiveditJain merged 8 commits into
mainfrom
luv-220

Conversation

@NiveditJain
Copy link
Copy Markdown
Member

@NiveditJain NiveditJain commented Apr 28, 2026

Summary

  • Adds OpenAI Codex hook support alongside existing Claude Code support, via a per-CLI Integration adapter (src/hooks/integrations.ts). Codex's hook protocol is Claude-compatible by design — same stdin field names, same exit-code + JSON output, same {hooks: {EventName: [...]}} settings shape — so the runtime hot path needed only event-name canonicalization (snake_case → PascalCase).
  • All six documented Codex events: SessionStart, PreToolUse, PermissionRequest (Codex-only — emits the hookSpecificOutput.decision.behavior shape per Codex docs), PostToolUse, UserPromptSubmit, Stop. block-sudo now also fires for PermissionRequest so Codex sandbox-escalation requests are guarded.
  • Settings paths: ~/.codex/hooks.json (user), <cwd>/.codex/hooks.json (project). Codex doesn't have a local scope; install rejects --cli codex --scope local with a friendly error.
  • New --cli claude codex flag on policies --install/--uninstall and --hook EVENTspace-separated multi-value (or repeatable --cli claude --cli codex). The parser only consumes known CLI names after --cli, so --cli claude block-sudo correctly parses block-sudo as a positional policy name. Default behavior is interactive: detect installed CLIs (which claude / which codex) and prompt; auto-pick if only one is present; back-compat default of claude when no flag and no agent detected.
  • Permission-mode tracking for Codex: src/hooks/resolve-permission-mode.ts walks ~/.codex/sessions/<…>/<sessionId>.jsonl for turn_context.payload.approval_policy and maps never→full-auto, on-request→default.
  • isAgentInternalPath / isAgentSettingsFile generalized to also cover ~/.codex/ and .codex/hooks.json so existing path-protection rules (block-read-outside-cwd, etc.) apply to Codex unchanged.
  • Telemetry: every hook decision and install/remove now tags the originating CLI in PostHog payloads. Activity dashboard renders a per-CLI badge next to each row (Claude Code / OpenAI Codex).
  • Integration ID renamed claude-codeclaude to match the actual CLI binary name (per claude --help). User-facing displayName is still "Claude Code".
  • README: replaced shields with logo images (simpleicons CDN: cdn.simpleicons.org/claude and cdn.simpleicons.org/openai) plus a "+ more coming soon" line. CHANGELOG entry under ## Unreleased > ### Features. Docs updated: configuration.mdx, introduction.mdx, getting-started.mdx all mention Codex + --cli.
  • New .codex/hooks.json in this repo mirroring .claude/settings.json — all six Codex events under PascalCase keys with __failproofai_hook__ marker. Codex doesn't define an env var equivalent to $CLAUDE_PROJECT_DIR, so the in-repo dev hooks use a relative bun bin/failproofai.mjs path; CLAUDE.md documents the constraint and the --cli codex install recipe for production users.

Reference: closed multi-CLI mega-PR #185 — borrowed the Integration interface shape, but trimmed to two impls (claude, codex) and added PermissionRequest which #185 omitted. Other CLIs (Cursor, Copilot, Gemini, OpenCode, Pi) are easy to add later via the same adapter.

Files: 18 modified + 5 new (integrations.ts, resolve-permission-mode.ts, __tests__/hooks/integrations.test.ts, __tests__/e2e/hooks/codex-integration.e2e.test.ts, .codex/hooks.json).

Test plan

  • bun run lint — clean (only the pre-existing <img> warning in tool-input-output.tsx)
  • bunx tsc --noEmit — clean
  • bun run test:run1066/1066 pass (24 new unit tests in integrations.test.ts; existing handler.test.ts updated to assert cli/integration fields are emitted in telemetry + activity entries)
  • bun run test:e2e217/217 pass including 10 new Codex E2E tests covering: PreToolUse Bash deny, snake_case pre_tool_use canonicalization, agent-settings-file path guard, PostToolUse additionalContext, PermissionRequest deny shape, UserPromptSubmit allow, activity entry tagged with integration: codex, settings file written to .codex/hooks.json with PascalCase keys + version: 1, --scope local rejected with friendly error, uninstall idempotence
  • bun run build — Next.js build succeeds, dist/index.js regenerated, prune-standalone runs clean
  • CI pipeline green

🤖 Generated with Claude Code

NiveditJain and others added 2 commits April 28, 2026 15:06
Introduces a per-CLI Integration adapter (src/hooks/integrations.ts) so
failproofai can register, dispatch, and protect both Claude Code and
OpenAI Codex sessions out of the box. Codex's hook protocol is
Claude-compatible by design — same stdin field names, same exit-code +
JSON output (`hookSpecificOutput.permissionDecision`, `additionalContext`,
exit 2 + stderr for Stop), same `{hooks: {EventName: [...]}}` settings
shape. The work is concentrated in install plumbing and a small set of
agent-agnostic-ifications.

Highlights:
- All six documented Codex events (SessionStart, PreToolUse,
  PermissionRequest, PostToolUse, UserPromptSubmit, Stop). PermissionRequest
  emits the Codex-specific hookSpecificOutput.decision.behavior shape and
  is wired into block-sudo so sandbox-escalation requests are guarded too.
- Settings paths: ~/.codex/hooks.json (user) and <cwd>/.codex/hooks.json
  (project). Codex stores hook entries under PascalCase event keys; we
  canonicalize the snake_case stdin name to PascalCase before policy lookup.
- New `--cli claude-code|codex` flag on `policies --install/--uninstall`
  and `--hook EVENT`. Repeatable / comma-separated. Default behavior is
  interactive: detect installed CLIs (probe `which`) and prompt; auto-pick
  if only one is found; back-compat default of claude-code when no flag
  and no agent detected.
- Permission mode for Codex resolved by walking
  ~/.codex/sessions/<…>/<sessionId>.jsonl for turn_context.payload
  .approval_policy and mapping never→full-auto, on-request→default.
- Generalized isAgentInternalPath / isAgentSettingsFile to cover
  ~/.codex/ and .codex/hooks.json so existing path-protection rules apply.
- Telemetry: every hook decision and install/remove now includes the
  originating cli/integration; activity dashboard renders a per-CLI badge.
- README: added supported-CLIs section with Claude Code + OpenAI Codex
  badges and "more coming soon".
- Tests: 24 new unit tests for integrations.ts + 10 new E2E tests for the
  full Codex install→fire→deny flow (PreToolUse, PostToolUse,
  PermissionRequest, snake_case canonicalization, --scope local rejection,
  uninstall idempotence). All 1066 unit + 217 E2E tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Companion docs update for the OpenAI Codex hook integration. Spells out
the per-CLI settings file locations (~/.claude/settings.json vs
~/.codex/hooks.json) and shows how to target one or both agents via
`--cli claude-code|codex` (repeatable / comma-separated).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 2026

Warning

Rate limit exceeded

@NiveditJain has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 10 minutes and 23 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a066fd17-4f1e-412e-8f0e-1901b7c0a7ec

📥 Commits

Reviewing files that changed from the base of the PR and between 5dfa140 and e81fe15.

⛔ Files ignored due to path filters (3)
  • assets/logos/claude.svg is excluded by !**/*.svg
  • assets/logos/openai-dark.svg is excluded by !**/*.svg
  • assets/logos/openai-light.svg is excluded by !**/*.svg
📒 Files selected for processing (21)
  • .codex/hooks.json
  • CHANGELOG.md
  • CLAUDE.md
  • README.md
  • __tests__/e2e/helpers/hook-runner.ts
  • __tests__/e2e/hooks/codex-integration.e2e.test.ts
  • __tests__/hooks/handler.test.ts
  • __tests__/hooks/integrations.test.ts
  • app/actions/install-hooks-web.ts
  • app/policies/hooks-client.tsx
  • bin/failproofai.mjs
  • docs/configuration.mdx
  • docs/getting-started.mdx
  • docs/introduction.mdx
  • src/hooks/handler.ts
  • src/hooks/hook-activity-store.ts
  • src/hooks/install-prompt.ts
  • src/hooks/integrations.ts
  • src/hooks/manager.ts
  • src/hooks/resolve-permission-mode.ts
  • src/hooks/types.ts
📝 Walkthrough

Walkthrough

This PR adds comprehensive OpenAI Codex hook integration alongside existing Claude Code support. It introduces a new integration registry system, CLI-specific settings file handling with proper scope management, event name canonicalization (snake_case to PascalCase), permission-mode resolution from Codex transcripts, and activity tracking by originating CLI. The implementation spans the hook handler, policy evaluator, installer, CLI interface, and test infrastructure.

Changes

Cohort / File(s) Summary
Integration Registry & Type System
src/hooks/integrations.ts, src/hooks/types.ts
New integration abstraction defining Claude Code and Codex adapters with settings path resolution, hook entry I/O, CLI detection; adds IntegrationType, CODEX_HOOK_SCOPES, CODEX_HOOK_EVENT_TYPES, CODEX_EVENT_MAP type definitions.
Hook Handler & Event Processing
src/hooks/handler.ts, src/hooks/policy-evaluator.ts, src/hooks/resolve-permission-mode.ts
Handler now accepts optional cli parameter, canonicalizes event types via CODEX_EVENT_MAP, threads CLI through session metadata and telemetry. Policy evaluator returns PermissionRequest denial with CLI-specific output. New resolver reads Codex session transcripts to extract approval_policy.
Hook Installation & Management
src/hooks/manager.ts, src/hooks/install-prompt.ts, app/actions/install-hooks-web.ts
Manager now orchestrates multi-CLI hook installation/removal via integration registry. New resolveTargetClis helper implements CLI detection and interactive selection. Web actions accept optional CLI list and resolve targets.
CLI Entrypoint
bin/failproofai.mjs
--hook and policies commands now support --cli flag for targeting specific integrations (claude-code, codex); resolves target CLIs and validates against known integrations.
Type Extensions & Activity Tracking
src/hooks/policy-types.ts, src/hooks/hook-activity-store.ts
PolicyContext extended with optional cli field. HookActivityEntry adds optional integration field to record originating CLI.
Security & Path Protection
src/hooks/builtin-policies.ts
Expands path whitelisting to cover both ~/.claude/ and ~/.codex/, settings blocking to include .codex/hooks.json; extends block-sudo policy to guard PermissionRequest alongside PreToolUse.
UI & Dashboard
app/policies/hooks-client.tsx
Activity table adds "CLI" column with IntegrationBadge component displaying human-readable integration names (Claude Code, OpenAI Codex).
Documentation
CHANGELOG.md, README.md, docs/configuration.mdx
Documents Codex integration, hook installation via --cli flag, per-agent settings paths, CLI detection and auto-selection behavior; updates feature list to include Codex alongside Claude Code.
Test Support Infrastructure
__tests__/e2e/helpers/hook-runner.ts, __tests__/e2e/helpers/payloads.ts, __tests__/hooks/handler.test.ts, __tests__/hooks/integrations.test.ts
Hook runner accepts optional cli parameter; adds assertPermissionRequestDeny assertion. Codex payload factories with snake_case event names. Tests validate integration registry, settings path derivation, hook entry construction per CLI.
E2E Integration Tests
__tests__/e2e/hooks/codex-integration.e2e.test.ts
Comprehensive Codex protocol suite: verifies hook execution stages (PreToolUse, PostToolUse, PermissionRequest, UserPromptSubmit), event name canonicalization, .codex/hooks.json installation with PascalCase keys, scope constraints, activity logging with codex integration tag.

Sequence Diagram

sequenceDiagram
    participant CLI as Codex CLI
    participant Handler as Hook Handler
    participant Resolver as Event Resolver
    participant Evaluator as Policy Evaluator
    participant Store as Activity Store
    participant Integrations as Integration Registry

    CLI->>Handler: --hook PermissionRequest --cli codex
    Handler->>Handler: Parse cli parameter
    Handler->>Resolver: canonicalizeEventType('permission_request', 'codex')
    Resolver-->>Handler: 'PermissionRequest' (PascalCase)
    Handler->>Evaluator: evaluatePolicies(PermissionRequest, context)
    Evaluator->>Evaluator: Evaluate deny policy<br/>(applies to PermissionRequest)
    Evaluator-->>Handler: exitCode: 0, decision.behavior: 'deny'
    Handler->>Store: persistActivity({<br/>event: 'PermissionRequest',<br/>integration: 'codex',<br/>...})
    Store-->>Handler: Activity persisted
    Handler-->>CLI: JSON response with<br/>hookSpecificOutput.decision.behavior
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly Related PRs

Poem

🐰 A Codex hops into the warren,
snake_case names to PascalCase we tarren,
session transcripts read with careful cheer,
permissions guarded, integrations dear,
Two CLIs strong, now side by side! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding OpenAI Codex hook integration alongside Claude Code support, which is the primary objective of this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request description comprehensively covers the changes, includes a detailed test plan with verification results, and follows the spirit of the template by documenting what the PR does, why it's needed, and providing evidence of successful testing.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch luv-220

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

CI's E2E runner does not run `npm install -g` or `bun link` for failproofai,
so the new Codex install/uninstall E2E tests failed at the `which failproofai`
probe. Add an override env var that bypasses the PATH probe — tests now point
it at bin/failproofai.mjs in the workspace.

No production behavior change: if the override is unset or empty, the existing
`which failproofai` path runs unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hooks/handler.ts (1)

121-128: ⚠️ Potential issue | 🟡 Minor

Normalize custom_hook_error.event_type too.

This is the one telemetry path still sending raw eventType while the rest of the handler switched to canonicalEventType. For Codex hook failures that splits the same event across values like pre_tool_use and PreToolUse, which makes error analysis noisier than the success path.

Suggested fix
         void trackHookEvent(getInstanceId(), "custom_hook_error", {
           hook_name: hookName,
           error_type: isTimeout ? "timeout" : "exception",
-          event_type: eventType,
+          event_type: canonicalEventType,
           cli,
           is_convention_policy: isConvention,
           convention_scope: conventionScope ?? null,
         });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/handler.ts` around lines 121 - 128, The telemetry call to
trackHookEvent is still sending the raw eventType which causes noisy error
telemetry; update the payload in the trackHookEvent call (the object with keys
hook_name, error_type, event_type, cli, is_convention_policy, convention_scope)
to use the normalized canonicalEventType instead of eventType (e.g., set
event_type: canonicalEventType or fallback to canonicalEventType ?? eventType),
leaving other fields (hookName, isTimeout, cli, isConvention, conventionScope)
unchanged so error events match the success path.
🧹 Nitpick comments (3)
src/hooks/integrations.ts (1)

49-57: Consider adding a timeout to binaryExists to prevent hangs.

The execSync call in binaryExists could hang indefinitely if which/where stalls. This is unlikely but could cause poor UX during CLI startup.

♻️ Suggested improvement
 function binaryExists(name: string): boolean {
   try {
     const cmd = process.platform === "win32" ? `where ${name}` : `which ${name}`;
-    execSync(cmd, { encoding: "utf8", stdio: "pipe" });
+    execSync(cmd, { encoding: "utf8", stdio: "pipe", timeout: 5000 });
     return true;
   } catch {
     return false;
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/integrations.ts` around lines 49 - 57, The binaryExists function
can hang because execSync is called without a timeout; update binaryExists to
pass a timeout option to execSync (e.g., { encoding: "utf8", stdio: "pipe",
timeout: 2000 }) so which/where calls are killed after a short period, and
ensure the catch still returns false on timeout or other errors; reference the
binaryExists function, the execSync call, and the use of process.platform/cmd
when making this change.
src/hooks/manager.ts (1)

246-256: Duplicate-scope warning only covers Claude Code — consider expanding or clarifying.

The warning uses HOOK_SCOPES and hooksInstalledInSettings which are Claude Code-specific. If a user installs hooks for both Claude Code and Codex, they won't be warned about Codex duplicate scopes.

This may be intentional (comment says "Claude Code only"), but consider:

  1. Adding a similar check for Codex scopes, or
  2. Adding a comment explaining why Codex is excluded (e.g., "Codex only supports 2 scopes, so duplicates are less likely")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/manager.ts` around lines 246 - 256, The duplicate-scope warning
only checks HOOK_SCOPES (Claude Code); extend the same duplicate-detection logic
to Codex hook scopes or clearly document the exclusion. Concretely, replicate
the block that uses deduplicateScopes(HOOK_SCOPES, cwd),
hooksInstalledInSettings(...), scopeLabel(...) and the scopeList/duplicates
handling for the Codex scopes constant (e.g., CODEX_HOOK_SCOPES or the actual
Codex scopes identifier) so users get the same warning for Codex, or add a
clarifying comment above this block explaining why Codex is intentionally
excluded.
bin/failproofai.mjs (1)

236-252: Consider extracting duplicated --cli parsing logic into a helper.

The --cli flag parsing and validation logic is nearly identical between the install (lines 236-252) and uninstall (lines 309-325) flows. This duplication increases maintenance burden.

♻️ Suggested refactor
// Add helper function before the runCli() function:
function parseCliFlags(subArgs) {
  const cliFlagIdxs = subArgs.map((a, i) => (a === "--cli" ? i : -1)).filter((i) => i >= 0);
  const cliFlagValues = [];
  const cliConsumedIdxs = new Set();
  const VALID_CLIS = new Set(["claude-code", "codex"]);
  for (const idx of cliFlagIdxs) {
    const value = subArgs[idx + 1];
    if (!value || value.startsWith("-")) {
      throw new CliError("Missing value for --cli. Valid values: claude-code, codex");
    }
    for (const part of value.split(",").map((s) => s.trim()).filter(Boolean)) {
      if (!VALID_CLIS.has(part)) {
        throw new CliError(`Invalid --cli value: ${part}. Valid: claude-code, codex`);
      }
      cliFlagValues.push(part);
    }
    cliConsumedIdxs.add(idx + 1);
  }
  return { cliFlagValues, cliConsumedIdxs };
}

Also applies to: 309-325

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/failproofai.mjs` around lines 236 - 252, Extract the duplicated --cli
parsing/validation into a helper function (e.g., parseCliFlags) that accepts
subArgs and returns { cliFlagValues, cliConsumedIdxs }; move the
constants/validation (VALID_CLIS) and error throws (CliError) into that helper
and call it from both the install and uninstall flows (replacing the inline
loops at the blocks that currently compute
cliFlagIdxs/cliFlagValues/cliConsumedIdxs), ensuring the helper reads subArgs,
checks for missing values and invalid parts (same messages), splits comma lists
and populates cliFlagValues and cliConsumedIdxs so both places reuse the logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@__tests__/e2e/hooks/codex-integration.e2e.test.ts`:
- Around line 173-176: The failing E2E tests call execSync directly (using
BINARY_PATH) without setting FAILPROOFAI_DIST_PATH, causing
resolveFailproofaiBinary() to not find the binary; update each execSync
invocation in the install/uninstall suite (the calls around the execSync using
`bun ${BINARY_PATH} policies --install` / uninstall) to include
FAILPROOFAI_DIST_PATH in the env (alongside HOME and
FAILPROOFAI_TELEMETRY_DISABLED), or refactor by adding a helper (e.g.,
runPoliciesCmd) that wraps execSync and injects FAILPROOFAI_DIST_PATH (resolved
via the repo root) so all calls use the same env trusted by runHook().

In `@app/actions/install-hooks-web.ts`:
- Around line 27-31: removeHooksWebAction currently forwards cli as undefined
into removeHooks which triggers the default fallback in src/hooks/manager.ts
(["claude-code"]) and can leave unexpected hooks behind; change
removeHooksWebAction so that when cli is omitted it resolves the same CLI list
used by the install-side path (i.e. replicate the install resolution logic or
call the same helper used by the installer) and pass that resolved
IntegrationType[] into removeHooks instead of undefined, referencing
removeHooksWebAction and removeHooks (and the fallback in src/hooks/manager.ts)
to locate where to apply the fix.

In `@CHANGELOG.md`:
- Around line 5-6: Update the CHANGELOG.md bullet that starts "Add OpenAI Codex
hook integration." by appending the PR trailer " (`#220`)" to the end of that
bullet so it matches the repository changelog format (i.e., change the existing
Features entry line to end with " (`#220`)").

In `@docs/configuration.mdx`:
- Around line 199-206: Update the docs paragraph describing the --cli fallback
to include the two missing branches: state that when no agent binaries are
detected by the "which claude / which codex" checks the installer defaults to
targeting "claude-code", and state that in non-interactive runs (CI/TTY-less)
the installer will skip prompting and target both detected CLIs rather than
prompting; keep references to the existing examples/commands (e.g., failproofai
policies --install --cli) and the detection logic so readers can find the
matching implementation paths.

In `@README.md`:
- Around line 26-36: Change the heading level for "Supported agent CLIs" from h3
to h2 to avoid skipping h2 (replace "### Supported agent CLIs" with "##
Supported agent CLIs"), or alternatively move the badges into an existing h2
section so the document hierarchy remains sequential; update the heading text
exactly "Supported agent CLIs" and ensure any surrounding TOC or references
match the new level.

In `@src/hooks/install-prompt.ts`:
- Around line 72-86: Change the onKey handler in install-prompt.ts to accept the
(str, key) signature and detect Ctrl+C via key.ctrl && key.name === "c" (same
pattern as promptPolicySelection()), restoring terminal state (reset setRawMode
to wasRaw, removeListener("keypress", onKey), and write a newline) before
aborting; on Ctrl+C either reject the Promise with an AbortError or call
process.exit(1) instead of falling through to resolve(detected). Also keep the
existing branches for "c" and "d" (after normalizing str) so ordinary letter
input still resolves correctly.

---

Outside diff comments:
In `@src/hooks/handler.ts`:
- Around line 121-128: The telemetry call to trackHookEvent is still sending the
raw eventType which causes noisy error telemetry; update the payload in the
trackHookEvent call (the object with keys hook_name, error_type, event_type,
cli, is_convention_policy, convention_scope) to use the normalized
canonicalEventType instead of eventType (e.g., set event_type:
canonicalEventType or fallback to canonicalEventType ?? eventType), leaving
other fields (hookName, isTimeout, cli, isConvention, conventionScope) unchanged
so error events match the success path.

---

Nitpick comments:
In `@bin/failproofai.mjs`:
- Around line 236-252: Extract the duplicated --cli parsing/validation into a
helper function (e.g., parseCliFlags) that accepts subArgs and returns {
cliFlagValues, cliConsumedIdxs }; move the constants/validation (VALID_CLIS) and
error throws (CliError) into that helper and call it from both the install and
uninstall flows (replacing the inline loops at the blocks that currently compute
cliFlagIdxs/cliFlagValues/cliConsumedIdxs), ensuring the helper reads subArgs,
checks for missing values and invalid parts (same messages), splits comma lists
and populates cliFlagValues and cliConsumedIdxs so both places reuse the logic.

In `@src/hooks/integrations.ts`:
- Around line 49-57: The binaryExists function can hang because execSync is
called without a timeout; update binaryExists to pass a timeout option to
execSync (e.g., { encoding: "utf8", stdio: "pipe", timeout: 2000 }) so
which/where calls are killed after a short period, and ensure the catch still
returns false on timeout or other errors; reference the binaryExists function,
the execSync call, and the use of process.platform/cmd when making this change.

In `@src/hooks/manager.ts`:
- Around line 246-256: The duplicate-scope warning only checks HOOK_SCOPES
(Claude Code); extend the same duplicate-detection logic to Codex hook scopes or
clearly document the exclusion. Concretely, replicate the block that uses
deduplicateScopes(HOOK_SCOPES, cwd), hooksInstalledInSettings(...),
scopeLabel(...) and the scopeList/duplicates handling for the Codex scopes
constant (e.g., CODEX_HOOK_SCOPES or the actual Codex scopes identifier) so
users get the same warning for Codex, or add a clarifying comment above this
block explaining why Codex is intentionally excluded.
🪄 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

Run ID: 55374b66-3007-470b-ad25-792f6ac7b79a

📥 Commits

Reviewing files that changed from the base of the PR and between cfeacbb and 5dfa140.

📒 Files selected for processing (21)
  • CHANGELOG.md
  • README.md
  • __tests__/e2e/helpers/hook-runner.ts
  • __tests__/e2e/helpers/payloads.ts
  • __tests__/e2e/hooks/codex-integration.e2e.test.ts
  • __tests__/hooks/handler.test.ts
  • __tests__/hooks/integrations.test.ts
  • app/actions/install-hooks-web.ts
  • app/policies/hooks-client.tsx
  • bin/failproofai.mjs
  • docs/configuration.mdx
  • src/hooks/builtin-policies.ts
  • src/hooks/handler.ts
  • src/hooks/hook-activity-store.ts
  • src/hooks/install-prompt.ts
  • src/hooks/integrations.ts
  • src/hooks/manager.ts
  • src/hooks/policy-evaluator.ts
  • src/hooks/policy-types.ts
  • src/hooks/resolve-permission-mode.ts
  • src/hooks/types.ts

Comment thread __tests__/e2e/hooks/codex-integration.e2e.test.ts
Comment thread app/actions/install-hooks-web.ts Outdated
Comment thread CHANGELOG.md Outdated
Comment thread docs/configuration.mdx Outdated
Comment thread README.md Outdated
Comment thread src/hooks/install-prompt.ts
Comment thread src/hooks/resolve-permission-mode.ts
NiveditJain and others added 5 commits April 28, 2026 15:29
…d .codex/hooks.json

Three follow-up changes from PR review feedback:

1. Integration ID renamed `claude-code` → `claude`. The CLI binary is `claude`
   (per `claude --help`), so the integration key now matches. `displayName` stays
   "Claude Code" in user-facing strings; only the machine-readable id changed.
   Sweep covers types/handler/manager/install-prompt/resolve-permission-mode,
   the dashboard's IntegrationBadge, both web actions, all unit + E2E tests,
   README/CHANGELOG/docs/configuration.mdx/getting-started.mdx/introduction.mdx.

2. `--cli` flag now accepts space-separated multi-values (`--cli claude codex`)
   instead of comma-separated (`--cli claude,codex`). The parser consumes
   positional values after `--cli` only while they match a known CLI name —
   so `--cli claude block-sudo` correctly parses block-sudo as a positional
   policy name, not a malformed --cli value. Repetition still works
   (`--cli claude --cli codex`). Help text and EXAMPLES updated.

3. README's per-CLI shields replaced with actual logo images (simpleicons CDN:
   `cdn.simpleicons.org/claude` and `cdn.simpleicons.org/openai`) and a
   "+ more coming soon" line.

4. New `.codex/hooks.json` for this repo, mirroring the existing
   `.claude/settings.json`. All six Codex events under PascalCase keys with
   the `__failproofai_hook__` marker. Per Codex docs, Codex sets no env-var
   equivalent of `$CLAUDE_PROJECT_DIR`, so the command uses a relative
   `bun bin/failproofai.mjs` path (Codex spawns hooks with the project root
   as cwd in the common case). CLAUDE.md grew a section explaining this and
   the `--cli codex` install recipe for production users.

Verified: 1066/1066 unit tests pass, 217/217 E2E tests pass, lint clean,
build green. Re-confirmed against the live Codex hooks docs that our stdin
field reads (session_id, transcript_path, cwd, hook_event_name, tool_name,
tool_input, tool_response, prompt) and output protocol
(`hookSpecificOutput.permissionDecision: "deny"` for PreToolUse,
`hookSpecificOutput.decision.behavior: "deny"` for PermissionRequest,
`hookSpecificOutput.additionalContext` for PostToolUse, exit 2 + stderr
fallback) match exactly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Six fixes from the bot review on the original Codex-integration commits:

1. README "Supported agent CLIs" heading lifted from h3 to h2 to fix
   markdownlint MD001 (heading-level jump from h1).
2. CHANGELOG.md Codex feature bullet now ends with the required
   `(#220)` PR-number trailer per repo convention.
3. docs/configuration.mdx fallback-branches section now covers all four
   cases the installer actually handles: one CLI detected (auto-pick),
   both detected interactive (prompt), both detected non-interactive
   (install for both), neither detected (fall back to claude with a
   warning).
4. app/actions/install-hooks-web.ts removeHooksWebAction mirrors the
   install-side resolution: when the dashboard omits cli, it resolves
   via detectInstalledClis() instead of forwarding undefined into
   removeHooks() and silently falling back to ["claude"]. Prevents a
   web uninstall from leaving Codex hooks behind.
5. src/hooks/install-prompt.ts raw-mode CLI picker now accepts the
   (str, key) keypress signature and detects Ctrl+C/Ctrl+D via key.ctrl
   + key.name, restoring the terminal and exiting 130 on abort instead
   of silently resolving to "install for both". Mirrors the contract
   used by promptPolicySelection().
6. src/hooks/resolve-permission-mode.ts no longer scans the full
   ~/.codex/sessions tree on every Codex hook. Adds a disk cache at
   ~/.failproofai/cache/codex-session-paths.json (sessionId → transcript
   path, verified-on-read) and tries today + yesterday's date directories
   before falling back to the full tree. Bounds the common-case latency
   to O(1) lookups for the second+ tool use of any active session.

Verified: 1066/1066 unit, 217/217 E2E, lint clean (only the pre-existing
<img> warning), tsc clean, build green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`cdn.simpleicons.org/openai` returns 404 — simpleicons doesn't ship an
OpenAI mark. Swap both Claude + OpenAI logos to iconify
(api.iconify.design/logos/{anthropic,openai}-icon.svg) which:

  • Both URLs return HTTP 200 colored SVGs (verified via curl -I)
  • Iconify covers both brands, so the visual treatment is consistent
  • Plain SVG, no color-param suffix needed

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two CDN attempts didn't render reliably on GitHub:
  • simpleicons.org returned 404 for /openai (no such slug)
  • iconify.design returned valid SVGs but with em-based dimensions
    (width="0.99em" height="1em") — GitHub's image proxy/camo can't
    scale em units from an <img height="48"> attribute, so the icons
    appeared blank/missing on the rendered README. Anthropic's iconify
    SVG also used fill="#181818" (near-black), invisible on dark theme.

Fix: vendor the simpleicons SVGs into assets/logos/ and add explicit
brand-color fills:
  • claude.svg with fill="#D97757" (Anthropic orange)
  • openai.svg with fill="#10A37F" (OpenAI green)

Reference via relative paths (`assets/logos/claude.svg`) which GitHub
resolves correctly when rendering README.md, and which scale cleanly
because the SVGs use viewBox="0 0 24 24" with no inline pixel
dimensions — the <img width/height> attrs control render size.

Verified detectInstalledClis() works on a machine with both CLIs:
  which claude → /home/nivedit/.local/bin/claude
  which codex  → /home/nivedit/.nvm/versions/node/v25.8.2/bin/codex
  detectInstalledClis() → ["claude", "codex"]

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…en for Codex logo

`#10A37F` is OpenAI's *ChatGPT* brand color, not Codex's. Per OpenAI's own
guidelines the spiral mark renders black on light backgrounds and white on
dark backgrounds, not green.

Replace assets/logos/openai.svg with two theme variants:
  • openai-light.svg → fill="#000000" (black; renders on light theme)
  • openai-dark.svg  → fill="#FFFFFF" (white; renders on dark theme)

Reference both via <picture> in README so GitHub serves the right one based
on `prefers-color-scheme`. Falls back to the light variant when the browser
or renderer doesn't support media queries.

Claude logo stays #D97757 (Anthropic's brand orange) — it's the canonical
brand color and reads well on both light and dark themes, so no <picture>
needed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@NiveditJain NiveditJain merged commit 3c16d9e into main Apr 28, 2026
9 checks passed
NiveditJain added a commit that referenced this pull request Apr 29, 2026
Bumps package.json from 0.0.9-beta.2 to 0.0.9 and rolls the ## Unreleased
changelog section into ## 0.0.9 — 2026-04-28.

0.0.9 contents:

Features:
- OpenAI Codex hook integration via `failproofai policies --install --cli codex`
  (or --cli claude codex for both); supports all six Codex hook events,
  PermissionRequest wired through policy-evaluator, per-CLI telemetry tagging,
  interactive arrow-key CLI selector when both agents detected (#220, #222, #223)
- Surface Slack community link in CLI banner and Reach Us menu (#225)

Fixes:
- Trailing blank line after enabled-policies summary in install output (#224)
- Resolve hook bin via $CLAUDE_PROJECT_DIR (was relative path) (#219)
- Mintlify validation of Arabic built-in-policies docs

Docs:
- Bump built-in policy count from 32 to 39 in README and translations (#207)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
NiveditJain added a commit that referenced this pull request Apr 29, 2026
Promotes the version from 0.0.9-beta.3 to 0.0.9 stable. The
## 0.0.9 — 2026-04-28 changelog section is already up to date with the
contents below; no CHANGELOG churn required.

0.0.9 contents:

Features:
- OpenAI Codex hook integration via `failproofai policies --install --cli codex`
  (or --cli claude codex for both); supports all six Codex hook events,
  PermissionRequest wired through policy-evaluator, per-CLI telemetry tagging,
  interactive arrow-key CLI selector when both agents detected (#220, #222, #223)
- Activity dashboard CLI filter alongside event-type, policy, and session-id
  filters; Codex sessions in the activity feed are now clickable, with the
  existing log viewer rendering Codex transcripts via lib/codex-sessions.ts (#226)
- Surface Slack community link in CLI banner and Reach Us menu (#225)
- Show OpenAI Codex projects on the /projects page alongside Claude Code projects,
  with CLI badges per row and per session; /project/[name] is Codex-aware and the
  session viewer renders the CLI badge beside the Session Log header (#232)

Fixes:
- Trailing blank line after enabled-policies summary in install output (#224)
- Resolve hook bin via $CLAUDE_PROJECT_DIR (was relative path) (#219)
- Mintlify validation of Arabic built-in-policies docs
- Mintlify parse error in docs/de/dashboard.mdx (#229)
- block-read-outside-cwd false-deny on globs and -v host:/path (#230)
- Decouple hook chain from clsx/tailwind via lib/format-date.ts (#231)

Docs:
- Bump built-in policy count from 32 to 39 in README and translations (#207)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
NiveditJain added a commit that referenced this pull request Apr 29, 2026
* feat: add GitHub Copilot CLI integration (beta)

Mirrors the Codex rollout (#220 + #226 + #232) for GitHub Copilot CLI:
hook install/handler/path-protection, activity dashboard CLI filter +
session viewer fallback, /projects listing + per-project sessions.

Hooks install in Copilot's "VS Code compatible" PascalCase mode so the
existing handler, snake_case payload parser, and hookSpecificOutput
output shape all work unchanged. User scope writes
~/.copilot/hooks/failproofai.json; project scope writes
<cwd>/.github/hooks/failproofai.json with version:1 and bash+powershell
+timeoutSec entries (verified against Copilot CLI 1.0.39).

Dashboard parser was rewritten against the real events.jsonl schema
inspected from a live install — dotted-path record types
(session.start, user.message, assistant.message, tool.execution_start/
_complete, …) rather than the camelCase hook payload format the docs
hinted at. Project listing reads cwd from each session's workspace.yaml
(always present, even pre-interaction).

Adds lib/cli-registry.ts as the single source of truth for per-CLI
metadata (label, badge classes, dashboard providers); future CLIs land
with one registry entry instead of touching badge/filter/merge code.

Marked beta because Copilot's events.jsonl schema is not publicly
documented; parser is best-effort and falls open on unknown record
types.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: green CI + uninstall prompt heading

CI build failed with Turbopack "Module not found: Can't resolve
'fs/promises'" because lib/cli-registry.ts declared lazy
`getProjects` / `loadSessionLog` providers via dynamic imports of
codex-projects / copilot-projects. Even though no caller used them
yet, Turbopack traced the dynamic-import expressions through the
client-bundled CliBadge / project-list / hooks-client consumers and
pulled Node-only modules into the client graph. Drop the providers
from cli-registry; it's now a pure metadata module (label +
badgeClasses). lib/projects.ts and the session viewer page already
have their own targeted dynamic imports.

Also: remove cross-module import in lib/copilot-projects.ts (was
importing `getCopilotSessionStateRoot` from copilot-sessions); inline
the trivial path computation so the two trees stay independent
(mirrors how lib/codex-projects.ts and lib/codex-sessions.ts are
arranged).

Bug fix surfaced in conversation: `failproofai policies --uninstall`
prompt heading said "Install Hooks" / "Choose where to install:" even
though the action was uninstall. Add an `action: "install" |
"uninstall"` parameter to resolveTargetClis + promptCliTargetSelection
so the heading reads "Remove Hooks" / "Choose where to remove from:"
during uninstall, and the non-interactive single-CLI log line says
"removing hooks from <CLI>" instead of "installing hooks for <CLI>".
Threaded through both bin/failproofai.mjs call sites.

Tests: 1158 unit + 227 e2e pass; tsc clean; lint clean; bun run build
clean (Turbopack no longer errors).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: address CodeRabbit review on PR #236

Critical / major:
- lib/cli-registry.ts: isKnownCli now uses
  Object.prototype.hasOwnProperty.call instead of `in` so inherited
  prototype keys ("toString", "constructor", "hasOwnProperty") don't
  validate as known CLIs.
- lib/copilot-sessions.ts: getCopilotSessionDir +
  findCopilotTranscript / findCopilotWorkspace reject path-traversal
  sessionIds (`../foo`, `..`, `/abs/path`) by `path.resolve`-ing the
  candidate and asserting it stays under getCopilotSessionStateRoot().
- lib/copilot-projects.ts: track `hasTranscript` per session-state dir
  and filter workspace-only sessions out of metasToSessionFiles —
  /project/[cwd] no longer renders clickable rows that lead to
  "Session log file not found." Workspace-only sessions still surface
  the project itself on /projects.
- lib/copilot-projects.ts + lib/projects.ts: switch fs/promises, os,
  path imports to the `node:` specifier form (mirrors lib/codex-
  sessions.ts and is what Turbopack expects).
- __tests__/e2e/hooks/copilot-integration.e2e.test.ts: pass
  FAILPROOFAI_BINARY_OVERRIDE in the --scope local rejection test so
  it doesn't depend on a global failproofai install in PATH.
- __tests__/lib/projects.test.ts: add Copilot-aggregation
  assertions — Claude+Copilot merge, Claude+Codex+Copilot merge,
  Copilot-only surfacing, and graceful fall-through when
  getCopilotProjects() rejects.

Minor:
- CHANGELOG.md: shorten Copilot Features + Fixes entries to the repo's
  one-line + (#236) format.
- docs/dashboard.mdx: rewrite Activity-tab Session viewer wording so
  the badge explanation lists all three CLIs and the click-through
  description includes Copilot CLI.

Tests: 1165 unit + 227 e2e pass; tsc + bun run build + lint clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: cleanups from self-review on PR #236

Three minor, no-behavior-change tidies surfaced during /review:

- lib/copilot-projects.ts: replace `open(path).stat().close()` with
  direct `stat(path)` from node:fs/promises in `statMtime`. Halves the
  FD pressure (was opening two handles per session-state dir for mtime
  alone) and removes the try/finally close dance. The codex-projects
  equivalent uses open() because it reuses the handle for
  readFirstLine; we don't, so the simpler form is cleaner.

- lib/copilot-sessions.ts: rename `CopilotToolToolTelemetry` (typo)
  to `CopilotToolTelemetry`.

- lib/copilot-sessions.ts: inline `getMs(date)` — the wrapper was just
  `date.getTime()` and added indirection at four call sites.

1165 unit + 227 e2e + tsc + bun run build all green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: use current GitHub Copilot mark with light/dark variants

The previous copilot.svg used an older SimpleIcons path with a
hard-coded fill="#000000", which rendered invisibly on GitHub's dark
README theme. Replaced with the current canonical githubcopilot.svg
path from simple-icons/simple-icons, split into copilot-light.svg
and copilot-dark.svg, and switched the README block to <picture>
with prefers-color-scheme — same pattern as the OpenAI logo.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs: changelog entry for Copilot logo fix

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: address remaining CodeRabbit nits on PR #236

- project-list CLI filter onChange now validates via isKnownCli, mirroring
  the URL-read path on line 54-57. Defends against <option> values drifting
  out of sync with KNOWN_CLI_IDS.
- resolve-permission-mode fallback comment broadened to cover the codex
  without-sessionId case in addition to copilot / unknown.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: address CodeRabbit follow-up review on PR #236

Four findings from the latest CodeRabbit pass:

- lib/copilot-projects.ts: filter `hasTranscript` in getCopilotProjects
  and getCopilotSessionsByEncodedName so workspace-only sessions don't
  surface as /projects rows whose detail page would show an empty
  session list. Mirrors the filter already in metasToSessionFiles.
- lib/copilot-sessions.ts: wrap readFile + parseCopilotLog in try/catch
  in getCopilotSessionLog. findCopilotTranscript only proves existence
  at lookup time; the file can be removed/rotated before the async
  readFile lands. Preserve the nullable contract instead of throwing
  into the session page.
- lib/codex-projects.ts: switch fs/promises, os, path imports to the
  node:-prefixed form for Turbopack resolver consistency. Other
  per-CLI modules already use this form.
- __tests__/e2e/hooks/copilot-integration.e2e.test.ts: build the
  JWT-shaped fixture string at runtime via base64url Buffer encodes
  instead of embedding a contiguous JWT literal in source. Avoids
  tripping secret scanners (and this repo's own sanitize-jwt detector)
  on test code. Each segment is 10+ base64url chars so it still
  matches JWT_RE in builtin-policies.ts.

Tests: existing copilot-projects tests that previously asserted
workspace-only sessions surface as /projects rows are updated to
match the new behavior; new positive tests cover the filter and the
read-race path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: silence one Turbopack NFT warning in next.config.ts

Replace `readFileSync(join(__dirname, "package.json"))` with a static
JSON import. Turbopack's Node File Tracer was flagging the runtime
filesystem call as a "could touch the whole project" operation,
producing one of two "Encountered unexpected file in NFT list"
warnings on `bun run build`. Static `import pkg from "./package.json"`
resolves at compile time so NFT no longer treats the config as
dynamic.

This drops the build warning count from 2 → 1; the remaining
warning is a Turbopack quirk where `next.config.ts` shows up
transitively in the NFT trace from server components and doesn't
have a clean user-code fix.

`bun run dev` is unaffected and starts clean. tsconfig already has
`resolveJsonModule: true` so the static import type-checks
without changes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat: dogfood Copilot hooks via .github/hooks/failproofai.json

Mirror the existing .claude/settings.json and .codex/hooks.json
contributor configs by shipping a project-scope Copilot hooks file
so devs working on this repo with the GitHub Copilot CLI get
failproofai hooks active automatically — no `policies --install`
step required.

The file lives at .github/hooks/failproofai.json (Copilot's
project-scope path; .copilot/ is the user-scope path) and uses
Copilot's "VS Code compatible" form: `version: 1`, PascalCase event
names, and `bash` + `powershell` + `timeoutSec` per entry. All six
COPILOT_HOOK_EVENT_TYPES are wired (SessionStart, SessionEnd,
UserPromptSubmit, PreToolUse, PostToolUse, Stop) to
`bun bin/failproofai.mjs --hook <Event> --cli copilot`. Path is
relative since Copilot — like Codex — spawns hooks with the project
root as cwd and exposes no $COPILOT_PROJECT_DIR equivalent.

CLAUDE.md gains a `### Copilot hooks` subsection documenting the
path choice, the relative-vs-absolute caveat, and the recommended
production-user install command. CHANGELOG's existing Unreleased
Copilot Features bullet is extended to call out the dogfood config.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
NiveditJain added a commit that referenced this pull request Apr 29, 2026
Mirrors the Codex (#220, #226, #232) and Copilot (#236) rollouts for
Cursor Agent CLI. Hook install/uninstall, handler canonicalization,
policy-evaluator output adapter, path-protection, activity dashboard
filter + per-CLI badge, /projects merge, and session viewer fallback.

Cursor's hook protocol differs from Claude/Codex/Copilot:
  - camelCase event keys (preToolUse, beforeSubmitPrompt, …) — handler
    canonicalizes via CURSOR_EVENT_MAP before policy lookup.
  - Flat array of hook entries per event (no Claude-style {hooks: [...]}
    matcher wrapper); writeHookEntries / removeHooksFromFile /
    hooksInstalledInSettings all walk the flat structure directly.
  - Stdout decision shape is {permission, user_message, agent_message,
    additional_context, followup_message} (not hookSpecificOutput).
    policy-evaluator.ts now branches on session.cli === "cursor" for the
    deny / instruct / allow-with-info paths and emits the Cursor shape.
  - No documented permission-mode equivalent; resolve-permission-mode.ts
    falls into the existing "default" branch (same as Copilot).
  - Detected via cursor-agent (preferred) or agent (legacy alias).

Settings paths:
  user    → ~/.cursor/hooks.json
  project → <cwd>/.cursor/hooks.json

Path-protection (isAgentInternalPath + isAgentSettingsFile) extended to
cover ~/.cursor/ and .cursor/hooks.json so the agent can't disable its
own hooks.

Frontend: lib/cli-registry.ts adds a "Cursor Agent" entry with an
emerald badge; lib/projects.ts merges cursor projects; the project page
(app/project/[name]/page.tsx) and session viewer
(app/project/[name]/session/[sessionId]/page.tsx) extend the
external-CLI fallback chain. The activity feed (hooks-client.tsx)
recognizes /.cursor/ transcript paths.

Adds lib/cursor-projects.ts + lib/cursor-sessions.ts as scaffold
parsers — Cursor's transcript layout is undocumented, so the modules
probe candidate subdirs (agent-sessions/, conversations/, sessions/)
and metadata files (meta.json/session.json/workspace.{json,yaml}) and
gracefully return [] when the directory layout doesn't match. The
parser handles dotted-path record types (session.start, user.message,
…) and degrades to system entries for unknown types so nothing is
silently dropped — same shape as the Copilot parser.

Drops the .cursor/hooks.json file at the project root (mirrors the
existing .codex/hooks.json and .github/hooks/failproofai.json) so
contributors developing failproofai with Cursor get hooks active
automatically. Adjusts .gitignore to track that single file while
leaving the rest of .cursor/ ignored.

Tests: 60 unit suites / 1228 tests pass; 9 e2e suites / 235 tests pass
(including the new __tests__/e2e/hooks/cursor-integration.e2e.test.ts
which covers PreToolUse deny + agent-settings guard +
beforeSubmitPrompt allow + activity tagging + install/uninstall flows).

Bumps version to 0.0.10-beta.1.

Also fixes a pre-existing parity gap: the project detail page
(app/project/[name]/page.tsx) only listed Claude + Codex sessions,
missing Copilot (and now Cursor); it now merges all four sources.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
NiveditJain added a commit that referenced this pull request Apr 30, 2026
* docs: note GitHub Copilot CLI testing is ongoing

Update the README beta callout to clarify that GitHub Copilot CLI
support is still under active testing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [luv-245] feat: add Cursor Agent CLI integration (beta)

Mirrors the Codex (#220, #226, #232) and Copilot (#236) rollouts for
Cursor Agent CLI. Hook install/uninstall, handler canonicalization,
policy-evaluator output adapter, path-protection, activity dashboard
filter + per-CLI badge, /projects merge, and session viewer fallback.

Cursor's hook protocol differs from Claude/Codex/Copilot:
  - camelCase event keys (preToolUse, beforeSubmitPrompt, …) — handler
    canonicalizes via CURSOR_EVENT_MAP before policy lookup.
  - Flat array of hook entries per event (no Claude-style {hooks: [...]}
    matcher wrapper); writeHookEntries / removeHooksFromFile /
    hooksInstalledInSettings all walk the flat structure directly.
  - Stdout decision shape is {permission, user_message, agent_message,
    additional_context, followup_message} (not hookSpecificOutput).
    policy-evaluator.ts now branches on session.cli === "cursor" for the
    deny / instruct / allow-with-info paths and emits the Cursor shape.
  - No documented permission-mode equivalent; resolve-permission-mode.ts
    falls into the existing "default" branch (same as Copilot).
  - Detected via cursor-agent (preferred) or agent (legacy alias).

Settings paths:
  user    → ~/.cursor/hooks.json
  project → <cwd>/.cursor/hooks.json

Path-protection (isAgentInternalPath + isAgentSettingsFile) extended to
cover ~/.cursor/ and .cursor/hooks.json so the agent can't disable its
own hooks.

Frontend: lib/cli-registry.ts adds a "Cursor Agent" entry with an
emerald badge; lib/projects.ts merges cursor projects; the project page
(app/project/[name]/page.tsx) and session viewer
(app/project/[name]/session/[sessionId]/page.tsx) extend the
external-CLI fallback chain. The activity feed (hooks-client.tsx)
recognizes /.cursor/ transcript paths.

Adds lib/cursor-projects.ts + lib/cursor-sessions.ts as scaffold
parsers — Cursor's transcript layout is undocumented, so the modules
probe candidate subdirs (agent-sessions/, conversations/, sessions/)
and metadata files (meta.json/session.json/workspace.{json,yaml}) and
gracefully return [] when the directory layout doesn't match. The
parser handles dotted-path record types (session.start, user.message,
…) and degrades to system entries for unknown types so nothing is
silently dropped — same shape as the Copilot parser.

Drops the .cursor/hooks.json file at the project root (mirrors the
existing .codex/hooks.json and .github/hooks/failproofai.json) so
contributors developing failproofai with Cursor get hooks active
automatically. Adjusts .gitignore to track that single file while
leaving the rest of .cursor/ ignored.

Tests: 60 unit suites / 1228 tests pass; 9 e2e suites / 235 tests pass
(including the new __tests__/e2e/hooks/cursor-integration.e2e.test.ts
which covers PreToolUse deny + agent-settings guard +
beforeSubmitPrompt allow + activity tagging + install/uninstall flows).

Bumps version to 0.0.10-beta.1.

Also fixes a pre-existing parity gap: the project detail page
(app/project/[name]/page.tsx) only listed Claude + Codex sessions,
missing Copilot (and now Cursor); it now merges all four sources.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [luv-245] fix: address CodeRabbit review feedback

Five fixes from the bot review on PR #245:

1. .cursor/hooks.json: drop the `__failproofai_hook__` marker from the
   static dev config so the file matches Cursor's documented hook entry
   schema ({type, command, timeout}). The marker is still written by
   `cursor.buildHookEntry` for dynamically installed hooks; the legacy
   command-substring fallback in `isMarkedHook` identifies these static
   entries during uninstall.

2. CHANGELOG.md: fold the new Cursor entries into the existing Unreleased
   subsections (Features / Fixes / Docs) instead of creating a duplicate
   `### Features` heading. Resolves the MD024 markdownlint complaint.

3. lib/cursor-projects.ts: parse meta.json with `JSON.parse` first so
   escape sequences in JSON strings (Windows paths like
   `C:\\Users\\alice\\repo`) are decoded. Falls back to the YAML-ish regex
   only when the file isn't valid JSON.

4. lib/cursor-projects.ts: replace `findFirstExisting` (returns first
   existing path) with `findFirstUsableMeta` (returns first path whose
   parsed cwd is non-empty). Stops a stale `meta.json` from shadowing a
   valid `workspace.yaml` and dropping the session.

5. lib/cursor-sessions.ts: validate `data.content` / `data.text` with
   `typeof === "string"` before treating them as text in the user.message
   and assistant.message branches. Avoids surfacing non-string values via
   the `as string` casts.

All 1228 unit + 235 e2e tests still pass. Lint clean (one pre-existing
unrelated `<img>` warning).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
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.

1 participant