chenhg5/cc-connect · error
marshal stdin: %w
Error message
marshal stdin: %w
What it means
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.
Source
Thrown at agent/claudecode/cc_hooks.go:249
// matchHookEntry checks if toolName matches the matcher.
// Empty or "*" matcher matches everything. Otherwise exact match.
func matchHookEntry(matcher, toolName string) bool {
if matcher == "" || matcher == "*" {
return true
}
return strings.EqualFold(matcher, toolName)
}
// runHookCommand executes a hook command with tool info on stdin.
// Returns the parsed decision. Timeout: 60s (matching Claude Code's own).
func runHookCommand(
ctx context.Context,
command string,
stdinData map[string]any,
) (ccHookDecision, error) {
stdinJSON, err := json.Marshal(stdinData)
if err != nil {
return ccHookDecision{}, fmt.Errorf("marshal stdin: %w", err)
}
timeoutCtx, cancel := context.WithTimeout(ctx, 60*time.Second)
defer cancel()
cmd := exec.CommandContext(timeoutCtx, "sh", "-c", command)
cmd.Stdin = bytes.NewReader(stdinJSON)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
// Strip the skip flag so the hook does real work when cc-connect
// calls it (even if the host environment has it set).
cmd.Env = filterEnv(os.Environ(), "CC_CONNECT_PERMISSION_HOOK_SKIP")
if err := cmd.Run(); err != nil {
return ccHookDecision{}, fmt.Errorf("hook exec: %w (stderr: %s)", err, truncateStr(strings.TrimSpace(stderr.String()), 200))
}
View on GitHub (pinned to 4000b2338a)
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
Example fix
// before
stdinData["deadline"] = time.Time{} // or a func/NaN value
// after
stdinData["deadline"] = t.UTC().Format(time.RFC3339) Defensive patterns
Strategy: validation
Validate before calling
if err := json.Valid(mustJSON(stdinData)); err != nil { ... } // or pre-check:
for k, v := range stdinData { if !jsonSerializable(v) { return fmt.Errorf("field %q not JSON-serializable", k) } } Type guard
func jsonSerializable(v any) bool { _, err := json.Marshal(v); return err == nil } Try / catch
decision, err := runHookCommand(ctx, cmd, stdinData)
if err != nil {
var ute *json.UnsupportedTypeError
if errors.As(err, &ute) { log.Errorf("non-serializable value in hook input: %v", ute.Value) }
return err
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.
Related errors
- marshal Agy permission hook: %w
- marshal Agy hooks overlay: %w
- parse hook output: %w
- codex app-server encode: %w
- bridge: marshal reconstruct reply ctx: %w
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/6c74208f161b356e.
Report an issue: GitHub.