chenhg5/cc-connect · warning

piSession: marshal command

Error message

piSession: marshal command: %w

What it means

agent/pi/session.go:454 — writeRPCCommand marshals the command map to JSON before writing it to the RPC stdin; if json.Marshal fails it returns "piSession: marshal command: %w". The commands built by this library (get_state probe, prompt commands) are plain map[string]any of JSON-safe values, so this nearly always indicates non-serializable data (channels, funcs, or invalid values like NaN) injected into the command payload — e.g. via prompt or attachment path content.

Solutions

  1. Inspect the wrapped json error (UnsupportedTypeError names the offending Go type) and remove/convert that value.
  2. Sanitize payload values: ensure prompt text, image paths, and file paths are plain strings.
  3. Replace NaN/Inf floats with strings or omit them from the command map.
  4. If you maintain custom pi-agent code, validate the command with json.Marshal in a test before calling writeRPCCommand.

Example fix

// before
cmd := map[string]any{"type": "prompt", "message": msg, "cb": callback} // func not marshalable
// after
cmd := map[string]any{"type": "prompt", "message": msg}
Defensive patterns

Strategy: validation

Validate before calling

func validateRPCCommand(cmd map[string]any) error {
    if _, err := json.Marshal(cmd); err != nil {
        return fmt.Errorf("command not JSON-serializable: %w", err)
    }
    return nil
}

Try / catch

if err := validateRPCCommand(cmdMap); err != nil {
    return fmt.Errorf("refusing to send invalid RPC command: %w", err)
}
if err := writeRPCCommand(cmdMap); err != nil {
    if strings.Contains(err.Error(), "marshal command:") {
        slog.Error("RPC command marshal failed; sanitize payload", "err", err)
    }
    return err
}

Prevention

When it happens

Trigger: writeRPCCommand is called by startRPC (get_state probe) and sendRPC (prompt commands); marshal fails when: (1) a value in the command map is not JSON-marshalable (chan, func, complex); (2) an unsupported value type slips in via extra args/attachment lists; (3) json.Marshal returns json.UnsupportedTypeError / UnsupportedValueError for float NaN/Inf.

Common situations: Custom code or a fork injecting non-JSON-safe fields into the prompt command; NaN/Inf values propagated into the payload; misuse of the internal API by plugin/extension code building commands with richer types.

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/e93e43c4ec9d8deb. Report an issue: GitHub.

Appendix: source

Thrown at agent/pi/session.go:454

	}
	sid := s.CurrentSessionID()
	evt := core.Event{Type: core.EventResult, SessionID: sid, Done: true}
	select {
	case s.events <- evt:
	case <-s.ctx.Done():
	}

	return nil
}

// writeRPCCommand marshals cmd as a single JSONL line and writes it to the
// RPC process's stdin under rpcStdinMu. Used by both sendRPC (for "prompt"
// commands during a turn) and startRPC (for the startup "get_state" probe
// that fetches the session id before callers are released).
func (s *piSession) writeRPCCommand(cmd map[string]any) error {
	b, err := json.Marshal(cmd)
	if err != nil {
		return fmt.Errorf("piSession: marshal command: %w", err)
	}
	b = append(b, '\n')

	s.rpcStdinMu.Lock()
	_, err = s.rpcStdin.Write(b)
	s.rpcStdinMu.Unlock()
	if err != nil {
		return fmt.Errorf("piSession: write stdin: %w", err)
	}
	return nil
}

// sendRPC writes a JSON "prompt" command to the persistent RPC process stdin.
// Events are read asynchronously by readLoopRPC, including agent_end which
// triggers EventResult.
//
// Issue #1723: image paths are embedded into the message text as
// @<path> references (pi's standard mechanism, parsed the same way as in

View on GitHub (pinned to 4000b2338a)