# Cursor hooks for production agent workflows: block, audit, and verify actions

> How to use Cursor hooks.json to audit edits, block risky commands, control tool calls, and keep agent workflows reviewable in production repositories.
> By Dave · 2026-09-02
> Source: https://otf-kit.dev/blog/cursor-agent-hooks

Cursor hooks let you observe, control, and extend an agent session with scripts that communicate over JSON. For a production repository, the useful pattern is not “run a script after every prompt.” It is to put a small, explicit policy layer around risky actions: audit file edits, block unsafe shell commands, scan outputs for secrets, and record enough evidence to review what happened.

Hooks run as spawned processes and can be configured at the project or user level. Cursor’s [official hooks documentation](https://cursor.com/docs/hooks) defines the lifecycle events, JSON-over-stdio contract, exit-code behavior, cloud-agent limits, and configuration examples. This guide turns those primitives into a workflow you can introduce without making the agent impossible to use.

## What Cursor hooks can control

Cursor supports hooks around agent sessions, tool calls, shell execution, file access, MCP execution, subagents, prompt submission, compaction, and completion. It also exposes separate hooks for inline Tab operations and workspace lifecycle events.

The most useful production events are:

- `beforeShellExecution` to inspect or block commands before they run
- `afterShellExecution` to record the result of a command
- `beforeReadFile` to restrict sensitive paths
- `afterFileEdit` to format, scan, or audit a changed file
- `preToolUse`, `postToolUse`, and `postToolUseFailure` to trace tool activity
- `subagentStart` and `subagentStop` to track delegated work
- `stop` to run a final check when the agent reports completion

Hooks receive JSON through standard input and return JSON through standard output. That gives you a clean boundary: the agent loop emits an event, your script makes a narrow decision, and the loop continues or stops based on the result.

Do not treat a hook as a replacement for branch protection, deployment approval, or server-side authorization. A local script can be bypassed by another workflow. Use hooks to improve agent behavior and evidence; keep irreversible authority in the repository and deployment systems.

The takeaway: choose hooks by the action they protect, not by the number of lifecycle events you can register.

## Start with an audit hook

The safest first hook records what the agent did without blocking anything. An audit hook gives you a baseline for which tools, files, and commands appear during normal work. Cursor’s project hooks run from the project root, so a project configuration can call `.cursor/hooks/audit.sh`.

```json
{
  "version": 1,
  "hooks": {
    "afterFileEdit": [
      {
        "command": ".cursor/hooks/audit.sh"
      }
    ],
    "afterShellExecution": [
      {
        "command": ".cursor/hooks/audit.sh"
      }
    ]
  }
}
```

A minimal command hook reads the JSON event and appends it to a local file:

```bash
#!/usr/bin/env bash
set -euo pipefail

payload="$(cat)"
printf '%s\t%s\n' "$(date -u +%FT%TZ)" "$payload" \
  >> .cursor/agent-events.jsonl

printf '{}\n'
```

This is deliberately small. Do not log raw prompts, file contents, tokens, or secrets by default. Record event type, command metadata, paths, exit status, and a request identifier when available. If the event payload contains sensitive data, redact before writing. Keep the log out of version control unless your policy explicitly requires checked-in evidence.

The audit phase should answer basic questions: which command ran, which file changed, whether the command succeeded, and whether the agent attempted a protected operation. Once you know the normal event shape, add one policy at a time.

The takeaway: observe first so a blocking policy is based on actual repository behavior.

## Block risky shell commands before execution

Cursor documents command-based hook exit codes: exit code `0` means the hook succeeded, exit code `2` blocks the action, and other exit codes allow the action to proceed by default. Use `beforeShellExecution` for commands that need a deterministic rule.

A basic policy can deny destructive commands while leaving ordinary reads and tests alone:

```json
{
  "version": 1,
  "hooks": {
    "beforeShellExecution": [
      {
        "command": ".cursor/hooks/guard-shell.sh",
        "timeout": 30,
        "matcher": "rm|git\\s+reset|git\\s+clean|drop table"
      }
    ]
  }
}
```

The matcher narrows when the hook runs; the script must still inspect the received command. Never make a security decision from a loose substring alone. A command containing `drop table` in a test fixture is different from a database client executing it, and shell quoting can change what actually runs.

```bash
#!/usr/bin/env bash
set -euo pipefail

payload="$(cat)"

if printf '%s' "$payload" | grep -Eiq '(^|[[:space:];])rm[[:space:]]+-rf|git[[:space:]]+reset[[:space:]]+--hard|git[[:space:]]+clean[[:space:]]+-fd'; then
  printf '{"permission":"deny","reason":"destructive command requires human review"}\n'
  exit 2
fi

printf '{}\n'
```

The example is a starting point, not a complete shell parser. Test it against the command formats your repository actually uses. Prefer allowlists for high-risk operations: permit the exact migration or test command you expect instead of trying to enumerate every unsafe variation.

The takeaway: block a small set of high-impact actions and make the denial reason visible.

## Add file and secret checks after edits

`afterFileEdit` is a good place for fast checks that do not need to decide whether an edit was allowed. Run a formatter, detect obvious credential patterns, or record the changed path for a later review. Keep the hook fast; a script that scans the entire repository after every small edit will turn agent work into a queue.

A useful split is:

```text
afterFileEdit: fast format and local secret-pattern scan
stop: focused tests and final changed-file summary
CI: complete secret scanner, dependency checks, and release gates
```

The local scan should fail loudly when it sees a likely secret, but do not claim that a regex proves the repository is clean. False positives need a documented escape path, and the full check belongs in CI where the environment and results are controlled.

For example, a hook can inspect only the edited file path from the event payload, reject known credential prefixes, and return a reason. If the hook cannot parse the event, it should report that it could not evaluate the file rather than silently claiming success.

Do not put credentials in `hooks.json`, scripts, command arguments, or logs. If a scan needs access to a secret-management service, use the host’s secure credential mechanism and return only a pass/fail result plus a request ID.

The takeaway: local hooks catch cheap mistakes; CI remains the authoritative repository gate.

## Use stop hooks for bounded completion checks

A `stop` hook runs when the agent reaches a completion point. It can check whether a task-specific marker exists, whether the focused test command passed, or whether the agent left a required summary. Cursor’s best-practices guide shows a bounded loop that uses a stop hook and a maximum iteration count.

The bound matters. An automated continuation should have:

- a maximum number of iterations
- a visible state file or event record
- a concrete completion condition
- a preserved failure message when the condition is not met

A conceptual state file might contain:

```json
{
  "task": "add-invoice-export",
  "status": "checks_pending",
  "attempt": 2,
  "maxAttempts": 5,
  "requiredChecks": ["typecheck", "invoice-export-tests"]
}
```

Do not let a stop hook turn a failing test into an unbounded sequence of edits. After the maximum attempts, return control to a human with the command, failure output, and current diff. A failed check may indicate a code defect, an environment issue, or a requirement that needs clarification.

The takeaway: a completion hook should prove a bounded condition, not encourage endless activity.

## Account for cloud-agent differences

Cursor’s documentation distinguishes local project and user hooks from cloud-agent support. Cloud agents load project hooks from `.cursor/hooks.json`, and Enterprise environments can add managed hooks. User-level hooks in `~/.cursor/hooks.json` are not available in cloud agents because those workers do not have access to your local home-directory configuration.

Cloud agents support command-based hooks, including shell, file, tool, subagent, prompt-submission, compaction, response, thought, and stop events. Some local lifecycle events are unavailable or deferred: `sessionStart`, `sessionEnd`, MCP lifecycle hooks, Tab hooks, and `workspaceOpen` do not behave as local editor events in the cloud environment.

This has a practical consequence: project hooks must contain the policy required for a remote run. Do not rely on a private home-directory script to protect a cloud task. Test the same repository in the environment where the agent will run, and record which hooks were loaded.

Cursor’s [Agent Skills documentation](https://cursor.com/docs/skills) also notes that user-level skills are not copied into Cloud Agents, remote SSH sessions, or self-hosted workers. Put essential workflow instructions and checks in the project when portability matters.

The takeaway: repository-scoped policy travels farther than workstation configuration.

## Combine hooks with review and observability

Hooks are most useful as one layer in a larger loop. A pre-execution hook can block a risky command; an after-event hook can record what happened; a final check can run focused tests; a reviewer can inspect the diff and the event record. No one layer should pretend to cover all failure modes.

For an agent-generated production change, review at least:

1. files read and edited
2. commands executed and exit statuses
3. blocked actions and their reasons
4. tests run and omitted tests
5. new dependencies and configuration changes
6. final diff against the task specification

[AI app security checklist](/blog/ai-app-security-checklist) covers the boundary around inputs, model calls, tools, database writes, and user-visible output. [LLM observability](/blog/llm-observability-guide) covers connecting requests to model calls, tool calls, latency, cost, and outcomes. Those concerns are larger than Cursor, but hooks can supply useful local events to the same review habit.

OTF’s paid full-stack kits include owned application code with `CLAUDE.md`, `.cursorrules`, and 20+ tested AI prompts. That gives a coding agent a repository contract to extend, while hooks add action-level checks around the session. The two layers complement each other: instructions explain the expected workflow, and hooks provide machine-readable evidence when the workflow runs.

Cursor hooks work best when they stay narrow: audit first, block high-impact commands, scan changed files, cap automated loops, and test the actual cloud or local environment. Keep secrets out of scripts and logs, return explicit reasons for denials, and leave final authority with version control, CI, and deployment controls. That turns `hooks.json` from a novelty into a small, reviewable part of a production agent workflow.

## Sources

- [Hooks — Cursor Documentation](https://cursor.com/docs/hooks)
- [Agent Skills — Cursor Documentation](https://cursor.com/docs/skills)
- [Best practices for coding with agents — Cursor](https://cursor.com/blog/agent-best-practices)

For an owned application foundation that agents can extend, [browse OTF templates](https://otf-kit.dev/templates).