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

  1. Audit the error objects your handlers attach (WithLocalError, custom args) for non-JSON-serializable values (channels, funcs, cycles).
  2. Set SendDebugMessagesToClients to false in production so marshal failures don't leak internals (you'd then get the plain {"error":"server_error"} variant).
  3. Upgrade fosite — past releases fixed marshal edge cases around error serialization.
  4. 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

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


AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03). Data as JSON: /api/errors/ec7b6632a9ee5387. Report an issue: GitHub.