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
- Read the wrapped message (err.Error() contains '%w: <backend message>') to identify the exact backend complaint.
- Refresh or fix the auth token if the message indicates unauthorized/forbidden.
- Check that the requested resource (room, session id) still exists before calling.
- 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
- Refresh tokens proactively before expiry to avoid 401-driven backend errors.
- Validate resource existence (rooms/sessions) before API calls.
- Log the wrapped backend message for diagnosis.
- Keep error handling keyed on errors.Is(ErrBackendRespone).
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.