The guardrail scan and the hook event are reported in two separate requests, so
neither can learn an id the other assigned. Mint one per AIHookPayload - one
payload is one hook event - and send it on both: in the scan metadata, so the
detection can name the event it came from, and as the event id itself, the way
the conversation id is already sent.
A generation id cannot stand in for it: the IDE mints one per prompt, so several
hook events share it, and some IDEs supply none at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Summary
Mint one hook event id per ai-guardrails hook and send it on both requests the hook makes, so a Cycode violation can be resolved back to the exact hook event that produced it.
Changes
Why
A guardrail hook makes two independent requests: the scan (which becomes a violation) and the hook event report. Neither can learn an id the other was assigned, and the event is reported in the handler's finally — after the scan has already gone out. Minting the id here is what lets both name the same event without either waiting on the other.
generation_id cannot stand in for it. The IDE mints one per prompt, so every hook of that prompt shares it, and some IDEs supply none at all — codex reads turn_id, cursor reads generation_id, claude-code digs it out of the transcript and may come back with None.
Both consumers already tolerate its absence, so this can ship independently:
Testing
pytest tests/cyclient/test_ai_security_manager_client.py tests/cli/commands/ai_guardrails/ — 302 passed.
🤖 Generated with Claude Code