chenhg5/cc-connect · error

codex app-server encode: %w

Error message

codex app-server encode: %w

What it means

`writeJSON` marshals the JSON-RPC payload with `json.Marshal`; if that fails it returns `codex app-server encode: %w`. For payloads built from `map[string]any` holding normal strings/numbers this is nearly impossible, so it usually signals a caller-supplied value containing unmarshalable data (channels, funcs, NaN floats, cyclic structures).

Source

Thrown at agent/codex/appserver_session.go:1731

	}
	s.procMu.Unlock()
}

func (s *appServerSession) notify(method string, params any) error {
	payload := map[string]any{
		"jsonrpc": "2.0",
		"method":  method,
	}
	if params != nil {
		payload["params"] = params
	}
	return s.writeJSON(payload)
}

func (s *appServerSession) writeJSON(v any) error {
	b, err := json.Marshal(v)
	if err != nil {
		return fmt.Errorf("codex app-server encode: %w", err)
	}

	s.procMu.Lock()
	stdin := s.stdin
	s.procMu.Unlock()
	if stdin == nil {
		return fmt.Errorf("codex app-server connection is closed")
	}

	s.writeMu.Lock()
	defer s.writeMu.Unlock()
	if _, err := stdin.Write(append(b, '\n')); err != nil {
		return fmt.Errorf("codex app-server write: %w", err)
	}
	return nil
}

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Inspect the params value passed to the failing call for NaN/Inf floats or non-serializable types.
  2. Sanitize user options before passing them into session requests (only string maps allowed).
  3. Add a dry-run `json.Marshal(params)` in tests to catch this early.
  4. Ensure numeric values derived from usage/rate data are checked with math.IsNaN/IsInf before use.

Example fix

// before: pass through raw float from external data
params := map[string]any{"tokens": usage.Ratio} // may be NaN

// after: sanitize
if math.IsNaN(usage.Ratio) || math.IsInf(usage.Ratio, 0) {
    usage.Ratio = 0
}
params := map[string]any{"tokens": usage.Ratio}
Defensive patterns

Strategy: validation

Validate before calling

// validate params are marshalable before sending
func validateParams(params any) error {
    _, err := json.Marshal(params)
    return err
}
// call before request()/notify()

Type guard

func isMarshalable(v any) bool {
    rv := reflect.ValueOf(v)
    switch rv.Kind() {
    case reflect.Chan, reflect.Func, reflect.UnsafePointer:
        return false
    case reflect.Float64, reflect.Float32:
        f := rv.Float()
        return !math.IsNaN(f) && !math.IsInf(f, 0)
    }
    return true
}

Try / catch

if err := sess.notify(method, params); err != nil {
    var encErr *json.UnsupportedTypeError
    if errors.As(err, &encErr) || strings.Contains(err.Error(), "encode") {
        slog.Error("unmarshalable params", "method", method, "err", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling `request`, `requestWithTimeout`, or `notify` with params containing a value json.Marshal cannot encode: NaN/Inf floats, channels, funcs, or cyclic references in options maps.

Common situations: User-supplied `options["env"]`/`args` maps carrying exotic values propagated into request params; floats computed from usage stats that became NaN; a bug passing a non-serializable type as params.

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