microsoft/typescript-go · error · ErrorCode

-32600

-32600

Error message

%w: %w

What it means

In lspReader.Read (server.go:133-137), the raw JSON-RPC message itself failed to unmarshal into lsproto.Message — malformed frame JSON, missing protocol-required members (jsonrpc/method), wrong top-level kinds — and the error does not classify as InvalidParams, so it is wrapped with ErrorCodeInvalidRequest (-32600). Unlike the -32602 path, the message is returned as nil: the frame is unidentifiable and only a generic error response (if the id could be recovered) is possible.

Source

Thrown at internal/lsp/server.go:137

func (e *messageMarshalError) Error() string { return "failed to marshal message: " + e.err.Error() }

func (e *messageMarshalError) Unwrap() []error {
	return []error{lsproto.ErrorCodeInternalError, e.err}
}

func (r *lspReader) Read() (*lsproto.Message, error) {
	data, err := r.r.Read()
	if err != nil {
		return nil, err
	}

	req := &lsproto.Message{}
	if err := json.Unmarshal(data, req); err != nil {
		if errors.Is(err, lsproto.ErrorCodeInvalidParams) {
			return req, fmt.Errorf("%w: %w", lsproto.ErrorCodeInvalidParams, err)
		}
		return nil, fmt.Errorf("%w: %w", lsproto.ErrorCodeInvalidRequest, err)
	}

	return req, nil
}

func ToReader(r io.Reader) Reader {
	return &lspReader{r: lsproto.NewBaseReader(r)}
}

func (w *lspWriter) Write(msg *lsproto.Message) error {
	data, err := json.Marshal(msg)
	if err != nil {
		return &messageMarshalError{err: err}
	}
	return w.w.Write(data)
}

func ToWriter(w io.Writer) Writer {

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Log the raw frame bytes and validate them with an independent JSON-RPC parser; fix framing (exact Content-Length byte count, UTF-8 no BOM).
  2. Ensure single messages only — no batch arrays — and that every request/notification carries jsonrpc and method.
  3. If a specific required member is rejected, compare against the JSON-RPC 2.0 shape lsproto.Message enforces (union of request/notification/response with strict kinds).
  4. For proxies, pass the byte stream through unmodified and never interleave stdout writes.

Example fix

// before: batch frame
[{"jsonrpc":"2.0","id":1,"method":"shutdown"}]
// after: single frame
{"jsonrpc":"2.0","id":1,"method":"shutdown"}
Defensive patterns

Strategy: try-catch

Validate before calling

// sender-side: validate the frame is a single, well-formed JSON-RPC message
var probe any
if err := json.Unmarshal(frame, &probe); err != nil {
    return fmt.Errorf("bad frame JSON: %w", err)
}
m, ok := probe.(map[string]any)
if !ok || m["jsonrpc"] != "2.0" {
    return fmt.Errorf("frame must be a single JSON-RPC 2.0 object")
}

Type guard

func isSingleJsonRpcObject(frame []byte) bool {
    var v any
    if json.Unmarshal(frame, &v) != nil {
        return false
    }
    m, ok := v.(map[string]any)
    return ok && m["jsonrpc"] == "2.0"
}

Try / catch

if _, err := reader.Read(); err != nil {
    if errors.Is(err, lsproto.ErrorCodeInvalidRequest) {
        // recoverable: skip the malformed frame, optionally send -32600 if an id was parsed
        continue
    }
    return err
}

Prevention

When it happens

Trigger: Sending a JSON-RPC frame that is an array (batch, unsupported shape here), a frame missing "method", a frame where "id" is an object, or invalid JSON bytes from a misframed Content-Length stream; any decode failure in Message.UnmarshalJSON outside the params stage.

Common situations: Broken Content-Length framing (length mismatch truncating JSON); clients sending batch requests; proxies inserting BOM or log lines into the stream; version skew where clients assume permissive base-protocol parsing.

Related errors


AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16). Data as JSON: /api/errors/4a573d0b27a91021. Report an issue: GitHub.