{"record":{"id":"427bc3d9bd9ad807","repo":"gravitational/teleport","slug":"an-unknown-error-has-occurred","errorCode":null,"errorMessage":"an unknown error has occurred","messagePattern":"an unknown error has occurred","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"lib/web/desktop.go","lineNumber":185,"sourceCode":"\t\treturn trace.Wrap(err)\n\t}\n\treturn trace.Wrap(w.conn.WriteMessage(websocket.BinaryMessage, data))\n}\n\n// implements handshaker for legacy TDP clients\n// TODO(rhammonds) DELETE IN v20.0.0\ntype tdpHandshaker struct {\n\tconnection tdp.MessageReadWriter\n\twithheld   []tdp.Message\n\tscreenSpec legacy.ClientScreenSpec\n\t// May or may not be nil. Not all web client versions will send a keyboard layout.\n\tkeyboardLayout *legacy.ClientKeyboardLayout\n}\n\nfunc (t *tdpHandshaker) sendError(ctx context.Context, log *slog.Logger, err error) error {\n\tif err == nil {\n\t\tlog.WarnContext(ctx, \"SendError called with empty message\")\n\t\terr = errors.New(\"an unknown error has occurred\")\n\t}\n\n\treturn trace.Wrap(t.connection.WriteMessage(&legacy.Alert{\n\t\tMessage:  err.Error(),\n\t\tSeverity: legacy.SeverityError,\n\t}))\n}\n\nfunc (t *tdpHandshaker) getPromptBuilder(log *slog.Logger) mfaPromptBuilder {\n\treturn legacy.NewTDPMFAPrompt(t.connection, &t.withheld, log)\n}\n\nfunc (t *tdpHandshaker) performInitialHandshake(ctx context.Context, log *slog.Logger) error {\n\tmsg, err := t.connection.ReadMessage()\n\tif err != nil {\n\t\treturn trace.Wrap(err)\n\t}\n","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/gravitational/teleport/blob/1283425b60ec5f60d509ba4c791183d452923ff7/lib/web/desktop.go#L167-L203","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nif someCondition {\n    return hs.sendError(ctx, log, err) // err may be nil\n}\n// after\nif someCondition {\n    if err == nil {\n        err = trace.Errorf(\"handshake failed\")\n    }\n    return hs.sendError(ctx, log, err)\n}","handlingStrategy":"try-catch","validationCode":"// never pass a nil error to sendError\nif err == nil {\n    err = trace.Errorf(\"desktop handshake failed\")\n}","typeGuard":"func nonNilErr(err error) error { if err == nil { return trace.Errorf(\"unknown handshake failure\") }; return err }","tryCatchPattern":"if err := hs.handshake(ctx); err != nil {\n    return hs.sendError(ctx, log, nonNilErr(err))\n}","preventionTips":["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."],"tags":["desktop-access","websocket","protocol"],"backgroundTag":"unknown-error-fallback","analyzedSha":"1283425b60ec5f60d509ba4c791183d452923ff7","analyzedAt":"2026-09-02T04:06:41.601Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}