gravitational/teleport · warning
an unknown error has occurred
Error message
an unknown error has occurred
What it means
Fallback message produced by tdpHandshaker.sendError (lib/web/desktop.go:185). When the desktop access handshake needs to report an error to the client but the error value is nil (a bug — sendError was called with nothing to send), it substitutes the placeholder 'an unknown error has occurred', logs a warning, and writes a legacy Alert message to the client connection.
Source
Thrown at lib/web/desktop.go:185
return trace.Wrap(err)
}
return trace.Wrap(w.conn.WriteMessage(websocket.BinaryMessage, data))
}
// implements handshaker for legacy TDP clients
// TODO(rhammonds) DELETE IN v20.0.0
type tdpHandshaker struct {
connection tdp.MessageReadWriter
withheld []tdp.Message
screenSpec legacy.ClientScreenSpec
// May or may not be nil. Not all web client versions will send a keyboard layout.
keyboardLayout *legacy.ClientKeyboardLayout
}
func (t *tdpHandshaker) sendError(ctx context.Context, log *slog.Logger, err error) error {
if err == nil {
log.WarnContext(ctx, "SendError called with empty message")
err = errors.New("an unknown error has occurred")
}
return trace.Wrap(t.connection.WriteMessage(&legacy.Alert{
Message: err.Error(),
Severity: legacy.SeverityError,
}))
}
func (t *tdpHandshaker) getPromptBuilder(log *slog.Logger) mfaPromptBuilder {
return legacy.NewTDPMFAPrompt(t.connection, &t.withheld, log)
}
func (t *tdpHandshaker) performInitialHandshake(ctx context.Context, log *slog.Logger) error {
msg, err := t.connection.ReadMessage()
if err != nil {
return trace.Wrap(err)
}
View on GitHub (pinned to 1283425b60)
Solutions
- Fix the caller so a non-nil error is always passed to sendError; find where the real error is discarded upstream.
- Check server logs for the 'SendError called with empty message' warning to locate the offending call site.
- Re-run the desktop session with debug logging to capture the actual handshake failure before it is replaced.
Example fix
// before
if someCondition {
return hs.sendError(ctx, log, err) // err may be nil
}
// after
if someCondition {
if err == nil {
err = trace.Errorf("handshake failed")
}
return hs.sendError(ctx, log, err)
} Defensive patterns
Strategy: try-catch
Validate before calling
// never pass a nil error to sendError
if err == nil {
err = trace.Errorf("desktop handshake failed")
} Type guard
func nonNilErr(err error) error { if err == nil { return trace.Errorf("unknown handshake failure") }; return err } Try / catch
if err := hs.handshake(ctx); err != nil {
return hs.sendError(ctx, log, nonNilErr(err))
} Prevention
- Audit call sites of sendError for possible nil errors.
- Always wrap intermediate failures with trace.Errorf instead of returning bare nil.
- Log the underlying error before sending the alert so it is not lost.
When it happens
Trigger: Calling sendError on tdpHandshaker with err == nil during the TDP desktop handshake; usually caused by a code path swallowing the real error before reaching sendError.
Common situations: Desktop access sessions failing to handshake where the underlying error was lost (nil return from an intermediate step); misused API in custom integrations of the desktop handshake code.
Related errors
- failed to fetch MySQL version
- xsession was terminated
- failed to create decoder
- decoder not initialized
- invalid resize dimensions
AI-assisted analysis of gravitational/teleport@1283425b60 (2026-09-02).
Data as JSON: /api/errors/427bc3d9bd9ad807.
Report an issue: GitHub.