Class: SwarmSDK::Hooks::ShellExecutor

Inherits:
Object
  • Object
show all
Defined in:
lib/swarm_sdk/hooks/shell_executor.rb

Overview

Executes shell command hooks with JSON I/O and exit code handling

ShellExecutor runs external shell commands (defined in YAML hooks) and converts their exit codes to Result objects that control execution flow.

Exit Code Behavior (following Claude Code convention)

  • 0: Success - continue execution (Result.continue)
  • 2: Block with error feedback to LLM (Result.halt)
  • Other: Non-blocking error - log warning and continue (Result.continue)

JSON I/O Format

stdin (to hook script):

{
  "event": "pre_tool_use",
  "agent": "backend",
  "tool": "Write",
  "parameters": { "file_path": "api.rb", "content": "..." }
}

stdout (from hook script):

{
  "success": false,
  "error": "Validation failed: syntax error"
}

Examples:

Execute a validation hook

result = SwarmSDK::Hooks::ShellExecutor.execute(
  command: "python scripts/validate.py",
  input_json: { event: "pre_tool_use", tool: "Write", parameters: {...} },
  timeout: 10,
  agent_name: :backend,
  swarm_name: "Dev Team"
)
# => Result (continue or halt based on exit code)

Class Method Summary collapse

Class Method Details

.execute(command:, input_json:, timeout: nil, agent_name: nil, swarm_name: nil, event: nil) ⇒ Result

Execute a shell command hook

Parameters:

  • command (String)

    Shell command to execute

  • input_json (Hash)

    JSON data to provide on stdin

  • timeout (Integer) (defaults to: nil)

    Timeout in seconds (default: 60)

  • agent_name (Symbol, String, nil) (defaults to: nil)

    Agent name for environment variables

  • swarm_name (String, nil) (defaults to: nil)

    Swarm name for environment variables

  • event (Symbol) (defaults to: nil)

    Event type for context-aware behavior

Returns:

  • (Result)

    Result based on exit code (continue or halt)



62
63
64
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/swarm_sdk/hooks/shell_executor.rb', line 62

def execute(command:, input_json:, timeout: nil, agent_name: nil, swarm_name: nil, event: nil)
  timeout ||= SwarmSDK.config.hook_shell_timeout

  # Build environment variables
  env = build_environment(agent_name: agent_name, swarm_name: swarm_name)

  # Execute command with JSON stdin and timeout
  stdout, stderr, status = Timeout.timeout(timeout) do
    Open3.capture3(
      env,
      command,
      stdin_data: JSON.generate(input_json),
    )
  end

  # Handle exit code per Claude Code convention (context-aware)
  result = handle_exit_code(status.exitstatus, stdout, stderr, event)

  # Emit log event for hook execution
  case status.exitstatus
  when 0
    # Success - log stdout/stderr
    emit_hook_log(
      event: event,
      agent_name: agent_name,
      command: command,
      exit_code: status.exitstatus,
      success: true,
      stdout: stdout,
      stderr: stderr,
    )
  when 2
    # Blocking error - always log stderr
    emit_hook_log(
      event: event,
      agent_name: agent_name,
      command: command,
      exit_code: status.exitstatus,
      success: false,
      stderr: stderr,
      blocked: true,
    )
  else
    # Non-blocking error - log stderr
    emit_hook_log(
      event: event,
      agent_name: agent_name,
      command: command,
      exit_code: status.exitstatus,
      success: false,
      stderr: stderr,
      blocked: false,
    )
  end

  result
rescue Timeout::Error
  emit_hook_log(
    event: event,
    agent_name: agent_name,
    command: command,
    exit_code: nil,
    success: false,
    stderr: "Timeout after #{timeout}s",
  )
  # Don't block on timeout - log and continue
  Result.continue
rescue StandardError => e
  emit_hook_log(
    event: event,
    agent_name: agent_name,
    command: command,
    exit_code: nil,
    success: false,
    stderr: e.message,
  )
  # Don't block on errors - log and continue
  Result.continue
end