diff --git a/CLAUDE.md b/CLAUDE.md index df6a29c..cc2c6a6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -534,7 +534,7 @@ Stop hooks **always exit with code 0**. The `decision` field controls whether Cl - `decision` field omitted: Stop proceeds normally - `"block"`: Stop is blocked (early return) -Errors are logged to stderr as warnings, but cchook continues to output JSON and exits successfully. On errors, `decision` defaults to `"block"` for safety (fail-safe). +Errors are logged to stderr as warnings, but cchook continues to output JSON and exits successfully. On errors, `decision` is omitted (allow stop) per the official Claude Code spec where hook errors are non-blocking. **Backward Compatibility**: Prior to JSON output support, Stop hooks used exit codes: @@ -616,7 +616,7 @@ SubagentStop hooks **always exit with code 0**. The `decision` field controls wh - `decision` field omitted: SubagentStop proceeds normally - `"block"`: SubagentStop is blocked (early return) -Errors are logged to stderr as warnings, but cchook continues to output JSON and exits successfully. On errors, `decision` defaults to `"block"` for safety (fail-safe). +Errors are logged to stderr as warnings, but cchook continues to output JSON and exits successfully. On errors, `decision` is omitted (allow subagent stop) per the official Claude Code spec where hook errors are non-blocking. **Example**: ```yaml diff --git a/actions_test.go b/actions_test.go index 6e6d493..2673187 100644 --- a/actions_test.go +++ b/actions_test.go @@ -262,12 +262,12 @@ func TestExecuteStopAction_CommandWithStubRunner(t *testing.T) { wantDecision: "", }, { - name: "command failure blocks stop with decision: block", + name: "command failure allows stop (decision empty)", command: "exit 1", stderr: "stop command failed", exitCode: 1, - // Non-zero exit → fail-safe block - wantDecision: "block", + // Non-zero exit → allow stop (per official spec: hook errors are non-blocking) + wantDecision: "", }, } diff --git a/executor.go b/executor.go index dc63e82..cfdf877 100644 --- a/executor.go +++ b/executor.go @@ -273,6 +273,7 @@ func (e *ActionExecutor) ExecuteStopAction(action Action, input *StopInput, rawJ stdout, stderr, exitCode, err := e.runner.RunCommandWithOutput(cmd, action.UseStdin, rawJSON) // Command failed with non-zero exit code + // Per official spec: hook errors are non-blocking → allow stop if exitCode != 0 { errMsg := fmt.Sprintf("Command failed with exit code %d: %s", exitCode, stderr) if strings.TrimSpace(stderr) == "" && err != nil { @@ -281,8 +282,7 @@ func (e *ActionExecutor) ExecuteStopAction(action Action, input *StopInput, rawJ fmt.Fprintf(os.Stderr, "Warning: %s\n", errMsg) return &ActionOutput{ Continue: true, - Decision: "block", - Reason: errMsg, + Decision: "", // Allow stop on error SystemMessage: errMsg, }, nil } @@ -302,8 +302,7 @@ func (e *ActionExecutor) ExecuteStopAction(action Action, input *StopInput, rawJ fmt.Fprintf(os.Stderr, "Warning: %s\n", errMsg) return &ActionOutput{ Continue: true, - Decision: "block", - Reason: errMsg, + Decision: "", // Allow stop on error SystemMessage: errMsg, }, nil } @@ -315,8 +314,7 @@ func (e *ActionExecutor) ExecuteStopAction(action Action, input *StopInput, rawJ fmt.Fprintf(os.Stderr, "Warning: %s\n", errMsg) return &ActionOutput{ Continue: true, - Decision: "block", - Reason: errMsg, + Decision: "", // Allow stop on error SystemMessage: errMsg, }, nil } @@ -327,8 +325,7 @@ func (e *ActionExecutor) ExecuteStopAction(action Action, input *StopInput, rawJ fmt.Fprintf(os.Stderr, "Warning: %s\n", errMsg) return &ActionOutput{ Continue: true, - Decision: "block", - Reason: errMsg, + Decision: "", // Allow stop on error SystemMessage: errMsg, }, nil } @@ -349,14 +346,14 @@ func (e *ActionExecutor) ExecuteStopAction(action Action, input *StopInput, rawJ case "output": processedMessage := unifiedTemplateReplace(action.Message, rawJSON) - // Empty message check → fail-safe (decision: block) + // Empty message check → allow stop (error in systemMessage) + // Per official spec: hook errors are non-blocking → allow stop if strings.TrimSpace(processedMessage) == "" { errMsg := "Empty message in Stop action" fmt.Fprintf(os.Stderr, "Warning: %s\n", errMsg) return &ActionOutput{ Continue: true, - Decision: "block", - Reason: errMsg, + Decision: "", // Allow stop on error SystemMessage: errMsg, }, nil } @@ -375,8 +372,7 @@ func (e *ActionExecutor) ExecuteStopAction(action Action, input *StopInput, rawJ fmt.Fprintf(os.Stderr, "Warning: %s\n", errMsg) return &ActionOutput{ Continue: true, - Decision: "block", - Reason: processedMessage, + Decision: "", // Allow stop on error SystemMessage: errMsg, }, nil } @@ -402,13 +398,13 @@ func (e *ActionExecutor) ExecuteStopAction(action Action, input *StopInput, rawJ } // Final validation: decision "block" requires non-empty reason + // Per official spec: hook errors are non-blocking → allow stop on validation error if decision == "block" && strings.TrimSpace(reason) == "" { errMsg := "Empty reason when decision is 'block' (reason is required for block)" fmt.Fprintf(os.Stderr, "Warning: %s\n", errMsg) return &ActionOutput{ Continue: true, - Decision: "block", - Reason: errMsg, + Decision: "", // Allow stop on error SystemMessage: errMsg, }, nil } @@ -425,7 +421,7 @@ func (e *ActionExecutor) ExecuteStopAction(action Action, input *StopInput, rawJ } // ExecuteSubagentStopAction executes an action for the SubagentStop event. -// Command failures result in exit status 2 to block the subagent stop operation. +// Per official Claude Code hooks spec: hook errors are non-blocking (allow subagent stop). func (e *ActionExecutor) ExecuteSubagentStopAction(action Action, input *SubagentStopInput, rawJSON any) (*ActionOutput, error) { switch action.Type { case "command": @@ -433,6 +429,7 @@ func (e *ActionExecutor) ExecuteSubagentStopAction(action Action, input *Subagen stdout, stderr, exitCode, err := e.runner.RunCommandWithOutput(cmd, action.UseStdin, rawJSON) // Command failed with non-zero exit code + // Per official spec: hook errors are non-blocking → allow subagent stop if exitCode != 0 { errMsg := fmt.Sprintf("Command failed with exit code %d: %s", exitCode, stderr) if strings.TrimSpace(stderr) == "" && err != nil { @@ -441,8 +438,7 @@ func (e *ActionExecutor) ExecuteSubagentStopAction(action Action, input *Subagen fmt.Fprintf(os.Stderr, "Warning: %s\n", errMsg) return &ActionOutput{ Continue: true, - Decision: "block", - Reason: errMsg, + Decision: "", // Allow subagent stop on error SystemMessage: errMsg, }, nil } @@ -462,8 +458,7 @@ func (e *ActionExecutor) ExecuteSubagentStopAction(action Action, input *Subagen fmt.Fprintf(os.Stderr, "Warning: %s\n", errMsg) return &ActionOutput{ Continue: true, - Decision: "block", - Reason: errMsg, + Decision: "", // Allow subagent stop on error SystemMessage: errMsg, }, nil } @@ -475,8 +470,7 @@ func (e *ActionExecutor) ExecuteSubagentStopAction(action Action, input *Subagen fmt.Fprintf(os.Stderr, "Warning: %s\n", errMsg) return &ActionOutput{ Continue: true, - Decision: "block", - Reason: errMsg, + Decision: "", // Allow subagent stop on error SystemMessage: errMsg, }, nil } @@ -487,8 +481,7 @@ func (e *ActionExecutor) ExecuteSubagentStopAction(action Action, input *Subagen fmt.Fprintf(os.Stderr, "Warning: %s\n", errMsg) return &ActionOutput{ Continue: true, - Decision: "block", - Reason: errMsg, + Decision: "", // Allow subagent stop on error SystemMessage: errMsg, }, nil } @@ -509,14 +502,14 @@ func (e *ActionExecutor) ExecuteSubagentStopAction(action Action, input *Subagen case "output": processedMessage := unifiedTemplateReplace(action.Message, rawJSON) - // Empty message check → fail-safe (decision: block) + // Empty message check → allow subagent stop (error in systemMessage) + // Per official spec: hook errors are non-blocking → allow subagent stop if strings.TrimSpace(processedMessage) == "" { errMsg := "Empty message in SubagentStop action" fmt.Fprintf(os.Stderr, "Warning: %s\n", errMsg) return &ActionOutput{ Continue: true, - Decision: "block", - Reason: errMsg, + Decision: "", // Allow subagent stop on error SystemMessage: errMsg, }, nil } @@ -535,8 +528,7 @@ func (e *ActionExecutor) ExecuteSubagentStopAction(action Action, input *Subagen fmt.Fprintf(os.Stderr, "Warning: %s\n", errMsg) return &ActionOutput{ Continue: true, - Decision: "block", - Reason: processedMessage, + Decision: "", // Allow subagent stop on error SystemMessage: errMsg, }, nil } @@ -562,13 +554,13 @@ func (e *ActionExecutor) ExecuteSubagentStopAction(action Action, input *Subagen } // Final validation: decision "block" requires non-empty reason + // Per official spec: hook errors are non-blocking → allow subagent stop on validation error if decision == "block" && strings.TrimSpace(reason) == "" { errMsg := "Empty reason when decision is 'block' (reason is required for block)" fmt.Fprintf(os.Stderr, "Warning: %s\n", errMsg) return &ActionOutput{ Continue: true, - Decision: "block", - Reason: errMsg, + Decision: "", // Allow subagent stop on error SystemMessage: errMsg, }, nil } diff --git a/executor_test.go b/executor_test.go index 607ae7a..4a313a2 100644 --- a/executor_test.go +++ b/executor_test.go @@ -1245,27 +1245,27 @@ func TestExecuteStopAndSubagentStopAction_TypeOutput(t *testing.T) { wantErr: false, }, { - name: "Stop: Invalid decision value -> fail-safe (decision: block)", + name: "Stop: Invalid decision value -> allow (error in systemMessage)", eventType: "Stop", action: Action{ Type: "output", Message: "Invalid decision test", Decision: stringPtr("invalid"), }, - wantDecision: "block", - wantReason: "Invalid decision test", + wantDecision: "", + wantReason: "", wantSystemMessage: "Invalid decision value in action config: must be 'block' or field must be omitted", wantErr: false, }, { - name: "Stop: Empty message -> fail-safe (decision: block, reason=fixed message)", + name: "Stop: Empty message -> allow (error in systemMessage)", eventType: "Stop", action: Action{ Type: "output", Message: "", }, - wantDecision: "block", - wantReason: "Empty message in Stop action", + wantDecision: "", + wantReason: "", wantSystemMessage: "Empty message in Stop action", wantErr: false, }, @@ -1360,26 +1360,26 @@ func TestExecuteStopAndSubagentStopAction_TypeOutput(t *testing.T) { wantSystemMessage: "Allowing subagent stop", }, { - name: "SubagentStop: invalid decision value - fail-safe block", + name: "SubagentStop: invalid decision value - allow (error in systemMessage)", eventType: "SubagentStop", action: Action{ Type: "output", Message: "Invalid decision", Decision: stringPtr("invalid"), }, - wantDecision: "block", - wantReason: "Invalid decision", + wantDecision: "", + wantReason: "", wantSystemMessage: "Invalid decision value in action config: must be 'block' or field must be omitted", }, { - name: "SubagentStop: empty message - fail-safe block", + name: "SubagentStop: empty message - allow (error in systemMessage)", eventType: "SubagentStop", action: Action{ Type: "output", Message: "", }, - wantDecision: "block", - wantReason: "Empty message in SubagentStop action", + wantDecision: "", + wantReason: "", wantSystemMessage: "Empty message in SubagentStop action", }, { @@ -1550,7 +1550,7 @@ func TestExecuteStopAndSubagentStopAction_TypeCommand(t *testing.T) { wantErr: false, }, { - name: "Stop: Command failure (exit != 0) -> fail-safe block", + name: "Stop: Command failure (exit != 0) -> allow (error in systemMessage)", eventType: "Stop", action: Action{ Type: "command", @@ -1559,8 +1559,8 @@ func TestExecuteStopAndSubagentStopAction_TypeCommand(t *testing.T) { stubStdout: "", stubStderr: "Permission denied", stubExitCode: 1, - wantDecision: "block", - wantReason: "Command failed with exit code 1: Permission denied", + wantDecision: "", + wantReason: "", wantSystemMessage: "Command failed with exit code 1: Permission denied", wantErr: false, }, @@ -1578,7 +1578,7 @@ func TestExecuteStopAndSubagentStopAction_TypeCommand(t *testing.T) { wantErr: false, }, { - name: "Stop: Invalid JSON -> fail-safe block", + name: "Stop: Invalid JSON -> allow (error in systemMessage)", eventType: "Stop", action: Action{ Type: "command", @@ -1586,13 +1586,13 @@ func TestExecuteStopAndSubagentStopAction_TypeCommand(t *testing.T) { }, stubStdout: `{invalid json}`, stubExitCode: 0, - wantDecision: "block", - wantReason: "Command output is not valid JSON: {invalid json}", + wantDecision: "", + wantReason: "", wantSystemMessage: "Command output is not valid JSON: {invalid json}", wantErr: false, }, { - name: "Stop: decision: block + reason missing -> fail-safe with reason warning", + name: "Stop: decision: block + reason missing -> allow (error in systemMessage)", eventType: "Stop", action: Action{ Type: "command", @@ -1603,13 +1603,13 @@ func TestExecuteStopAndSubagentStopAction_TypeCommand(t *testing.T) { "decision": "block" }`, stubExitCode: 0, - wantDecision: "block", - wantReason: "Missing required field 'reason' when decision is 'block'", + wantDecision: "", + wantReason: "", wantSystemMessage: "Missing required field 'reason' when decision is 'block'", wantErr: false, }, { - name: "Stop: Invalid decision value -> fail-safe block", + name: "Stop: Invalid decision value -> allow (error in systemMessage)", eventType: "Stop", action: Action{ Type: "command", @@ -1621,8 +1621,8 @@ func TestExecuteStopAndSubagentStopAction_TypeCommand(t *testing.T) { "reason": "should not matter" }`, stubExitCode: 0, - wantDecision: "block", - wantReason: "Invalid decision value: must be 'block' or field must be omitted entirely", + wantDecision: "", + wantReason: "", wantSystemMessage: "Invalid decision value: must be 'block' or field must be omitted entirely", wantErr: false, }, @@ -1705,7 +1705,7 @@ func TestExecuteStopAndSubagentStopAction_TypeCommand(t *testing.T) { wantErr: false, }, { - name: "SubagentStop: Command failure (exit != 0) -> fail-safe block", + name: "SubagentStop: Command failure (exit != 0) -> allow (error in systemMessage)", eventType: "SubagentStop", action: Action{ Type: "command", @@ -1714,8 +1714,8 @@ func TestExecuteStopAndSubagentStopAction_TypeCommand(t *testing.T) { stubStdout: "", stubStderr: "Permission denied", stubExitCode: 1, - wantDecision: "block", - wantReason: "Command failed with exit code 1: Permission denied", + wantDecision: "", + wantReason: "", wantSystemMessage: "Command failed with exit code 1: Permission denied", wantErr: false, }, @@ -1733,7 +1733,7 @@ func TestExecuteStopAndSubagentStopAction_TypeCommand(t *testing.T) { wantErr: false, }, { - name: "SubagentStop: Invalid JSON -> fail-safe block", + name: "SubagentStop: Invalid JSON -> allow (error in systemMessage)", eventType: "SubagentStop", action: Action{ Type: "command", @@ -1741,13 +1741,13 @@ func TestExecuteStopAndSubagentStopAction_TypeCommand(t *testing.T) { }, stubStdout: `{invalid json}`, stubExitCode: 0, - wantDecision: "block", - wantReason: "Command output is not valid JSON: {invalid json}", + wantDecision: "", + wantReason: "", wantSystemMessage: "Command output is not valid JSON: {invalid json}", wantErr: false, }, { - name: "SubagentStop: decision: block + reason missing -> fail-safe with reason warning", + name: "SubagentStop: decision: block + reason missing -> allow (error in systemMessage)", eventType: "SubagentStop", action: Action{ Type: "command", @@ -1758,13 +1758,13 @@ func TestExecuteStopAndSubagentStopAction_TypeCommand(t *testing.T) { "decision": "block" }`, stubExitCode: 0, - wantDecision: "block", - wantReason: "Missing required field 'reason' when decision is 'block'", + wantDecision: "", + wantReason: "", wantSystemMessage: "Missing required field 'reason' when decision is 'block'", wantErr: false, }, { - name: "SubagentStop: Invalid decision value -> fail-safe block", + name: "SubagentStop: Invalid decision value -> allow (error in systemMessage)", eventType: "SubagentStop", action: Action{ Type: "command", @@ -1776,8 +1776,8 @@ func TestExecuteStopAndSubagentStopAction_TypeCommand(t *testing.T) { "reason": "should not matter" }`, stubExitCode: 0, - wantDecision: "block", - wantReason: "Invalid decision value: must be 'block' or field must be omitted entirely", + wantDecision: "", + wantReason: "", wantSystemMessage: "Invalid decision value: must be 'block' or field must be omitted entirely", wantErr: false, }, diff --git a/hooks_execute.go b/hooks_execute.go index 0142a7f..4bfc6f2 100644 --- a/hooks_execute.go +++ b/hooks_execute.go @@ -371,14 +371,10 @@ func executeStopHooks(config *Config, input *StopInput, rawJSON any) (*StopOutpu var allErrors []error allErrors = append(allErrors, conditionErrors...) - // アクションエラーがある場合はfail-safe: decision "block" + // アクションエラーがある場合: 公式仕様に従いdecisionは変更しない(allow stop) + // エラー情報はsystemMessageに記録して観測性を維持 if len(actionErrors) > 0 { - finalOutput.Decision = "block" - errorMsg := errors.Join(actionErrors...).Error() - if finalOutput.Reason == "" { - finalOutput.Reason = errorMsg - } if finalOutput.SystemMessage != "" { finalOutput.SystemMessage += "\n" + errorMsg } else { @@ -396,7 +392,7 @@ func executeStopHooks(config *Config, input *StopInput, rawJSON any) (*StopOutpu } // executeSubagentStopHooks executes all matching SubagentStop hooks based on condition checks. -// Returns an error to block the subagent stop operation if any hook fails. +// Per official spec: hook errors are non-blocking (allow subagent stop). func executeSubagentStopHooks(config *Config, input *SubagentStopInput, rawJSON any) (*SubagentStopOutput, error) { executor := NewActionExecutor(nil) var conditionErrors []error @@ -495,14 +491,10 @@ func executeSubagentStopHooks(config *Config, input *SubagentStopInput, rawJSON var allErrors []error allErrors = append(allErrors, conditionErrors...) - // アクションエラーがある場合はfail-safe: decision "block" + // アクションエラーがある場合: 公式仕様に従いdecisionは変更しない(allow subagent stop) + // エラー情報はsystemMessageに記録して観測性を維持 if len(actionErrors) > 0 { - finalOutput.Decision = "block" - errorMsg := errors.Join(actionErrors...).Error() - if finalOutput.Reason == "" { - finalOutput.Reason = errorMsg - } if finalOutput.SystemMessage != "" { finalOutput.SystemMessage += "\n" + errorMsg } else { diff --git a/hooks_execute_test.go b/hooks_execute_test.go index 28689b7..4f336fc 100644 --- a/hooks_execute_test.go +++ b/hooks_execute_test.go @@ -466,19 +466,20 @@ func TestExecuteUserPromptSubmitHooks(t *testing.T) { } } -// TestExecuteStopAndSubagentStopHook_FailingCommandReturnsExit2 tests that failing commands -// result in decision="block" (fail-safe) from executeStopHooks and executeSubagentStopHooks. -func TestExecuteStopAndSubagentStopHook_FailingCommandReturnsExit2(t *testing.T) { +// TestExecuteStopAndSubagentStopHook_FailingCommandAllowsStop tests that failing commands +// result in decision="" (allow) from executeStopHooks and executeSubagentStopHooks. +// Per official Claude Code hooks spec, hook errors are non-blocking (allow stop). +func TestExecuteStopAndSubagentStopHook_FailingCommandAllowsStop(t *testing.T) { tests := []struct { name string eventType string // "Stop" or "SubagentStop" }{ { - name: "Stop: failing command returns decision block", + name: "Stop: failing command allows stop (decision empty)", eventType: "Stop", }, { - name: "SubagentStop: failing command returns decision block", + name: "SubagentStop: failing command allows subagent stop (decision empty)", eventType: "SubagentStop", }, } @@ -544,9 +545,9 @@ func TestExecuteStopAndSubagentStopHook_FailingCommandReturnsExit2(t *testing.T) continueValue = output.Continue } - // Fail-safe: decision should be "block" - if decision != "block" { - t.Errorf("Expected decision 'block' for failing command, got %q", decision) + // Per official spec: hook errors are non-blocking, decision should be "" (allow stop) + if decision != "" { + t.Errorf("Expected decision '' (allow) for failing command, got %q", decision) } // Continue should always be true @@ -1913,7 +1914,7 @@ func TestExecuteStopAndSubagentStopHooksJSON(t *testing.T) { wantErr: false, }, { - name: "Stop: 8. Action error - fail-safe block", + name: "Stop: 8. Action error - allow (error in systemMessage)", eventType: "Stop", stopConfig: []StopHook{ { @@ -1926,7 +1927,7 @@ func TestExecuteStopAndSubagentStopHooksJSON(t *testing.T) { }, }, wantContinue: true, - wantDecision: "block", + wantDecision: "", wantErr: false, }, { @@ -2075,7 +2076,7 @@ func TestExecuteStopAndSubagentStopHooksJSON(t *testing.T) { wantErr: false, }, { - name: "SubagentStop: 8. Action error - fail-safe block", + name: "SubagentStop: 8. Action error - allow (error in systemMessage)", eventType: "SubagentStop", subagentConfig: []SubagentStopHook{ { @@ -2088,8 +2089,8 @@ func TestExecuteStopAndSubagentStopHooksJSON(t *testing.T) { }, }, wantContinue: true, - wantDecision: "block", - wantReason: "Empty message in SubagentStop action", + wantDecision: "", + wantReason: "", wantErr: false, }, } diff --git a/main.go b/main.go index ca736f0..4bb20f7 100644 --- a/main.go +++ b/main.go @@ -185,18 +185,14 @@ func main() { // Log error to stderr fmt.Fprintf(os.Stderr, "Warning: %v\n", err) if output == nil { + // 公式仕様に従いdecisionは設定しない(allow stop) output = &StopOutput{ Continue: true, - Decision: "block", - Reason: fmt.Sprintf("Failed to process Stop: %v", err), SystemMessage: fmt.Sprintf("Failed to process Stop: %v", err), } } else { - // fail-safe: エラー時はdecisionを"block"に強制 - output.Decision = "block" - if output.Reason == "" { - output.Reason = fmt.Sprintf("Failed to process Stop: %v", err) - } + // 公式仕様に従いdecisionは変更しない(allow stop) + // エラー情報はsystemMessageに記録して観測性を維持 errMsg := fmt.Sprintf("Failed to process Stop: %v", err) if output.SystemMessage != "" { output.SystemMessage += "\n" + errMsg @@ -213,8 +209,6 @@ func main() { fmt.Fprintf(os.Stderr, "Warning: Error marshaling JSON: %v\n", err) fallbackOutput := StopOutput{ Continue: true, - Decision: "block", - Reason: fmt.Sprintf("Failed to marshal output: %v", err), SystemMessage: fmt.Sprintf("Failed to marshal output: %v", err), } jsonBytes, _ = json.MarshalIndent(fallbackOutput, "", " ") @@ -239,18 +233,14 @@ func main() { // Log error to stderr fmt.Fprintf(os.Stderr, "Warning: %v\n", err) if output == nil { + // 公式仕様に従いdecisionは設定しない(allow subagent stop) output = &SubagentStopOutput{ Continue: true, - Decision: "block", - Reason: fmt.Sprintf("Failed to process SubagentStop: %v", err), SystemMessage: fmt.Sprintf("Failed to process SubagentStop: %v", err), } } else { - // fail-safe: エラー時はdecisionを"block"に強制 - output.Decision = "block" - if output.Reason == "" { - output.Reason = fmt.Sprintf("Failed to process SubagentStop: %v", err) - } + // 公式仕様に従いdecisionは変更しない(allow subagent stop) + // エラー情報はsystemMessageに記録して観測性を維持 errMsg := fmt.Sprintf("Failed to process SubagentStop: %v", err) if output.SystemMessage != "" { output.SystemMessage += "\n" + errMsg @@ -267,8 +257,6 @@ func main() { fmt.Fprintf(os.Stderr, "Warning: Error marshaling JSON: %v\n", err) fallbackOutput := SubagentStopOutput{ Continue: true, - Decision: "block", - Reason: fmt.Sprintf("Failed to marshal output: %v", err), SystemMessage: fmt.Sprintf("Failed to marshal output: %v", err), } jsonBytes, _ = json.MarshalIndent(fallbackOutput, "", " ")