Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions src/lib/events/schemas/hook.delivery.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* Tests for hook.delivery schema — closes #1492.
*
* The strict schema previously did not list `event` as a valid key. After
* #1485 (hookify-perf-foundation) shipped `runHandler()` stamping
* `event: payload.hook_event_name` on every span, the strict-mode parser
* rejected every emission with `unrecognized key 'event'`, leaving the
* `hook_perf_baseline` view empty regardless of dispatch activity.
*/

import { describe, expect, test } from 'bun:test';
import { schema } from './hook.delivery.js';

describe('hook.delivery schema (closes #1492)', () => {
test('accepts payload WITH the new event key (post-#1485 emitter shape)', () => {
const result = schema.safeParse({
hook_name: 'session-sync-tool',
agent_id: 'genie/dog-fooder-11eb',
tool: 'Bash',
event: 'PreToolUse',
status: 'ok',
duration_ms: 12,
});
expect(result.success).toBe(true);
});

test('still accepts payload WITHOUT the event key (backward compat)', () => {
const result = schema.safeParse({
hook_name: 'session-sync-tool',
agent_id: 'genie/dog-fooder-11eb',
tool: 'Bash',
status: 'ok',
duration_ms: 12,
});
expect(result.success).toBe(true);
});

test('accepts tool-less hook events (UserPromptSubmit, Stop) with event but no tool', () => {
const result = schema.safeParse({
hook_name: 'session-sync-prompt',
agent_id: 'genie/dog-fooder-11eb',
event: 'UserPromptSubmit',
status: 'ok',
duration_ms: 8,
});
expect(result.success).toBe(true);
});

test('rejects unknown keys (strict mode preserved)', () => {
const result = schema.safeParse({
hook_name: 'session-sync-tool',
agent_id: 'genie/dog-fooder-11eb',
tool: 'Bash',
event: 'PreToolUse',
status: 'ok',
duration_ms: 12,
// Random extra key — should still be rejected
mystery_field: 'value',
});
expect(result.success).toBe(false);
});

test('rejects oversized event names (>64 chars)', () => {
const result = schema.safeParse({
hook_name: 'session-sync-tool',
agent_id: 'genie/dog-fooder-11eb',
tool: 'Bash',
event: 'A'.repeat(65),
status: 'ok',
});
expect(result.success).toBe(false);
});

test('rejects empty event names', () => {
const result = schema.safeParse({
hook_name: 'session-sync-tool',
agent_id: 'genie/dog-fooder-11eb',
tool: 'Bash',
event: '',
status: 'ok',
});
expect(result.success).toBe(false);
});
});
11 changes: 11 additions & 0 deletions src/lib/events/schemas/hook.delivery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ export const TYPE = 'hook.delivery' as const;
export const KIND = 'span' as const;

const HookNameSchema = tagTier(z.string().min(1).max(128), 'C', 'hook name — public');
/**
* Hook event name (PreToolUse, PostToolUse, UserPromptSubmit, Stop, etc.).
* Stamped on every hook.delivery span by `runHandler` so the
* `hook_perf_baseline` view (introduced by #1485 hookify-perf-foundation)
* can group by event for tool-less events.
*
* Closes #1492 — strict schema previously rejected this key, leaving the
* baseline view empty regardless of dispatch activity.
*/
const HookEventSchema = tagTier(z.string().min(1).max(64), 'C', 'CC hook event name — public');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The description for HookEventSchema contains a 'CC' prefix which appears to be a typo or inconsistent with other schemas in this file (e.g., HookNameSchema uses 'hook name — public'). For consistency and clarity in telemetry metadata, it should likely be 'hook event name — public'.

Suggested change
const HookEventSchema = tagTier(z.string().min(1).max(64), 'C', 'CC hook event name — public');
const HookEventSchema = tagTier(z.string().min(1).max(64), 'C', 'hook event name — public');

const AgentIdSchema = tagTier(
z
.string()
Expand Down Expand Up @@ -41,6 +51,7 @@ export const schema = z
hook_name: HookNameSchema,
agent_id: AgentIdSchema,
tool: ToolSchema,
event: HookEventSchema.optional(),
status: StatusSchema,
duration_ms: DurationSchema,
exit_code: ExitCodeSchema,
Expand Down
Loading