TestExecutorHandlerErrorDeniesPreToolUse documents the contract that any handler error (not just a non-zero exit) flows into the existing fail-closed behavior for PreToolUse.
(t *testing.T)
| 128 | // handler error (not just a non-zero exit) flows into the existing |
| 129 | // fail-closed behavior for PreToolUse. |
| 130 | func TestExecutorHandlerErrorDeniesPreToolUse(t *testing.T) { |
| 131 | t.Parallel() |
| 132 | |
| 133 | const customType HookType = "always-fails" |
| 134 | |
| 135 | registry := NewRegistry() |
| 136 | registry.Register(customType, func(_ HandlerEnv, _ Hook) (Handler, error) { |
| 137 | return handlerFunc(func(context.Context, []byte) (HandlerResult, error) { |
| 138 | return HandlerResult{ExitCode: -1}, errors.New("kaboom") |
| 139 | }), nil |
| 140 | }) |
| 141 | |
| 142 | config := &Config{ |
| 143 | PreToolUse: []MatcherConfig{ |
| 144 | { |
| 145 | Matcher: "*", |
| 146 | Hooks: []Hook{ |
| 147 | {Type: customType, Timeout: 5}, |
| 148 | }, |
| 149 | }, |
| 150 | }, |
| 151 | } |
| 152 | |
| 153 | exec := NewExecutorWithRegistry(config, t.TempDir(), nil, registry) |
| 154 | input := &Input{ |
| 155 | SessionID: "test-session", |
| 156 | ToolName: "shell", |
| 157 | ToolUseID: "test-id", |
| 158 | } |
| 159 | |
| 160 | result, err := exec.Dispatch(t.Context(), EventPreToolUse, input) |
| 161 | require.NoError(t, err) |
| 162 | assert.False(t, result.Allowed) |
| 163 | assert.Equal(t, -1, result.ExitCode) |
| 164 | } |
| 165 | |
| 166 | // TestExecutorHandlerErrorPreservesStderr pins the contract that when a |
| 167 | // handler returns an error, the diagnostic stderr it captured before |
nothing calls this directly
no test coverage detected