NAME
Langertha::Raider::Hooks::Result - Internal value of one hook event: the merged answer of its hooks and a report per run
VERSION
version 0.504
SYNOPSIS
# Internal to Langertha-Raider -- no API promise.
my $r = await $hooks->run_f( PreToolUse => { tool_name => 'bash', tool_input => { command => 'ls' } } );
return deny( $r->reason ) if ( $r->decision // '' ) eq 'deny';
$args = $r->updated_input if $r->updated_input;
journal( 'hook.ran', %$_ ) for @{ $r->runs };
DESCRIPTION
Internal module. Its interface may change without notice.
What "run_f" in Langertha::Raider::Hooks answers for one event: every hook that ran is one entry of "runs", and their outputs are merged into one answer. Which fields an event uses is the caller's business; the runner only fills them the way Claude Code reads them:
PreToolUse-- "decision" (deny>ask>allow), "reasons" of the winning decision, "updated_input" (raider form), "additional_context"UserPromptSubmit-- "blocked" (the prompt does not go to the model; "reasons" are for the user), "additional_context" (plain stdout counts)PostToolUse,PostToolUseFailure-- "blocked" with "reasons": feedback next to the tool result (the tool already ran), "additional_context"Stop,SubagentStop-- "blocked": do not stop, "reasons" are the next instruction; "additional_context": go on with this feedbackSessionStart,SubagentStart-- "additional_context" only (plain stdout counts forSessionStart)every event -- "system_messages" for the user, "stopped" and "stop_reason" (
continue: falseends the run, whatever else was said), "errors" (non-blocking: the action goes ahead)
event
The event name, e.g. PreToolUse.
runs
One hash ref per hook the event selected, in configuration order, including the ones skipped (duplicate, native briefing, unsupported type) and async hooks still running:
{
event => 'PreToolUse',
source => '.claude/settings.json' | 'plugin:loadguard',
plugin => 'loadguard' | undef,
matcher => 'Bash' | undef,
command => '${CLAUDE_PLUGIN_ROOT}/hooks/loadguard', # as configured
args => [ '${CLAUDE_PLUGIN_DATA}' ] | undef, # as configured
argv => [ '/abs/hooks/loadguard', '/abs/data' ], # what was started
form => 'exec' | 'shell',
async => 0 | 1,
timeout => 5, # seconds; undef for async
outcome => 'success' | 'block' | 'error' | 'timeout' | 'skipped' | 'running' | 'cancelled',
message => undef | 'why it is an error or was skipped',
exit => 0, signal => 0, # undef when not started
duration_ms => 12,
stdout => '...', stderr => '...',
output => 'empty' | 'text' | 'json' | 'invalid',
json => { ... } | undef, # the decoded stdout
}
errors
Non-blocking errors, one line each in Claude Code's words (PreToolUse:Bash hook error: [cmd]: Failed with non-blocking status code: ...), plus configuration problems (an invalid matcher). The action goes ahead.
decision
PreToolUse only: deny, ask, allow or undef (no hook decided). Exit code 2 is deny.
reasons
The reasons that go with "decision" (permissionDecisionReason, the stderr of an exit 2) or with "blocked" (reason, the stderr of an exit 2), in configuration order.
deny_messages
PreToolUse only, with a "decision" of deny: what the model is told, one entry per denying hook, in Claude Code's words (ADR 0022). A hook that blocked by exit code 2 is PreToolUse:Tool hook error: STDERR, a hook that gave a reason in JSON hands the reason through as it stands, a denial without a reason is PreToolUse:Tool hook blocked the tool call.
blocked
True when a hook blocked an event that can be blocked (decision: "block" or exit code 2): see "DESCRIPTION" for what it means per event. PreToolUse uses "decision" instead.
additional_context
Text for the model: every hookSpecificOutput.additionalContext, and the plain stdout of UserPromptSubmit and SessionStart hooks, in configuration order.
system_messages
Every systemMessage, for the user.
stopped
True when a hook answered continue: false; then nothing else counts.
stop_reason
The first stopReason of a stopping hook, else undef.
updated_input
PreToolUse only: the tool arguments a hook replaced, in raider form (translated back by "raider_input" in Langertha::Raider::ToolNames), or undef. Never set when "decision" is deny.
reason
my $why = $r->reason; # L</reasons> joined with newlines, or undef
deny_message
my $text = $r->deny_message; # L</deny_messages> joined with newlines, or undef
context
my $text = $r->context; # L</additional_context> joined with blank lines, or undef
ran
The number of hooks that were started (not skipped).
SEE ALSO
SUPPORT
Issues
Please report bugs and feature requests at https://github.com/Getty/langertha-raider/issues.
IRC
Join #langertha on irc.perl.org or message Getty directly.
CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
AUTHOR
Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/
COPYRIGHT AND LICENSE
This software is copyright (c) 2026 by Torsten Raudssus.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.