grpc/grpc-go · error

invalid code

Error message

invalid code: %d

What it means

Returned by codes.Code.UnmarshalJSON when the JSON input is a numeric value that is >= _maxCode (17). gRPC status codes are only defined for 0 (OK) through 16 (Unauthenticated); any integer 17 or above is out of the valid range and cannot be mapped to a gRPC Code.

Solutions

  1. Map the external numeric code into a valid gRPC Code (0–16) before unmarshaling, e.g. convert HTTP 404 to codes.NotFound(5).
  2. If the JSON value is a string code name, send it as a quoted string (e.g. "NOT_FOUND") instead of a number.
  3. Validate the numeric range [0,16] before assigning it to a codes.Code to surface the problem at the right boundary.

Example fix

// before
var c codes.Code
json.Unmarshal([]byte(`404`), &c) // invalid code: 404

// after
var c codes.Code = codes.NotFound // map HTTP 404 -> gRPC NotFound before encoding
Defensive patterns

Strategy: validation

Validate before calling

func parseNumericCode(b []byte) (codes.Code, error) {
    n, err := strconv.ParseUint(string(b), 10, 32)
    if err != nil {
        return codes.OK, err
    }
    if n >= 17 {
        return codes.OK, fmt.Errorf("code %d out of range [0,16]", n)
    }
    return codes.Code(n), nil
}

Type guard

func isValidNumericCode(n uint32) bool { return n < 17 }

Prevention

When it happens

Trigger: Calling json.Unmarshal on a *codes.Code with input like 17, 99, or 500 — i.e. a numeric JSON token (no quotes) whose value is outside [0, 16]. Also triggered by serializing a custom/unknown code from another system and deserializing it as a number.

Common situations: Interoperability with a non-gRPC system that uses its own numeric error codes that exceed the gRPC range, or a protobuf/JSON payload whose 'code' field was populated with an HTTP-style status code (e.g. 404) instead of a gRPC code.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/8a8e78cc29ef59dc. Report an issue: GitHub.

Appendix: source

Thrown at codes/codes.go:238

	`"DATA_LOSS"`:           DataLoss,
	`"UNAUTHENTICATED"`:     Unauthenticated,
}

// UnmarshalJSON unmarshals b into the Code.
func (c *Code) UnmarshalJSON(b []byte) error {
	// From json.Unmarshaler: By convention, to approximate the behavior of
	// Unmarshal itself, Unmarshalers implement UnmarshalJSON([]byte("null")) as
	// a no-op.
	if string(b) == "null" {
		return nil
	}
	if c == nil {
		return fmt.Errorf("nil receiver passed to UnmarshalJSON")
	}

	if ci, err := strconv.ParseUint(string(b), 10, 32); err == nil {
		if ci >= _maxCode {
			return fmt.Errorf("invalid code: %d", ci)
		}

		*c = Code(ci)
		return nil
	}

	if jc, ok := strToCode[string(b)]; ok {
		*c = jc
		return nil
	}
	return fmt.Errorf("invalid code: %q", string(b))
}

View on GitHub (pinned to 0c51461d27)