Class: Coat::Hook

Inherits:
Object
  • Object
show all
Defined in:
lib/coat/hook.rb

Overview

One Claude Code hook payload, said in coat's own words: who made the scrap, what ran, what came out. This maps fields and decides nothing - the collector still says what a piece is.

Constant Summary collapse

UNKNOWN_AGENT =
"claude"
UNKNOWN_LABEL =
"scrap"
KEY_LENGTH =
16

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(payload) ⇒ Hook

Returns a new instance of Hook.



21
22
23
# File 'lib/coat/hook.rb', line 21

def initialize(payload)
  @payload = payload
end

Class Method Details

.read(io) ⇒ Object



15
16
17
18
19
# File 'lib/coat/hook.rb', line 15

def self.read(io)
  new(JSON.parse(io.read))
rescue JSON::ParserError => e
  raise Error, "hook payload is not valid JSON: #{e.message}"
end

Instance Method Details

#agentObject

Subagents are named by the agent type they were spawned as; anything else was Claude itself.



45
46
47
# File 'lib/coat/hook.rb', line 45

def agent
  input["subagent_type"] || UNKNOWN_AGENT
end

#bodyObject

Nil when there is nothing worth keeping, so an empty result does not become an empty patch.



68
69
70
71
# File 'lib/coat/hook.rb', line 68

def body
  text = flatten(response)
  text unless text.strip.empty?
end

#cmdObject



53
54
55
# File 'lib/coat/hook.rb', line 53

def cmd
  input["command"]
end

#cwdObject



29
30
31
# File 'lib/coat/hook.rb', line 29

def cwd
  @payload["cwd"] || Dir.pwd
end

#eventObject



25
26
27
# File 'lib/coat/hook.rb', line 25

def event
  @payload["hook_event_name"]
end

#exit_codeObject

Read only if the payload carries one. Claude Code does not promise an exit status for every tool, and a guessed exit status is worse than none: it would mark patches torn that never tore.



60
61
62
63
64
# File 'lib/coat/hook.rb', line 60

def exit_code
  return nil unless response.is_a?(Hash)

  response["exit_code"] || response["exitCode"]
end

#keyObject

Pins have to be found again by the hook that fires after the work, and the tool input is the only thing both hooks are guaranteed to see the same.

ponytail: two byte-identical tool calls in flight at once share a key, so the later pin wins and one of them gets the other's claims. Pin by hand with --key if that ever shows up.



39
40
41
# File 'lib/coat/hook.rb', line 39

def key
  Digest::SHA256.hexdigest(JSON.generate(input))[0, KEY_LENGTH]
end

#labelObject



49
50
51
# File 'lib/coat/hook.rb', line 49

def label
  input["description"] || input["command"] || @payload["tool_name"] || UNKNOWN_LABEL
end