chenhg5/cc-connect · error

decode %s response: %w

Error message

decode %s response: %w

What it means

Returned when the app-server's JSON-RPC result for a request cannot be unmarshaled into the caller-provided `out` value: `fmt.Errorf("decode %s response: %w", method, err)`. This indicates the response shape does not match the expected Go struct — usually a codex protocol version change rather than a runtime failure.

Source

Thrown at agent/codex/appserver_session.go:1646

	remaining := time.Until(deadline)
	if remaining <= 0 {
		s.pendingMu.Lock()
		delete(s.pending, id)
		s.pendingMu.Unlock()
		return fmt.Errorf("%s timed out", method)
	}

	timer := time.NewTimer(remaining)
	defer timer.Stop()
	ctxDone := s.contextDone()
	select {
	case resp := <-ch:
		if resp.Error != nil {
			return fmt.Errorf("%s", strings.TrimSpace(resp.Error.Message))
		}
		if out != nil {
			if err := json.Unmarshal(resp.Result, out); err != nil {
				return fmt.Errorf("decode %s response: %w", method, err)
			}
		}
		return nil
	case <-ctxDone:
		return s.contextErr()
	case <-timer.C:
		s.pendingMu.Lock()
		delete(s.pending, id)
		s.pendingMu.Unlock()
		return fmt.Errorf("%s timed out", method)
	}
}

func (s *appServerSession) writeJSONWithTimeout(method string, v any, timeout time.Duration) error {
	done := make(chan error, 1)
	go func() {
		done <- s.writeJSON(v)
	}()

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Pin/upgrade cc-connect to a version matching your installed codex CLI version.
  2. Log `string(resp.Result)` for the failing method to compare actual vs expected schema.
  3. Check that the `out` argument passed to `request()` matches the method's documented response type.
  4. Downgrade codex if you must stay on the current cc-connect release.
  5. Handle null results defensively by decoding into json.RawMessage first.

Example fix

// before: assume old schema
var out threadStartResponse
s.request("thread/start", params, &out)

// after: inspect raw payload when decoding fails
var raw json.RawMessage
if err := s.request("thread/start", params, &raw); err != nil { ... }
slog.Debug("codex raw response", "body", string(raw))
Defensive patterns

Strategy: type-guard

Validate before calling

// decode into RawMessage first to detect shape drift
var raw json.RawMessage
if err := sess.request("thread/start", params, &raw); err != nil { return err }
var probe map[string]json.RawMessage
return json.Unmarshal(raw, &probe) // inspect keys before strict decode

Type guard

func decodeAs[T any](raw json.RawMessage) (*T, error) {
    var v T
    if err := json.Unmarshal(raw, &v); err != nil {
        return nil, fmt.Errorf("schema mismatch: %w", err)
    }
    return &v, nil
}

Try / catch

if err := json.Unmarshal(resp.Result, out); err != nil {
    slog.Error("codex response schema drift", "method", method, "body", string(resp.Result))
    return fmt.Errorf("decode %s response: %w", method, err)
}

Prevention

When it happens

Trigger: A successful (non-error) JSON-RPC response arrives but `json.Unmarshal(resp.Result, out)` fails — e.g. field type changed, new schema, null where a struct is expected, or a wrong `out` type passed by the caller.

Common situations: Codex CLI upgraded and response fields renamed/retyped; decoding into the wrong struct for the method; server returning null result for methods expected to return objects; proxy/middleware mangling the response JSON.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


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