ory/hydra · error
server_error
server_error
Error message
{"error":"server_error","error_description":"%s"} What it means
In fosite's writeJsonError, if json.Marshal of the RFC6749 error object itself fails, the handler cannot emit the normal JSON error body. When GetSendDebugMessagesToClients is enabled, it returns a hand-built {"error":"server_error","error_description":"<marshal error>"} with status 500. This is a last-resort path; Marshal of fosite's error struct essentially never fails, so encountering it signals a deeply unexpected internal condition or a customized Config/Error implementation with unmarshalable fields.
Source
Thrown at fosite/access_error.go:33
f.writeJsonError(ctx, rw, req, err)
}
func (f *Fosite) writeJsonError(ctx context.Context, rw http.ResponseWriter, requester Requester, err error) {
rw.Header().Set("Content-Type", "application/json;charset=UTF-8")
rw.Header().Set("Cache-Control", "no-store")
rw.Header().Set("Pragma", "no-cache")
rfcerr := ErrorToRFC6749Error(err).WithLegacyFormat(f.Config.GetUseLegacyErrorFormat(ctx)).WithExposeDebug(f.Config.GetSendDebugMessagesToClients(ctx))
if requester != nil {
rfcerr = rfcerr.WithLocalizer(f.Config.GetMessageCatalog(ctx), getLangFromRequester(requester))
}
js, err := json.Marshal(rfcerr)
if err != nil {
if f.Config.GetSendDebugMessagesToClients(ctx) {
errorMessage := EscapeJSONString(err.Error())
http.Error(rw, fmt.Sprintf(`{"error":"server_error","error_description":"%s"}`, errorMessage), http.StatusInternalServerError)
} else {
http.Error(rw, `{"error":"server_error"}`, http.StatusInternalServerError)
}
return
}
rw.WriteHeader(rfcerr.CodeField)
// ignoring the error because the connection is broken when it happens
_, _ = rw.Write(js)
}
View on GitHub (pinned to 4174065ffb)
Solutions
- Audit the error objects your handlers attach (WithLocalError, custom args) for non-JSON-serializable values (channels, funcs, cycles).
- Set SendDebugMessagesToClients to false in production so marshal failures don't leak internals (you'd then get the plain {"error":"server_error"} variant).
- Upgrade fosite — past releases fixed marshal edge cases around error serialization.
- Reproduce with a wrapper: temporarily marshal the same rfcerr in a test to capture the underlying error message.
Example fix
// before (custom grant handler attaching unserializable data)
ctx = fosite.WithRequestContext(ctx, myReq.WithSession(session{Done: make(chan int)}))
// after
ctx = fosite.WithRequestContext(ctx, myReq.WithSession(session{UserID: userID})) Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-flight: ensure your error/session values serialize.
if _, err := json.Marshal(myCustomErrorArgs); err != nil {
log.Printf("custom error payload not serializable: %v", err)
} Type guard
func jsonSafe(v any) bool {
_, err := json.Marshal(v)
return err == nil
} Try / catch
// Library-internal path; guard at the handler boundary:
if err := nextHandler(ctx, rw, req); err != nil {
if jsonSafe(err) {
log.Printf("request error: %v", err)
}
// serialize your own RFC6749 body instead of relying on fosite's fallback
writeRFC6749Error(rw, err, http.StatusInternalServerError)
} Prevention
- Never attach channels, funcs, mutexes, or contexts to fosite error args
- Set SendDebugMessagesToClients=false in production to avoid leaking marshal errors
- Add unit tests marshaling every custom error type your handlers produce
- Keep fosite updated — internal marshal paths should be unreachable
When it happens
Trigger: json.Marshal(rfcerr) returns an error inside writeJsonError (fosite/access_error.go:33), reached via WriteAccessError or WriteIntrospectionError — e.g. a custom RichError/args value containing channels, funcs, or cyclic references placed in the error's internal fields, or a custom Config whose debug-message wiring injected unsupported data.
Common situations: Custom error types attached to requests by custom grant handlers carrying non-serializable context; debugging enabled (SendDebugMessagesToClients: true) so the raw marshal error leaks to clients; embedding requests via form round-trips (RestoreRequest) that corrupted error metadata.
Understand the failure class
Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.
Related errors
- err.Error()
- failed to set token lifespans due to failed client type asse
- a secret for signing HMAC-SHA512/256 is expected to be defin
- Session must be of type JWTSessionContainer but got type: %T
- GetTokenClaims() must not be nil
AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03).
Data as JSON: /api/errors/ec7b6632a9ee5387.
Report an issue: GitHub.