m1k1o/neko · error

%w: %s

Error message

%w: %s

What it means

This error is produced in session.req when the backend returned a non-success status code AND the response body successfully unmarshaled as JSON containing a 'message' field. It wraps the ErrBackendRespone sentinel with the backend's structured error message, giving callers both a programmatically checkable cause (errors.Is) and the backend's explanation. It means the backend explicitly rejected the request with a JSON error payload.

Source

Thrown at server/internal/http/legacy/session.go:104

	if s.token != "" {
		req.Header.Set("Authorization", "Bearer "+s.token)
	}

	res, err := s.client.Do(req)
	if err != nil {
		return nil, nil, err
	}

	if res.StatusCode < 200 || res.StatusCode >= 300 {
		defer res.Body.Close()

		body, _ := io.ReadAll(res.Body)
		// try to unmarsal as json error message
		var apiErr struct {
			Message string `json:"message"`
		}
		if err := json.Unmarshal(body, &apiErr); err == nil {
			return nil, nil, fmt.Errorf("%w: %s", ErrBackendRespone, apiErr.Message)
		}
		// return raw body if failed to unmarshal
		return nil, nil, fmt.Errorf("unexpected status code: %d, body: %s", res.StatusCode, strings.TrimSpace(string(body)))
	}

	return res.Body, res.Header, nil
}

func (s *session) apiReq(method, path string, request, response any) error {
	reqBody, err := json.Marshal(request)
	if err != nil {
		return err
	}

	headers := http.Header{
		"Content-Type": []string{"application/json"},
	}

View on GitHub (pinned to b0f01cedea)

Solutions

  1. Read the wrapped message (err.Error() contains '%w: <backend message>') to identify the exact backend complaint.
  2. Refresh or fix the auth token if the message indicates unauthorized/forbidden.
  3. Check that the requested resource (room, session id) still exists before calling.
  4. Compare proxy API paths with the backend version's routes; upgrade or downgrade to align API versions.

Example fix

// before
err := s.apiReq("GET", "/api/room/settings", nil, &settings)
// after
err := s.apiReq("GET", "/api/room/settings", nil, &settings)
if errors.Is(err, legacy.ErrBackendRespone) {
    log.Error().Err(err).Msg("backend rejected request")
    // re-authenticate or surface backend message to caller
}
Defensive patterns

Strategy: type-guard

Validate before calling

if token == "" {
    return errors.New("cannot call backend API: auth token is empty")
}

Type guard

func backendJSONError(err error) (string, bool) {
    if errors.Is(err, ErrBackendRespone) {
        parts := strings.SplitN(err.Error(), ": ", 2)
        if len(parts) == 2 {
            return parts[1], true
        }
    }
    return "", false
}

Try / catch

body, hdr, err := s.req(method, path, req)
if err != nil {
    var msg string
    if errors.Is(err, ErrBackendRespone) {
        msg = strings.TrimPrefix(err.Error(), "error response from backend: ")
        // e.g. re-authenticate on 'unauthorized'
    }
    return fmt.Errorf("api request failed: %s", msg)
}

Prevention

When it happens

Trigger: session.req / apiReq receives e.g. 401 {"message":"unauthorized"}, 404 {"message":"room not found"}, or 500 with a JSON body — any non-2xx response whose body is valid JSON with a message field.

Common situations: Expired or missing auth token (401); requesting a room/session that no longer exists (404); backend validation failures (400); internal backend errors surfaced as JSON (500).

Related errors


AI-assisted analysis of m1k1o/neko@b0f01cedea (2026-09-01). Data as JSON: /api/errors/6518a6b822b97b8a. Report an issue: GitHub.