{"record":{"id":"6c74208f161b356e","repo":"chenhg5/cc-connect","slug":"marshal-stdin-w","errorCode":null,"errorMessage":"marshal stdin: %w","messagePattern":"marshal stdin: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/claudecode/cc_hooks.go","lineNumber":249,"sourceCode":"// matchHookEntry checks if toolName matches the matcher.\n// Empty or \"*\" matcher matches everything. Otherwise exact match.\nfunc matchHookEntry(matcher, toolName string) bool {\n\tif matcher == \"\" || matcher == \"*\" {\n\t\treturn true\n\t}\n\treturn strings.EqualFold(matcher, toolName)\n}\n\n// runHookCommand executes a hook command with tool info on stdin.\n// Returns the parsed decision. Timeout: 60s (matching Claude Code's own).\nfunc runHookCommand(\n\tctx context.Context,\n\tcommand string,\n\tstdinData map[string]any,\n) (ccHookDecision, error) {\n\tstdinJSON, err := json.Marshal(stdinData)\n\tif err != nil {\n\t\treturn ccHookDecision{}, fmt.Errorf(\"marshal stdin: %w\", err)\n\t}\n\n\ttimeoutCtx, cancel := context.WithTimeout(ctx, 60*time.Second)\n\tdefer cancel()\n\n\tcmd := exec.CommandContext(timeoutCtx, \"sh\", \"-c\", command)\n\tcmd.Stdin = bytes.NewReader(stdinJSON)\n\tvar stdout, stderr bytes.Buffer\n\tcmd.Stdout = &stdout\n\tcmd.Stderr = &stderr\n\t// Strip the skip flag so the hook does real work when cc-connect\n\t// calls it (even if the host environment has it set).\n\tcmd.Env = filterEnv(os.Environ(), \"CC_CONNECT_PERMISSION_HOOK_SKIP\")\n\n\tif err := cmd.Run(); err != nil {\n\t\treturn ccHookDecision{}, fmt.Errorf(\"hook exec: %w (stderr: %s)\", err, truncateStr(strings.TrimSpace(stderr.String()), 200))\n\t}\n","sourceCodeStart":231,"sourceCodeEnd":267,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/claudecode/cc_hooks.go#L231-L267","documentation":"runHookCommand marshals the hook's stdin payload (map[string]any) to JSON before piping it to the configured hook command. This error wraps json.Marshal failure — practically only when the map contains values JSON cannot represent, such as channels, funcs, or NaN/Inf floats injected by a caller.","triggerScenarios":"tryHook → runHookCommand → json.Marshal(stdinData) returns an UnsupportedTypeError/UnsupportedValueError because the caller populated stdinData with a non-JSON-serializable value (channel, func, cyclic structure, NaN).","commonSituations":"A developer extended the hook-input construction and passed a non-serializable field (e.g. a time.Time in a legacy format, a func value, or a struct with unexported/marshal-failing types); floats produced by division became NaN.","solutions":["Inspect what was put into stdinData and remove/convert non-JSON-serializable values (use a plain string/number/bool/map/slice)","Sanitize numeric values before marshaling — replace NaN/Inf with 0 or omit the field","If adding a new hook field, marshal it in a unit test first to catch UnsupportedTypeError early"],"exampleFix":"// before\nstdinData[\"deadline\"] = time.Time{} // or a func/NaN value\n// after\nstdinData[\"deadline\"] = t.UTC().Format(time.RFC3339)","handlingStrategy":"validation","validationCode":"if err := json.Valid(mustJSON(stdinData)); err != nil { ... } // or pre-check:\nfor k, v := range stdinData { if !jsonSerializable(v) { return fmt.Errorf(\"field %q not JSON-serializable\", k) } }","typeGuard":"func jsonSerializable(v any) bool { _, err := json.Marshal(v); return err == nil }","tryCatchPattern":"decision, err := runHookCommand(ctx, cmd, stdinData)\nif err != nil {\n    var ute *json.UnsupportedTypeError\n    if errors.As(err, &ute) { log.Errorf(\"non-serializable value in hook input: %v\", ute.Value) }\n    return err\n}","preventionTips":["Build hook stdin payloads only from JSON-native types (string, number, bool, map, slice)","Sanitize floats (NaN/Inf) before insertion","Unit-test marshaling whenever a new hook input field is added"],"tags":["json","marshal","hooks"],"backgroundTag":"json-marshal-failed","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}