Hook updated_input merge inverts the documented source priority order — lowest-priority source wins

Where does the bug appear (feature/product)?

Cursor IDE

Describe the Bug

Documented behaviour (docs/agent/hooks): “All matching hooks from every source run; when responses conflict, higher-priority sources take precedence during merge.” Priority: Enterprise → Team → Project → User.

Actual behaviour: For updated_input the precedence is inverted — the lowest-priority source’s mutation is applied and higher-priority mutations are discarded, silently.

Both hooks are invoked and both receive the original tool_input, so the first half of the documented rule holds — only the conflict resolution is inverted.

Steps to Reproduce

  1. Register a preToolUse hook in /Library/Application Support/Cursor/hooks.json (enterprise) that returns {“updated_input”: {“command”: "echo MARK_ENT >/dev/null; " + original}}
  2. Register a preToolUse hook in ~/.cursor/hooks.json (user) returning the same with MARK_USR
  3. Register a postToolUse hook to observe the executed command
  4. Ask the agent to run echo priority-test

Expected Behavior

Expected: MARK_ENT applied (enterprise is highest priority)
Actual: MARK_USR applied; MARK_ENT discarded with no error or diagnostic

Operating System

MacOS

Version Information

Environment: Cursor 3.17.8, macOS 26.3 (arm64), agent build 2026.08.11-e8db854

Additional Information

  • Imported third-party (Claude Code) hooks are merged last, so they override every native scope including enterprise. Within ~/.claude/settings.json the array order decides, later winning. The docs place third-party hooks nowhere in the priority order.
  • Security impact — privilege inversion. .claude/settings.local.json is project-local and typically user-writable and gitignored. A user can neutralise an enterprise-managed hook’s input mutation with an unreviewed file, and the enterprise hook receives no signal that its mutation was discarded.
    Permission decisions behave correctly — a deny from any source blocks the call regardless of scope, so this is specific to input mutation.
  • args are dropped when importing Claude Code hooks. A hook registered as {“command”: “/path/bin”, “args”: [“selector”]} is invoked without the argv, so any hook using arguments to select behaviour silently misbehaves. Cursor sends its own camelCase payload to imported hooks (not translated to Claude’s schema), while accepting either response shape.

Does this stop you from using Cursor

No - Cursor works, but with this issue

Hey @Maor_Botton - thanks for the detailed write-up. You’re right on all counts: for updated_input, conflicting mutations currently resolve in the opposite direction from the documented source order (so a lower-priority source’s mutation wins), and imported Claude Code hooks resolve after the native scopes. It’s not anything in your setup. We’ve let the team know and this is an issue we’re tracking.

In the meantime, for anything enforcement-critical you manage at the enterprise level: permission decisions (deny / ask) do resolve correctly across every scope, so lean on those rather than input mutation until this is sorted. I’ll post here when there’s an update.

Thanks for the quick response here, waiting for you guys to resolve it :raising_hands:

Anything new?