Class: ClaudeHooks::Output::PreToolUse

Inherits:
Base
  • Object
show all
Defined in:
lib/claude_hooks/output/pre_tool_use.rb

Instance Attribute Summary

Attributes inherited from Base

#data

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

#continue?, for_hook_type, #hook_specific_output, #initialize, #output_and_exit, #stop_reason, #suppress_output?, #system_message, #terminal_sequence, #to_json

Constructor Details

This class inherits a constructor from ClaudeHooks::Output::Base

Class Method Details

.merge(*outputs) ⇒ Object

MERGE HELPER ===



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/claude_hooks/output/pre_tool_use.rb', line 65

def self.merge(*outputs)
  compacted_outputs = outputs.compact
  return compacted_outputs.first if compacted_outputs.length == 1
  return super(*outputs) if compacted_outputs.empty?

  merged = super(*outputs)
  merged_data = merged.data

  # PreToolUse specific merge: deny > defer > ask > allow (most restrictive wins)
  permission_decision = 'allow'
  permission_reasons = []

  compacted_outputs.each do |output|
    output_data = output.respond_to?(:data) ? output.data : output

    next unless output_data.dig('hookSpecificOutput', 'permissionDecision')

    current_decision = output_data['hookSpecificOutput']['permissionDecision']
    case current_decision
    when 'deny'
      permission_decision = 'deny'
    when 'defer'
      permission_decision = 'defer' unless permission_decision == 'deny'
    when 'ask'
      permission_decision = 'ask' unless %w[deny defer].include?(permission_decision)
    end

    reason = output_data.dig('hookSpecificOutput', 'permissionDecisionReason')
    permission_reasons << reason if reason && !reason.empty?
  end

  merged_data['hookSpecificOutput'] ||= { 'hookEventName' => 'PreToolUse' }
  merged_data['hookSpecificOutput']['permissionDecision'] = permission_decision
  merged_data['hookSpecificOutput']['permissionDecisionReason'] = if permission_reasons.any?
                                                                    permission_reasons.join('; ')
                                                                  else
                                                                    ''
                                                                  end

  # If any output has updatedInput, use the last one (most recent wins)
  updated_inputs = []
  compacted_outputs.each do |output|
    output_data = output.respond_to?(:data) ? output.data : output
    updated = output_data.dig('hookSpecificOutput', 'updatedInput')
    updated_inputs << updated if updated
  end

  merged_data['hookSpecificOutput']['updatedInput'] = updated_inputs.last if updated_inputs.any?

  new(merged_data)
end

Instance Method Details

#allowed?Boolean

SEMANTIC HELPERS ===

Returns:

  • (Boolean)


24
25
26
# File 'lib/claude_hooks/output/pre_tool_use.rb', line 24

def allowed?
  permission_decision == 'allow'
end

#deferred?Boolean

Returns:

  • (Boolean)


41
42
43
# File 'lib/claude_hooks/output/pre_tool_use.rb', line 41

def deferred?
  permission_decision == 'defer'
end

#denied?Boolean Also known as: blocked?

Returns:

  • (Boolean)


28
29
30
# File 'lib/claude_hooks/output/pre_tool_use.rb', line 28

def denied?
  permission_decision == 'deny'
end

#exit_codeObject

EXIT CODE LOGIC ===

PreToolUse hooks use the advanced JSON API with exit code 0. Per Anthropic guidance: when using structured JSON with permissionDecision, always output to stdout with exit 0 (not stderr with exit 2). The permissionDecision field ('allow', 'deny', 'ask') controls behavior. Reference: https://github.com/anthropics/claude-code/issues/10875



52
53
54
# File 'lib/claude_hooks/output/pre_tool_use.rb', line 52

def exit_code
  0
end

#input_updated?Boolean

Returns:

  • (Boolean)


37
38
39
# File 'lib/claude_hooks/output/pre_tool_use.rb', line 37

def input_updated?
  !updated_input.nil?
end

#output_streamObject

OUTPUT STREAM LOGIC ===

PreToolUse hooks always output to stdout when using the JSON API.



59
60
61
# File 'lib/claude_hooks/output/pre_tool_use.rb', line 59

def output_stream
  :stdout
end

#permission_decisionObject

PERMISSION DECISION ACCESSORS ===



10
11
12
# File 'lib/claude_hooks/output/pre_tool_use.rb', line 10

def permission_decision
  hook_specific_output['permissionDecision']
end

#permission_reasonObject



14
15
16
# File 'lib/claude_hooks/output/pre_tool_use.rb', line 14

def permission_reason
  hook_specific_output['permissionDecisionReason'] || ''
end

#should_ask_permission?Boolean

Returns:

  • (Boolean)


33
34
35
# File 'lib/claude_hooks/output/pre_tool_use.rb', line 33

def should_ask_permission?
  permission_decision == 'ask'
end

#updated_inputObject



18
19
20
# File 'lib/claude_hooks/output/pre_tool_use.rb', line 18

def updated_input
  hook_specific_output['updatedInput']
end