feat(cursor): upgrade hooks to Cursor 2.4 postToolUse for Read/Grep/Shell coverage#1467
Conversation
…hell coverage
Cursor 2.4 (released 2026-01-22) shipped generic preToolUse/postToolUse hooks
matching `Shell|Read|Write|Grep|Delete|Task|MCP:<tool>`, replacing the
2.3-era beforeShellExecution hook that only fired on shell commands. The
existing integration only intercepted the shell path, so Cursor users got
graph augmentation roughly 10% as often as Claude Code users — only when
the agent dropped to rg/grep instead of using its native Read/Grep tools.
This swaps the integration over to postToolUse and ports the bash+jq
hook script to cross-platform Node:
- gitnexus-cursor-integration/hooks/hooks.json: registers a single
postToolUse hook matching Shell|Read|Grep that invokes the new
gitnexus-hook.cjs.
- gitnexus-cursor-integration/hooks/gitnexus-hook.cjs: new Node hook
mirroring the safety patterns from the Claude hook (absolute-cwd
validation, .gitnexus discovery with linked-worktree fallback,
npx.cmd on Windows, end-of-options `--` marker, debug truncation,
graceful failure). Extracts the search pattern per tool kind:
Grep -> toolInput.query; Read -> file basename stripped to identifier
chars; Shell -> existing rg/grep arg parser. Emits Cursor-shape
`{ "additional_context": "..." }` on stdout — no shell, no jq.
- gitnexus-cursor-integration/hooks/augment-shell.sh: removed (Windows
incompatible, narrower coverage).
- gitnexus/test/unit/cursor-hook.test.ts: 33 regression tests covering
manifest wiring, source-level invariants (no shell:true, npx.cmd,
isAbsolute, additional_context output shape, end-of-options marker),
extractPattern coverage per tool, and behavioral early-exit paths
(empty/invalid stdin, relative cwd, no .gitnexus, unknown tool name,
short patterns, non-search shell commands, case-insensitive matching).
- README.md / gitnexus/README.md: editor-support table now lists Cursor
as Full / hooks=Yes (postToolUse), matching reality.
- gitnexus/src/cli/augment.ts and gitnexus/src/core/augmentation/engine.ts:
doc-strings updated from `Cursor beforeShellExecution` to
`Cursor postToolUse`.
Closes #1466.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 00e1edc. Configure here.
CI Report✅ All checks passed Pipeline Status
Test Results
✅ All 8548 tests passed 1 test(s) skipped — expand for details
Code CoverageTests
📋 View full run · Generated by CI |
|
Claude finished @magyargergo's task in 5m 31s —— View job PR #1467 Review — Cursor 2.4 postToolUse Hook MigrationTodo List
Review bar for this PR
Current PR state
The 1 CI test failure appears pre-existing (Ruby block parameter closure inference, a documented skip category) but the CI workflow status still shows ❌ which blocks merge per DoD. Branch hygiene assessmentBranch hygiene verdict: clean feature/fix PR One commit ( Understanding of the changeThe legacy Findings[BLOCKER]
|
|
@abhigyanpatwari other than this flaky ci test failure, please look into these findings 🙏 |
Cursor's `timeout` field in hooks.json is in seconds (per https://cursor.com/docs/agent/hooks and the original integration's `"timeout": 5`). I'd written `10000` after blindly copying the issue body's example — that resolves to ~2.8 hours, not 10 seconds. If the script ever hangs before reaching its inner spawnSync timeouts (e.g. during stdin read), Cursor would have waited that long before killing it. Drop to `10` (seconds), matching the Claude plugin's hooks.json and giving plenty of headroom over the inner 7s augment-CLI timeout. Add a regression-guard assertion in cursor-hook.test.ts so a future ms/s mixup fails fast. Reported by Cursor Bugbot on PR #1467. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… install docs Resolves three findings from Claude reviewer on PR #1467: 1. Cursor payload field-name uncertainty (SIGNIFICANT) Claude flagged that the Grep `query` field is an unverified assumption per Cursor 2.4 docs (https://cursor.com/docs/agent/hooks). Mitigated: - Expanded Grep aliases: query | pattern | regex | q | search | searchQuery - Added pickLongestStringValue() last-resort fallback so the hook extracts *something* even if Cursor renames every documented field - Added GITNEXUS_DEBUG=1 stderr logging of the raw stdin payload so users can capture Cursor's actual contract when diagnosing silent no-ops, and report it back if aliases drift - Added Read alias `filePath` (camelCase variant alongside `file_path`) - Inline comment block citing the docs URL and the uncertainty 2. Hook command path resolution + install docs (SIGNIFICANT) Claude flagged `node ./hooks/gitnexus-hook.cjs` as relative without documented install path. Added gitnexus-cursor-integration/README.md with explicit install steps: - .cursor/hooks.json + hooks/gitnexus-hook.cjs at project root - Confirms Cursor's project-root CWD convention with doc link - Verify steps including GITNEXUS_DEBUG capture - Pattern-extraction contract table per tool - Troubleshooting: not-firing, npx fallback, wrong-pattern diagnosis 3. README "Full" overclaim for Cursor (MODERATE) Both README rows now read `Yes (postToolUse, manual install)` linking to the new install README, accurately signaling that hooks aren't automated by `gitnexus setup` like they are for Claude Code. 4. Shell quoted-pattern parser limitation (MINOR, documented) Added inline comment in gitnexus-hook.cjs documenting the known `rg "User Service"` -> `User` truncation, plus regression tests in cursor-hook.test.ts pinning the behavior so a future change is visible. Test additions (33 -> 41): - Wide-alias source coverage for Grep (query / pattern / regex / q / search / searchQuery) plus pickLongestStringValue fallback - Read alias coverage including camelCase filePath - GITNEXUS_DEBUG behavioral test: stderr quiet by default, payload echoed when env var set, stdout output contract preserved either way - Shell quoted-pattern documented behavior tests - Install README presence + content (.cursor/hooks.json, hooks/, debug diagnostics) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@claude review it and see that the above issues are fixed and it has complete test coverage |
|
Claude finished @abhigyanpatwari's task in 1m 32s —— View job PR #1467 Review — All Previous Issues Resolved ✅Todo List
Review SummaryAll three hard blockers and the significant findings from the previous review have been addressed. Here's the full status: [BLOCKER 1]
|
|
Claude finished @magyargergo's task in 5m 12s —— View job PR #1467 Review — Cursor 2.4 postToolUse Hook MigrationTodo List
Review bar for this passThe previous Claude review identified three hard blockers (timeout unit, weak timeout test, CI red) and two significant findings (payload field uncertainty, missing install docs). This pass verifies each is resolved and performs a fresh independent deep-dive on the final diff. Current PR state
Branch hygiene assessmentClassification: Category 2 — merge-from-main commits present but harmless and merge-safe Three merge-from-main commits (e4262a3, d698059, a0e6cd1). Inspected each: the churn they introduce is Lane A — Cursor hook manifest and install contract ✅
{ "version": 1, "hooks": { "postToolUse": [{ "matcher": "Shell|Read|Grep", "command": "node ./hooks/gitnexus-hook.cjs", "timeout": 10 }] } }
Lane B — Runtime hook implementation ✅ (with one pre-existing minor issue)Stdin / output contract:
The hook reads // IMPORTANT: Write to stderr, NOT stdout.
// LadybugDB's native module captures stdout fd at OS level during init,
// which makes stdout permanently broken in subprocess contexts.
process.stderr.write(result + '\n');So
The Cursor hook drops the
The Security — injection safety:
Global registry exclusion:
Worktree discovery:
Scoped to
Lane C — Tests and CI ✅ (with one acknowledged gap)CI: All 9/9 checks pass on the latest head ( Behavioral tests run the real hook binary: Tests use Timeout regression test ( expect(timeout).toBeLessThan(120);This correctly catches millisecond-scale values. Debug logging test: Verifies payload echoes to stderr only with Gap: no positive-path behavioral test: No test exercises the full augmentation path (hook emits This is a real gap — a test that verifies the hook correctly reads
Platform coverage: CI report confirms tests run on 3 platforms (ubuntu, windows, macOS). ✅ Lane D — Docs and user-facing claims ✅
Top-level README.md / gitnexus/README.md:
Minor pre-existing inaccuracy (not introduced by this PR): Cursor contract verificationThe hook payload assumptions ( Summary of findings
✅ Production-ready — approved to mergeAll three previous hard blockers are resolved. Both significant findings are addressed with appropriate defensive patterns and test-gated contracts. The implementation is correct on the critical paths:
The two remaining minor items ( |

Summary
Closes #1466.
Cursor 2.4 (released 2026-01-22) shipped generic
preToolUse/postToolUsehooks with matchersShell|Read|Write|Grep|Delete|Task|MCP:<tool>. The currentgitnexus-cursor-integration/hooks/hooks.jsonstill uses the 2.3-erabeforeShellExecution, so graph augmentation only fires when the agent runsrg/grepvia shell —ReadandGrep(the agent's far more common tools) get no graph context.This PR brings Cursor parity (and slight superset) with the Claude Code hook coverage:
gitnexus-cursor-integration/hooks/hooks.json— singlepostToolUseentry matchingShell|Read|Grep, invoking the new Node script.gitnexus-cursor-integration/hooks/gitnexus-hook.cjs(new) — cross-platform Node hook ported from the bash +jqscript. Mirrors safety patterns fromgitnexus/hooks/claude/gitnexus-hook.cjs:path.isAbsolute(cwd)validation.gitnexus/discovery with linked-worktree fallback (git rev-parse --git-common-dir)isGlobalRegistryDirheuristic)npx.cmdon Windows,--end-of-options marker (fix(hooks): pass augment pattern after end-of-options marker #200),.slice(0, 200)debug truncationstatus === 0, no broken pipe to Cursor)Grep→toolInput.query(alsopattern/regexaliases)Read→ file basename stripped to identifier chars (≥ 3 chars)Shell→ existing rg/grep arg parser{ "additional_context": "..." }(Cursor 2.4 shape), not Claude's nestedhookSpecificOutput.gitnexus-cursor-integration/hooks/augment-shell.sh(deleted) — bash +jqpipeline doesn't run on Windows and only covered Shell.gitnexus/test/unit/cursor-hook.test.ts(new) — 33 regression tests. Coverage:augment-shell.shremovalhooks.jsonwiring (matcher, command, version, no leftoverbeforeShellExecution)shell: true,npx.cmd,path.isAbsolute(cwd),.slice(0, 200),additional_context(nothookSpecificOutput), pattern length ≥ 3, end-of-options marker,.gitnexusgating, linked-worktree resolutionextractPatterncoverage per tool (Grep / Read / Shell)README.md/gitnexus/README.md— editor-support table now reads**Cursor** | Yes | Yes | Yes (postToolUse) | **Full**instead of— | MCP + Skills.gitnexus/src/cli/augment.ts+gitnexus/src/core/augmentation/engine.ts— doc-stringCursor beforeShellExecution→Cursor postToolUse; engine doc tool list expanded togrep/glob/read/search.Design notes
postToolUseonly, not alsopreToolUse. Augmentation is observational (the agent sees enriched context after the tool ran), not gating, so the second matcher is dead weight.src/auth/handler.ts.codebase_searchleft unhandled. Cursor's docs (https://cursor.com/docs/agent/hooks) don't list it as a matchable tool name. Per the issue's open question, semantic-search miss is acceptable —Read/Grep/Shellalready cover the dominant paths.Risk
Low. Change is isolated to
gitnexus-cursor-integration/plus four doc-string / README cells. The augment CLI andcore/augmentation/engine.tsAPI are unchanged — neither cares which harness called them. Cursor users on 2.3 will silently lose hook augmentation; users on 2.4+ gainRead/Grepcoverage on top of the existingShellpath.Test plan
cd gitnexus && npx vitest run test/unit/cursor-hook.test.ts— 33 / 33 pass in ~0.6scd gitnexus && npx vitest run test/unit/hooks.test.ts test/unit/cursor-hook.test.ts— 120 / 120 pass (new tests + existing Claude hook tests)cd gitnexus && npx tsc --noEmit— 0 errorscd gitnexus && npx eslint test/unit/cursor-hook.test.ts ../gitnexus-cursor-integration/hooks/gitnexus-hook.cjs src/cli/augment.ts src/core/augmentation/engine.ts— 0 issues on touched codecd gitnexus && npx prettier --check ../gitnexus-cursor-integration/hooks/{hooks.json,gitnexus-hook.cjs} test/unit/cursor-hook.test.ts src/cli/augment.ts src/core/augmentation/engine.ts— cleanOut of scope
gitnexus-cursor-integration/README — file doesn't exist; Cursor users currently copyhooks/+skills/directly. If install docs land later, they should referencegitnexus-hook.cjsnotaugment-shell.sh.setup.ts— does not currently install Cursor hooks (only MCP config + skills); unchanged here.🤖 Generated with Claude Code
Note
Low Risk
Low risk: changes are isolated to Cursor integration wiring/docs plus a new hook script and regression tests; main risk is hook behavior differences across Cursor versions or tool-input field variations causing missed augmentation.
Overview
Upgrades the Cursor integration from the legacy
beforeShellExecutionbash hook to a Cursor 2.4+postToolUsehook that augmentsShell/Read/Greptool results with GitNexus graph context.Adds a cross-platform Node hook (
gitnexus-hook.cjs) with safer pattern extraction,.gitnexus/worktree detection, and Windowsnpx.cmdhandling, deletes the oldaugment-shell.sh, and introduces unit tests to lock in the hook/manifest contract.Updates the top-level docs and
gitnexus/README.mdto mark Cursor hooks as full support with manual install instructions, and adjusts related docstrings inaugment.tsand the augmentation engine.Reviewed by Cursor Bugbot for commit 204a754. Bugbot is set up for automated code reviews on this repo. Configure here.