{"record":{"id":"29faa550a5de6942","repo":"gravitational/teleport","slug":"disconnect-escape-sequence-detected","errorCode":null,"errorMessage":"disconnect escape sequence detected","messagePattern":"disconnect escape sequence detected","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"info","filePath":"lib/client/escape/reader.go","lineNumber":40,"sourceCode":"\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"sync\"\n)\n\nconst (\n\treaderBufferLimit = 10 * 1024 * 1024 // 10MB\n\n\t// Note: on a raw terminal, \"\\r\\n\" is needed to move a cursor to the start\n\t// of next line.\n\thelpText = \"\\r\\ntsh escape characters:\\r\\n  ~? - display a list of escape characters\\r\\n  ~. - disconnect\\r\\n\"\n)\n\nvar (\n\t// ErrDisconnect is returned when the user has entered a disconnect\n\t// sequence, requesting connection to be interrupted.\n\tErrDisconnect = errors.New(\"disconnect escape sequence detected\")\n\t// ErrTooMuchBufferedData is returned when the Reader's internal buffer has\n\t// filled over 10MB. Either the consumer of Reader can't keep up with the\n\t// data or it's entirely stuck and not consuming the data.\n\tErrTooMuchBufferedData = errors.New(\"internal buffer has grown too big\")\n)\n\n// Reader is an io.Reader wrapper that catches OpenSSH-like escape sequences in\n// the input stream. See NewReader for more info.\n//\n// Reader is safe for concurrent use.\ntype Reader struct {\n\tinner        io.Reader\n\tout          io.Writer\n\tonDisconnect func(error)\n\tbufferLimit  int\n\n\t// cond protects buf and err and also announces to blocked readers that\n\t// more data is available.","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/gravitational/teleport/blob/1283425b60ec5f60d509ba4c791183d452923ff7/lib/client/escape/reader.go#L22-L58","documentation":"ErrDisconnect is returned by lib/client/escape/Reader when the user types the OpenSSH-style disconnect escape sequence '~.' at the start of a line during an interactive session. It signals that the user requested the connection be interrupted; the reader sets it as its error and the session consumer should tear down the connection cleanly.","triggerScenarios":"reader.go:156 — the Reader buffered a '~' escape character and the next byte is '.', completing the disconnect sequence; this is delivered both as a read error and (as in reader_test.go:145) as the disconnect error to the session handler.","commonSituations":"Users pressing '~.' intending to type a tilde (e.g. in vim or a shell) at the beginning of a line during tsh SSH sessions; deliberate disconnection from a hung session.","solutions":["Treat errors.Is(err, escape.ErrDisconnect) as an intentional, user-initiated disconnect — close the session without reporting a failure.","If the user typed it accidentally, simply reconnect; nothing is corrupted.","Use '~?' to display the escape help text and '~' twice to send a literal tilde.","In automated consumers, propagate the disconnect error to the session teardown path rather than retrying."],"exampleFix":"// before\n_, err := io.Copy(sessionOut, escReader)\nif err != nil {\n    return trace.Wrap(err) // reports user disconnect as a failure\n}\n// after\n_, err := io.Copy(sessionOut, escReader)\nif errors.Is(err, escape.ErrDisconnect) {\n    return nil // intentional user disconnect\n}\nif err != nil {\n    return trace.Wrap(err)\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func IsDisconnect(err error) bool { return errors.Is(err, escape.ErrDisconnect) }","tryCatchPattern":"_, err := io.Copy(sessionOut, escReader)\nif errors.Is(err, escape.ErrDisconnect) {\n    return nil // user-initiated disconnect; close cleanly, no error\n}\nif err != nil {\n    return trace.Wrap(err)\n}","preventionTips":["Educate users: '~.' disconnects; '~?' shows escape help; '~~' sends a literal tilde","In session consumers, always map ErrDisconnect to a clean teardown, not a failure","Never retry the connection automatically after ErrDisconnect"],"tags":["interactive-session","escape-sequence","ssh","sentinel-error"],"backgroundTag":"user-requested-disconnect","analyzedSha":"1283425b60ec5f60d509ba4c791183d452923ff7","analyzedAt":"2026-09-02T04:06:41.601Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}