Project 17: “The Type-Safe Hook with Bun” — Kiro Hooks
| Attribute | Value |
|---|---|
| File | KIRO_CLI_LEARNING_PROJECTS.md |
| Main Programming Language | TypeScript (Bun) |
| Coolness Level | Level 4: Hardcore Tech Flex |
| Difficulty | Level 3: Advanced |
| Knowledge Area | Kiro Hooks |
What you’ll build: A PostToolUse hook in Bun that parses JSON events with Zod and logs metrics.
Why it teaches Safe Automation: You replace fragile shell scripts with typed automation.
Success criteria:
- Hook logs structured output on every tool use.
Real World Outcome
You’ll have a production-ready Kiro hook written in TypeScript that runs with Bun and validates every tool execution event using Zod schemas. When Kiro calls any tool (Bash, Edit, Read, etc.), your hook will:
For a successful Bash tool call:
$ kiro "run the test suite"
# Your hook logs to ~/.kiro/hooks/metrics.jsonl:
{"timestamp":"2024-12-20T14:32:01.234Z","toolName":"Bash","duration":1234,"success":true,"command":"npm test","exitCode":0}
For a failed Edit operation:
{"timestamp":"2024-12-20T14:35:22.456Z","toolName":"Edit","duration":89,"success":false,"error":"old_string not found in file","filePath":"/path/to/file.ts"}
Live metrics dashboard:
$ bun run analyze-metrics.ts
Tool Usage Report (Last 24 Hours)
─────────────────────────────────
Bash: 47 calls (43 success, 4 failed) - avg 890ms
Edit: 23 calls (22 success, 1 failed) - avg 45ms
Read: 89 calls (89 success, 0 failed) - avg 12ms
Grep: 34 calls (34 success, 0 failed) - avg 156ms
Slowest Commands:
1. npm run build (3421ms)
2. git push origin main (2876ms)
3. docker-compose up (2134ms)
Error Rate: 2.6%
The hook validates every event against strict TypeScript types, ensuring you never miss a field or misparse data.
The Core Question You’re Answering
“How do I build production-grade automation on top of Kiro without brittle shell scripts that break on edge cases?”
Before you start coding, consider: Shell scripts are fragile—they silently fail on malformed JSON, ignore type errors, and give no compile-time guarantees. A typed hook in Bun/TypeScript gives you runtime validation (Zod), compile-time safety (TypeScript), and fast execution (Bun’s native speed). This project teaches you to replace “parse with grep/awk” with “validate with schemas.”
Concepts You Must Understand First
Stop and research these before coding:
- Kiro Hook Lifecycle
- What is the difference between PreToolUse and PostToolUse hooks?
- How does Kiro pass event data to hooks (stdin JSON)?
- What happens if a hook exits with non-zero status?
- Book Reference: Kiro CLI documentation - Hook System Architecture
- Zod Schema Validation
- How do you define a Zod schema for nested objects?
- What is
z.infer<typeof schema>and why is it critical? - How does Zod handle optional fields and defaults?
- How do you compose schemas (unions, intersections)?
- Book Reference: “Effective TypeScript” by Dan Vanderkam - Ch. 3 (Type Inference)
- Bun Runtime Specifics
- How does Bun’s
Bun.file()differ from Node’sfsmodule? - What is
Bun.write()and why is it faster than fs.appendFile? - How do you handle stdin in Bun (
await Bun.stdin.text())? - How does Bun’s bundler work (
bun build --compile)? - Book Reference: Bun documentation - Runtime APIs
- How does Bun’s
- JSON Lines (JSONL) Format
- Why use JSONL instead of JSON arrays for logs?
- How do you append to JSONL files atomically?
- How do you parse JSONL with streaming (jq, ndjson)?
- Book Reference: “Designing Data-Intensive Applications” by Martin Kleppmann - Ch. 4 (Encoding)
Questions to Guide Your Design
Before implementing, think through these:
- Schema Design
- What fields are common to all tool events (toolName, timestamp, duration)?
- How do you model tool-specific data (Bash exitCode, Edit filePath)?
- Should you use a discriminated union (
type: "bash" | "edit") or a flat schema? - How do you handle unknown tools gracefully?
- Error Handling
- What if Zod validation fails—do you crash the hook or log the error?
- Should a hook failure block Kiro’s execution or just warn?
- How do you ensure the log file is always writable?
- What happens if the disk is full?
- Performance
- Should you write to disk synchronously or asynchronously?
- Do you batch writes or append immediately?
- How do you avoid blocking Kiro on slow I/O?
- Should you rotate log files daily/hourly?
- Observability
- How do you debug a hook that’s failing silently?
- Should you log to stderr or a separate debug file?
- How do you measure the hook’s own performance overhead?
Thinking Exercise
Manual Hook Execution Trace
Before writing code, manually trace what happens when Kiro calls your hook:
Scenario: User runs kiro "run npm test" and Kiro invokes the Bash tool.
Step 1: Kiro prepares the event
{
"hookType": "PostToolUse",
"timestamp": "2024-12-20T14:32:01.234Z",
"tool": {
"name": "Bash",
"input": {"command": "npm test"},
"output": {"exitCode": 0, "stdout": "All tests passed", "stderr": ""},
"duration": 1234
}
}
Step 2: Kiro spawns your hook
bun run ~/.kiro/hooks/metrics-logger.ts < event.json
Step 3: Your hook reads stdin
const eventJson = await Bun.stdin.text();
// What if stdin is empty? What if it's malformed JSON?
Step 4: Zod validation
const event = PostToolUseEventSchema.parse(JSON.parse(eventJson));
// What if parsing throws? Should you catch and log, or let it crash?
Step 5: Write to JSONL
await Bun.write("metrics.jsonl", JSON.stringify(event) + "\n", {append: true});
// Is this atomic? What if another process is writing simultaneously?
Questions while tracing:
- At which step could things fail? How would you detect each failure?
- How do you test this hook without running Kiro every time?
- How would you simulate different tool events (success, failure, timeout)?
The Interview Questions They’ll Ask
Prepare to answer these:
- “How does Zod’s
z.inferwork, and why is it more reliable than manually typing JSON.parse results?” - “What are the performance characteristics of Bun compared to Node.js for I/O-heavy tasks like log writes?”
- “Explain the difference between JSON and JSON Lines. When would you use each format?”
- “How would you design a schema for a discriminated union in TypeScript to handle multiple tool types?”
- “What strategies would you use to ensure atomic writes to a log file from concurrent processes?”
- “How do you test code that reads from stdin without manually piping data every time?”
Hints in Layers
Hint 1: Start with the Schema
Define your Zod schemas first, then let TypeScript types flow from them. Start with a base ToolEvent schema, then extend it for specific tools.
Hint 2: Read Stdin Safely
Bun provides await Bun.stdin.text() to read all stdin as a string. Wrap this in a try/catch for JSON.parse and Zod validation. If validation fails, log to stderr and exit 0 (don’t block Kiro).
Hint 3: Atomic Appends
Use Bun.write(path, data, {append: true}) for atomic appends. Bun handles file locking internally. For rotation, check file size before writing and rename if needed.
Hint 4: Testing Without Kiro Create a test harness:
echo '{"hookType":"PostToolUse",...}' | bun run metrics-logger.ts
Write JSON fixtures for each tool type and pipe them to your hook.
Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| TypeScript Type System | “Effective TypeScript” by Dan Vanderkam | Ch. 3 (Type Inference), Ch. 4 (Type Design) |
| Schema Validation | “Programming TypeScript” by Boris Cherny | Ch. 6 (Advanced Types) |
| JSON Lines Format | “Designing Data-Intensive Applications” by Martin Kleppmann | Ch. 4 (Encoding and Evolution) |
| Bun Runtime | Bun official docs | Runtime APIs, File I/O |
| Hook Patterns | Kiro CLI docs | Hooks System, Event Schemas |
Common Pitfalls & Debugging
Problem 1: “Hook doesn’t run, no error messages”
- Why: Kiro might not have execute permissions on the hook file, or the shebang is wrong
- Fix:
chmod +x ~/.kiro/hooks/metrics-logger.ts # Add shebang: #!/usr/bin/env bun - Quick test:
~/.kiro/hooks/metrics-logger.tsshould run directly
Problem 2: “Zod validation fails with ‘Expected object, received undefined’“
- Why: Stdin is empty, or Kiro isn’t passing the event correctly
- Fix: Check if stdin exists before parsing:
const input = await Bun.stdin.text(); if (!input.trim()) { console.error("No input received"); process.exit(0); } - Quick test:
echo '{}' | bun run hook.tsshould fail gracefully
Problem 3: “Log file grows infinitely, disk fills up”
- Why: No log rotation implemented
- Fix: Implement daily rotation:
const logPath = `~/.kiro/hooks/metrics-${new Date().toISOString().split('T')[0]}.jsonl`; - Quick test: Run hook 1000 times, verify old logs are compressed/deleted
Problem 4: “Hook is slow, Kiro feels laggy”
- Why: Synchronous disk writes block Kiro’s execution
- Fix: Use async writes and don’t await them (fire-and-forget):
Bun.write(logPath, data, {append: true}); // Don't await - Quick test: Time hook execution with
time echo '...' | bun run hook.ts
Definition of Done
- Hook receives PostToolUse events from Kiro and parses them with Zod
- All tool types (Bash, Edit, Read, Grep, etc.) are logged correctly
- Validation errors are logged to stderr but don’t crash the hook
- JSONL log file is written atomically with append mode
- Log rotation is implemented (daily files, compression after 7 days)
- Hook adds <5ms overhead to Kiro’s tool execution
- Test suite covers success, failure, and malformed input cases
- Analysis script reads JSONL and produces human-readable reports
- Hook is executable (
chmod +x) with correct shebang - Documentation explains how to customize schemas for new tools