tailscale/tailscale · warning
logger closed
Error message
logger closed
What it means
loggingWriter.writeCastLine serializes writes to a recording with r.mu and writes asciicast lines to r.out. After the recording has been closed, r.out is nil, and any late Write from a still-running session process returns 'logger closed'. It marks the point where session output arrived after the recording was finalized.
Source
Thrown at ssh/tailssh/tailssh.go:1673
if err != nil {
return 0, err
}
j = append(j, '\n')
if err := w.writeCastLine(j); err != nil {
if !w.r.failOpen {
return 0, err
}
w.recordingFailedOpen = true
}
}
return w.w.Write(p)
}
func (w loggingWriter) writeCastLine(j []byte) error {
w.r.mu.Lock()
defer w.r.mu.Unlock()
if w.r.out == nil {
return errors.New("logger closed")
}
_, err := w.r.out.Write(j)
if err != nil {
return fmt.Errorf("logger Write: %w", err)
}
return nil
}
func envValFromList(env []string, wantKey string) (v string) {
for _, kv := range env {
if thisKey, v, ok := strings.Cut(kv, "="); ok && envEq(thisKey, wantKey) {
return v
}
}
return ""
}
// envEq reports whether environment variable a == b for the currentView on GitHub (pinned to cfe32b8be6)
Solutions
- Ensure the session output pump is stopped (session ctx done, process exited) before closing the recording
- Treat this error as non-fatal for session data: the session continues; log it for diagnostics
- Upgrade tailscaled — ordering fixes around recording close land periodically
Defensive patterns
Strategy: try-catch
Try / catch
n, err := w.Write(data)
if err != nil && strings.Contains(err.Error(), "logger closed") {
// output raced recording close; drop, session data unaffected
return n, nil
} Prevention
- Order teardown: stop session output pumps, then close the recording
- Treat 'logger closed' as diagnostic, not fatal, for the session stream
- Report reproducible late-write races upstream with session logs
When it happens
Trigger: Session stdout/stderr writes racing recording Close: the child process or SSH channel delivers output after the recording's Close set out to nil.
Common situations: Long or chatty sessions at teardown; bugs where the recording is closed before the session output pump goroutine stops; tests that close recordings while emitters still run.
Related errors
- listener closed
- server is shutting down
- no recorders configured
- recording: unexpected EOF
- did not receive ack frames from the recorder in 30s
AI-assisted analysis of tailscale/tailscale@cfe32b8be6 (2026-08-15).
Data as JSON: /api/errors/6a9df5a13fc5018a.
Report an issue: GitHub.