gravitational/teleport · info
disconnect escape sequence detected
Error message
disconnect escape sequence detected
What it means
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.
Source
Thrown at lib/client/escape/reader.go:40
import (
"errors"
"io"
"sync"
)
const (
readerBufferLimit = 10 * 1024 * 1024 // 10MB
// Note: on a raw terminal, "\r\n" is needed to move a cursor to the start
// of next line.
helpText = "\r\ntsh escape characters:\r\n ~? - display a list of escape characters\r\n ~. - disconnect\r\n"
)
var (
// ErrDisconnect is returned when the user has entered a disconnect
// sequence, requesting connection to be interrupted.
ErrDisconnect = errors.New("disconnect escape sequence detected")
// ErrTooMuchBufferedData is returned when the Reader's internal buffer has
// filled over 10MB. Either the consumer of Reader can't keep up with the
// data or it's entirely stuck and not consuming the data.
ErrTooMuchBufferedData = errors.New("internal buffer has grown too big")
)
// Reader is an io.Reader wrapper that catches OpenSSH-like escape sequences in
// the input stream. See NewReader for more info.
//
// Reader is safe for concurrent use.
type Reader struct {
inner io.Reader
out io.Writer
onDisconnect func(error)
bufferLimit int
// cond protects buf and err and also announces to blocked readers that
// more data is available.View on GitHub (pinned to 1283425b60)
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.
Example fix
// before
_, err := io.Copy(sessionOut, escReader)
if err != nil {
return trace.Wrap(err) // reports user disconnect as a failure
}
// after
_, err := io.Copy(sessionOut, escReader)
if errors.Is(err, escape.ErrDisconnect) {
return nil // intentional user disconnect
}
if err != nil {
return trace.Wrap(err)
} Defensive patterns
Strategy: type-guard
Type guard
func IsDisconnect(err error) bool { return errors.Is(err, escape.ErrDisconnect) } Try / catch
_, err := io.Copy(sessionOut, escReader)
if errors.Is(err, escape.ErrDisconnect) {
return nil // user-initiated disconnect; close cleanly, no error
}
if err != nil {
return trace.Wrap(err)
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- internal buffer has grown too big
- cannot route to empty target host
- updater config file not found
- executable has unstable path
- version is linked
AI-assisted analysis of gravitational/teleport@1283425b60 (2026-09-02).
Data as JSON: /api/errors/29faa550a5de6942.
Report an issue: GitHub.