chenhg5/cc-connect · error

parse hook output: %w

Error message

parse hook output: %w

What it means

parseHookOutput parses the permission hook's stdout JSON into ccHookDecision. This error wraps json.Unmarshal failure on the trimmed stdout, meaning the hook printed something that is not valid JSON (or a JSON shape differing from the expected hookSpecificOutput.decision wrapper).

Source

Thrown at agent/claudecode/cc_hooks.go:299

	case "allow":
		return ccHookDecision{Behavior: "allow"}, nil
	case "deny":
		return ccHookDecision{Behavior: "deny"}, nil
	case "ask":
		return ccHookDecision{}, nil
	}

	// Try structured JSON output.
	var out struct {
		HookSpecificOutput struct {
			Decision struct {
				Behavior string `json:"behavior"`
				Message  string `json:"message"`
			} `json:"decision"`
		} `json:"hookSpecificOutput"`
	}
	if err := json.Unmarshal(trimmed, &out); err != nil {
		return ccHookDecision{}, fmt.Errorf("parse hook output: %w", err)
	}
	behavior := strings.ToLower(out.HookSpecificOutput.Decision.Behavior)
	if behavior == "allow" || behavior == "deny" {
		return ccHookDecision{
			Behavior: behavior,
			Message:  out.HookSpecificOutput.Decision.Message,
		}, nil
	}
	return ccHookDecision{}, nil
}

// buildHookStdin constructs the JSON payload for the hook's stdin,
// matching Claude Code's PermissionRequest hook input spec.
func buildHookStdin(hctx hookContext) map[string]any {
	m := map[string]any{
		"session_id":      hctx.sessionID,
		"hook_event_name": "PermissionRequest",
		"tool_name":       hctx.toolName,

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Fix the hook script so its final stdout line is exactly one JSON object with hookSpecificOutput.decision.behavior of "allow" or "deny"
  2. Move any logging in the hook to stderr so stdout stays clean JSON
  3. Check the hook's schema against Claude Code's current permission-hook output format and update keys
  4. Run the hook manually and pipe stdout through `jq` to confirm it parses

Example fix

// before (hook script)
echo "checking permission..."
echo '{"behavior":"allow"}'
// after
echo "checking permission..." >&2
echo '{"hookSpecificOutput":{"decision":{"behavior":"allow","message":"ok"}}}'
Defensive patterns

Strategy: validation

Validate before calling

out, _ := sh("-c", hookCommand)
trimmed := strings.TrimSpace(out)
if !json.Valid([]byte(trimmed)) { return fmt.Errorf("hook stdout is not JSON: %q", trimmed) }

Type guard

func emitsHookJSON(cmd string) bool {
    out, err := exec.Command("sh", "-c", cmd).Output()
    if err != nil { return false }
    var v struct{ HookSpecificOutput struct{ Decision struct{ Behavior string `json:"behavior"` } `json:"decision"` } `json:"hookSpecificOutput"` }
    return json.Unmarshal([]byte(strings.TrimSpace(string(out))), &v) == nil
}

Try / catch

decision, err := parseHookOutput(stdout.Bytes())
if err != nil {
    log.Errorf("hook stdout not parseable: %v; raw=%q", err, string(stdout.Bytes()))
    return ccHookDecision{}, err
}

Prevention

When it happens

Trigger: runHookCommand → parseHookOutput(stdout.Bytes()) fails when the hook command prints human-readable logs, empty output, or JSON without the expected {"hookSpecificOutput":{"decision":{"behavior":...}}} nesting, so Unmarshal errors or returns fields that don't fit.

Common situations: Hook script echoes debug text before/instead of the JSON decision; hook uses an older Claude Code hook output schema; hook prints nothing and the unmarshal fails on empty input; hook emits valid JSON but a different key layout.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/288489f59b11c423. Report an issue: GitHub.