Skip to main content
Piebald supports Claude Code-compatible command hooks. Hooks are shell commands that run at specific points during a chat, receive JSON on stdin, and can return JSON on stdout to do the following:
  • Add context to the chat
  • Prevent prompts from being sent
  • Automatically approve or deny tools
  • Modify tool call input before tools are executed
  • Force the model to continue iterating when it stops
Piebald’s Claude Code hooks compatibility is limited to command hooks—HTTP-based, prompt-based, and agent-based hooks aren’t supported yet. if conditions, CLAUDE_ENV_FILE, transcript files, and async hooks are not supported, and statusMessage and suppressOutput are ignored. We also don’t support the following Claude Code hook types, which don’t have dirrect equivalents in Piebald: SessionEnd, Notification, ConfigChange, CwdChanged, FileChanged, InstructionsLoaded, WorktreeCreate, WorktreeRemove, Elicitation, ElicitationResult, TaskCreated, TaskCompleted, TeammateIdle, and PermissionRequest. If you need any of those features, let us know.

Quick Start

Piebald uses Claude Code’s hook format. We don’t have a custom hooks format, so all of your Claude Code hooks should work without modification in Piebald (except such as make use of functionality we don’t yet support). To get started with Claude Code hooks, create a .claude/settings.json file in your project:
Then create the hook script:
Make it executable:
To reload the hooks cache after changing your project .claude/settings.json, you’ll need to create a new chat in that project. After changing global hooks in ~/.claude/settings.json, restart Piebald to reload the global hooks cache.

Configuration Files

Hooks are loaded from Claude Code-compatible settings files: Later configs append hook handlers. For example, if both global and project files define PreToolUse, both sets of handlers can run. A hook group has a matcher and a list of hook handlers:
Matchers are case-sensitive regexes. Invalid regexes won’t match.

Supported Events

SessionEnd isn’t supported because Piebald sessions don’t necessarily ever end like they do in Claude Code.

Hook Execution

Hook commands run through the platform shell: Hook input JSON is sent to the command’s stdin and reads stdout and stderr. Hook commands receive these environment variables: Matched hook handlers run in parallel. If multiple hooks return decisions, Piebald resolves the decision according to the event rules below.

Common Input

Every hook receives a JSON object with common fields:

Output Format

Hooks can print JSON to stdout:
Piebald also accepts plain text on stdout for successful hooks. If a hook exits with code 0 and stdout isn’t valid JSON, the text is treated as additionalContext.

Exit Codes

UserPromptSubmit

UserPromptSubmit runs after you press Enter but before the message is actually sent. Matcher value: submit Input includes:
To block the prompt, return:
You can also exit with code 2; stderr will be used as the block reason. To add context to the message, return additionalContext:

PreToolUse

PreToolUse runs after a tool call is generated but before it’s executed. It can override approval decision that would usually be applied by the selected permission mode. Matcher value: the Claude Code tool name, such as Bash, Read, Edit, or mcp__github__create_issue. Input includes:
Return a permission decision:
Supported decisions: If multiple hooks return conflicting decisions, the most restrictive decision wins, so deny > ask > allow. A PreToolUse hook can also modify tool input with updatedInput:
Matched hooks run in parallel. If multiple hooks return updatedInput, the last returned update wins. Avoid configuring multiple hooks that modify the same tool input.
PreToolUse runs during the approval evaluation. If a hook returns ask, and the user manually approves the tool later, PreToolUse isn’t run again for that approval.

PostToolUse

PostToolUse runs after a tool completes successfully. Matcher value: the Claude Code tool name. Input includes:
PostToolUse can’t prevent a tool that already ran. If it returns decision: "block", the reason is queued as context for the model’s next turn:
You can also return additionalContext to add information for the next model turn.

PostToolUseFailure

PostToolUseFailure runs when a tool fails. Matcher value: the Claude Code tool name. Input includes:
Outputs are observational. additionalContext is queued for the model.

Stop and SubagentStop

Stop runs when a normal chat generation would finish. SubagentStop uses the same behavior for subagent chats. Matcher value: Input includes:
For SubagentStop, input also includes:
To make the model continue, return decision: "block" with a reason:
The reason is injected as a new user message and continues generation. To force an immediate stop, return continue: false:
continue: false takes precedence over decision: "block".
continue: true doesn’t mean “keep generating”. It’s treated as no opinion. Use decision: "block" or exit code 2 to request continuation.
Stop hook continuations are capped at 3 per generation loop to prevent infinite loops.

SessionStart

SessionStart runs when a chat starts. Matcher value: Input includes:
Return additionalContext to queue context for the chat.

Compaction Hooks

PreCompact runs before compaction, and PostCompact runs after compaction. Matcher value: manual or auto PreCompact input includes:
PostCompact input includes:
These hooks are observational. Their outputs don’t change compaction behavior.

StopFailure

StopFailure runs when generation fails. Matcher value: the classified error type, such as rate_limit, authentication_failed, timeout, network_error, cancelled, or unknown. Input includes:
Outputs are ignored.

SubagentStart

SubagentStart runs after a subagent chat is created and before its first message. Matcher value: default Input includes:
Return additionalContext to inject context into the subagent’s first message.

Tool Name Mapping

Tool names are automatically mapped to Claude Code tool names: Task is accepted as an alias for Agent in matchers.

Tool Input Reshaping

Before tool input is sent to hooks, it’s somewhat reshaped to match Claude Code’s field names: When PreToolUse returns updatedInput, Piebald converts the Claude Code-shaped input back into Piebald’s internal shape before executing the tool.

Example: Block Production Commands

Example: Ask The Model To Continue Until Tests Pass