Studio¶
Visual debugger for hooks. See exactly what your hooks receive and how they respond.
What is Studio?¶
Studio shows Claude's conversation with hook events inline:
π€ User: "delete the temp files"
π§ Thinking: "I'll use rm to clean up..."
π§ Bash(rm -rf /tmp/old-*)
ββ PreToolUse hooks ββββββββββββββββββββββββββββββ
β check_dangerous β β
allow 0.8ms β
β log_commands β β
allow 0.2ms β
β Total: 15.2ms β
ββββββββββββββββββββββββββββββββββββββββββββββββββ
π₯ Removed 3 files
π¬ Done! Cleaned up the temp files.
You see: - What Claude was thinking - What tool it called - Which handlers ran and what they decided - How long each handler took - The tool output
Installation¶
Quick Start¶
1. Add SQLiteObserver to your hooks¶
from fasthooks import HookApp
from fasthooks.observability import SQLiteObserver
app = HookApp()
app.add_observer(SQLiteObserver()) # Writes to ~/.fasthooks/studio.db
@app.pre_tool("Bash")
def check_bash(event):
if "rm -rf /" in event.command:
return deny("Blocked dangerous command")
app.run()
2. Run your hooks in Claude Code¶
3. Launch Studio¶
Opens http://localhost:5555 in your browser.
Studio UI¶
Session List (Sidebar)¶
Shows all Claude sessions that have hook events:
- Session ID (truncated)
- Number of hooks fired
- Number of events captured
- Last activity time
Click a session to view its conversation.
Conversation View (Main)¶
Shows the full conversation with hooks inline:
| Element | What it shows |
|---|---|
| π€ User | User messages |
| π§ Thinking | Claude's reasoning (collapsible) |
| π§ Tool | Tool calls with inline hook events |
| π₯ Output | Tool results |
| π¬ Assistant | Claude's responses |
Hook Events Panel¶
Under each tool call, you see:
ββ PreToolUse hooks ββββββββββββββββββββββββββββββ
β handler_name β β
allow 0.45ms β
β another_one β β
allow 0.19ms β
β ββββββββββββββββββββββββββββββββββββββββββββ β
β Total: 28.1ms β
ββββββββββββββββββββββββββββββββββββββββββββββββββ
- Handler name: Your function name
- Decision: β allow, β deny, π« block, βοΈ skip
- Duration: How long the handler took
- Reason: Why it denied (if applicable)
Click to expand input preview (the JSON your handler received).
Real-time Updates¶
Studio updates automatically when new events arrive:
- WebSocket connection to server
- "Live" indicator in footer
- No need to refresh
CLI Options¶
| Option | Default | Description |
|---|---|---|
--db |
~/.fasthooks/studio.db |
Path to SQLite database |
--host |
127.0.0.1 |
Host to bind server to |
--port |
5555 |
Port to bind server to |
--open |
false |
Open browser automatically |
--verbose |
false |
Enable debug logging |
Examples:
# Default (localhost:5555)
fasthooks studio
# Auto-open browser
fasthooks studio --open
# Custom port
fasthooks studio --port 8080
# Debug a specific database
fasthooks studio --db /path/to/debug.db --verbose
Debugging Workflows¶
"Why did my handler deny this?"¶
- Find the tool call in the conversation
- Expand the hook events panel
- Look for β deny - shows handler name and reason
- Click "Input" to see what the handler received
"Is my handler even running?"¶
- Check if events appear in studio
- If no events: Is SQLiteObserver added to your app?
- If events but no handler: Check your matcher (
@app.pre_tool("Bash"))
"My handler is slow"¶
- Look at duration_ms for each handler
- Find the slow one
- Optimize or move heavy work to background tasks
"What did Claude see after my deny?"¶
- Find the denied tool call
- Look at subsequent messages
- Claude typically explains why it couldn't proceed
Throwable Database¶
The studio database is disposable:
No migrations, no schema versions. Delete it anytime - SQLiteObserver recreates it on next run.
Architecture¶
βββββββββββββββββββ ββββββββββββββββ βββββββββββββββββββ
β Your Hooks ββββββΆβ SQLite DB βββββββ File Watcher β
β (SQLiteObserver) β (studio.db) β β (polls changes) β
βββββββββββββββββββ ββββββββββββββββ ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ ββββββββββββββββ βββββββββββββββββββ
β React Frontend βββββββ WebSocket βββββββ FastAPI Server β
β (browser) β β (broadcast) β β (REST + WS) β
βββββββββββββββββββ ββββββββββββββββ βββββββββββββββββββ
- Your hooks write events via SQLiteObserver
- Server polls database for changes
- Server broadcasts "updated" via WebSocket
- Frontend refetches data automatically
API Endpoints¶
For advanced users / custom integrations:
| Endpoint | Description |
|---|---|
GET /api/sessions |
List all sessions |
GET /api/sessions/{id}/conversation |
Get conversation with hooks inline |
GET /api/sessions/{id}/hooks |
Get all hook events for session |
GET /api/hooks/{hook_id} |
Get single hook detail |
GET /api/stats |
Global statistics |
WS /ws |
WebSocket for real-time updates |
Troubleshooting¶
"No sessions in studio"¶
- Did you add
SQLiteObserver()to your hooks? - Did you run Claude Code after adding it?
- Check the database exists:
ls ~/.fasthooks/studio.db
"Events not updating"¶
- Check "Live" indicator in footer
- If "Disconnected": refresh the page
- Check server console for errors
"Can't connect to studio"¶
- Is the server running?
fasthooks studio - Check port isn't in use:
lsof -i :5555 - Try different port:
fasthooks studio --port 8080
Next Steps¶
- Observability - Learn about observers
- CLI Reference - All CLI commands
- Testing - Test hooks without studio