chenhg5/cc-connect · error

marshal: %w

Error message

marshal: %w

What it means

writeJSON wraps a json.Marshal failure as 'marshal: %w'. The payload constructed by Send (user message with images/files) or RespondPermission (permission result) could not be serialized to JSON. In practice this is rare for these structurally-typed maps and usually indicates non-serializable values injected into the payload (e.g. NaN/infinite floats, channels, funcs) via UpdatedInput or attachments.

Source

Thrown at agent/claudecode/session.go:1095

		"type": "control_response",
		"response": map[string]any{
			"subtype":    "success",
			"request_id": requestID,
			"response":   permResponse,
		},
	}

	slog.Debug("claudeSession: permission response", "request_id", requestID, "behavior", result.Behavior)
	return cs.writeJSON(controlResponse)
}

func (cs *claudeSession) writeJSON(v any) error {
	cs.stdinMu.Lock()
	defer cs.stdinMu.Unlock()

	data, err := json.Marshal(v)
	if err != nil {
		return fmt.Errorf("marshal: %w", err)
	}
	if _, err := cs.stdin.Write(append(data, '\n')); err != nil {
		return fmt.Errorf("write stdin: %w", err)
	}
	return nil
}

func isClaudeEditTool(toolName string) bool {
	switch toolName {
	case "Edit", "Write", "NotebookEdit", "MultiEdit":
		return true
	default:
		return false
	}
}

func (cs *claudeSession) setPermissionMode(mode string) {
	cs.permissionMode.Store(mode)

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Inspect the wrapped error's path (json.UnsupportedTypeError names the field) and sanitize/convert that value to a serializable type (string, float64, etc.)
  2. In RespondPermission, ensure UpdatedInput only carries map[string]any / primitives — coerce or drop exotic values before assigning
  3. Log the payload type at debug level to find which caller (Send vs RespondPermission) produced the bad value
  4. Fix at the source: validate permission results before calling RespondPermission

Example fix

// before
result.UpdatedInput = rawToolInput // may contain NaN / unsupported types
// after
safe, err := sanitizeJSONValue(rawToolInput)
if err != nil {
    return fmt.Errorf("sanitize updated input: %w", err)
}
result.UpdatedInput = safe
Defensive patterns

Strategy: validation

Validate before calling

// validate payloads are JSON-safe before calling Send/RespondPermission
func jsonSafe(v any) error {
    _, err := json.Marshal(v)
    return err // surfaces UnsupportedTypeError with the offending path
}
// usage: if err := jsonSafe(result.UpdatedInput); err != nil { return err }

Try / catch

if err := sess.RespondPermission(reqID, result); err != nil {
    var ue *json.UnsupportedTypeError
    if errors.As(err, &ue) && strings.Contains(err.Error(), "marshal") {
        log.Warn("non-serializable value in permission result", "field", ue.Value)
        result.UpdatedInput = coerceToPrimitives(result.UpdatedInput)
        err = sess.RespondPermission(reqID, result)
    }
}

Prevention

When it happens

Trigger: Send with images/files whose metadata fields contain unsupported types; RespondPermission with result.UpdatedInput populated from parsed tool input containing values json.Marshal rejects (NaN, cyclic structures, func values).

Common situations: Tool input round-tripped through custom parsers storing unusual types; plugins/agent code inserting unsupported values into UpdatedInput; custom image/file attachment structs with non-serializable fields.

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


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