Back to the blog

Correlating IDE Prompt and Response Hooks with Disk-Backed State

Pair IDE prompts and agent responses across separate hook invocations using generation IDs, disk-backed pending records, and payload normalization, with explicit limits around cleanup, ordering, and fail-open handling.

Introduction

I built a hook script to pair a prompt submitted in Cursor with its agent response before sending both to an ingestion API. Before this change, there was no reliable way to connect the before-prompt event to its matching after-response event. Differences in the JSON field names in Cursor's payloads also threatened to break parsing on either side. The implementation connects those two hook invocations; it doesn't establish that every submission produces both events or that the events can arrive in any order.

After the change, the before-prompt hook normalizes the field names covered by the implemented aliases, checks that the prompt is non-empty, and writes a pending record to local disk keyed by generation_id. The after-response hook uses the same identifier to find the prompt, combine it with the response, and send the merged payload to the API. I chose fail-open error handling so handled telemetry errors don't block the IDE workflow. The recorded integration result supports this matching path, not a guarantee of uninterrupted operation or delivery under every failure.

The Rule That Shipped

The hooks correlate events by identifier and check for non-empty content before transmission. The before-prompt hook normalizes the payload using the implemented aliases, then checks the resulting prompt. If it's non-empty, the hook writes a pending record to disk under its generation_id. The after-response hook looks for a pending record with that same generation_id. If it finds a match and the response is non-empty, it combines the prompt and response into one payload and sends it to the ingestion API. Empty prompts or responses are not sent.

Unexpected errors are caught so processing fails open rather than letting a handled telemetry error block Cursor. That's the shipped error policy, not a complete availability guarantee. The evidence doesn't establish timeout behavior, whether operations can block, or what happens during an actual failure.

The matching path also requires the pending record to be available when the response hook looks for it. Handling for a response that arrives before the pending write finishes isn't established; that ordering could leave the pair uncaptured. No specific retention interval, maximum file count, or cleanup threshold is specified for pending records that never receive a matching response.

Worked Example

This walkthrough explains the supported mechanism; it isn't a captured payload trace. A user submits a prompt in Cursor. The before-prompt hook receives the payload, normalizes its fields using the implemented aliases, checks that the prompt is non-empty, and writes a pending record to disk under the request's generation_id. The matching rule doesn't require a particular identifier value or concrete payload field name for this explanation, and the evidence establishes neither.

The agent later returns a non-empty response with the same generation_id. In this case, the pending record is already available. The after-response hook looks up the identifier, finds the saved prompt, merges the prompt and response into one payload, and sends it to the ingestion API.

I recorded this as a successful integration of prompt and response data with the API. The resulting payload contains both halves of the matched exchange, rather than an isolated response. That result supports the path shown here. It doesn't establish repeated-delivery reliability, behavior when the pending record is missing, or recovery when a response arrives early. This example also makes no claim about whether the pending file is deleted after a successful send.

Challenges and Tradeoffs

I chose disk-backed pending state to connect the separate hook invocations. An in-memory map would avoid pending files, but it would depend on the relevant process staying alive and being accessible to both invocations. Disk-backed state removes that dependence on one process's memory. It doesn't, by itself, prove that writes are crash-safe, resolve event ordering, or ensure that every response finds its prompt.

The cost is having local state to manage. A prompt that never receives a matching response can leave an abandoned pending record. Cancellation or interruption could create an unmatched record, but neither is an observed cause established here. Without an evidenced retention policy, I can't say how long those records remain or what limits their accumulation. Cleanup remains an unresolved operational concern, not a completed repair.

Fail-open handling makes a separate tradeoff. A handled telemetry failure need not stop the developer from continuing, but the exchange may go uncaptured. A fail-closed alternative could prevent continuation when telemetry fails; it still wouldn't guarantee successful logging. Catching exceptions also doesn't establish whether a disk operation can block or a network request can wait indefinitely. Those limits matter when describing the protection the hook provides.

Field normalization reduces dependence on a single payload spelling, but only for aliases the implementation recognizes. An exact-schema parser would make a schema mismatch more explicit while accepting fewer variants. Normalization provides compatibility within its implemented coverage; it doesn't show that arbitrary future field names will be understood.

Verification Process

I separate the recorded integration result from broader verification claims. The evidence describes generation_id correlation and non-empty checks as implemented, and it records a successful integration of prompt and response data with the API. That supports the core path: a pending prompt exists, a response carries the matching identifier, and the merged data reaches the API. It doesn't establish a comprehensive test matrix or a delivery success rate.

I haven't verified the complete set of normalized JSON aliases across Cursor versions; that set isn't enumerated. There's also no recorded observation of fail-open behavior under a malformed payload, disk error, or API outage. Timeout handling, potentially blocking operations, and response-before-write behavior remain unverified. With no specified cleanup policy, I also can't claim that abandoned pending files are bounded.

Further verification should check these concerns separately rather than treating one successful exchange as coverage for all of them. Empty prompts and responses should be checked for suppression. Mismatched or missing identifiers should be checked for accidental pairing. Failure cases should be checked for both capture loss and workflow continuation. These are verification targets, not tests I can report as completed.

Conclusion

The change established a concrete correlation path: normalize the implemented aliases, reject empty content, save the prompt under generation_id, then match and merge the response for API ingestion. I observed a successful integration on that path and chose fail-open handling for caught errors rather than making telemetry success a prerequisite for continuation.

The limits remain explicit. Cleanup and retention are unspecified, alias coverage isn't enumerated, and actual failure behavior hasn't been observed. Ordering recovery and timeout behavior are also unverified. The supported result is a working path for matched exchanges, not a guarantee that telemetry always arrives or can never interrupt the IDE.