{"record":{"id":"6a9df5a13fc5018a","repo":"tailscale/tailscale","slug":"logger-closed","errorCode":null,"errorMessage":"logger closed","messagePattern":"logger closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"ssh/tailssh/tailssh.go","lineNumber":1673,"sourceCode":"\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tj = append(j, '\\n')\n\t\tif err := w.writeCastLine(j); err != nil {\n\t\t\tif !w.r.failOpen {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\tw.recordingFailedOpen = true\n\t\t}\n\t}\n\treturn w.w.Write(p)\n}\n\nfunc (w loggingWriter) writeCastLine(j []byte) error {\n\tw.r.mu.Lock()\n\tdefer w.r.mu.Unlock()\n\tif w.r.out == nil {\n\t\treturn errors.New(\"logger closed\")\n\t}\n\t_, err := w.r.out.Write(j)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"logger Write: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc envValFromList(env []string, wantKey string) (v string) {\n\tfor _, kv := range env {\n\t\tif thisKey, v, ok := strings.Cut(kv, \"=\"); ok && envEq(thisKey, wantKey) {\n\t\t\treturn v\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// envEq reports whether environment variable a == b for the current","sourceCodeStart":1655,"sourceCodeEnd":1691,"githubUrl":"https://github.com/tailscale/tailscale/blob/cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042/ssh/tailssh/tailssh.go#L1655-L1691","documentation":"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.","triggerScenarios":"Session stdout/stderr writes racing recording Close: the child process or SSH channel delivers output after the recording's Close set out to nil.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"n, err := w.Write(data)\nif err != nil && strings.Contains(err.Error(), \"logger closed\") {\n\t// output raced recording close; drop, session data unaffected\n\treturn n, nil\n}","preventionTips":["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"],"tags":["ssh","session-recording","concurrency","shutdown"],"backgroundTag":null,"analyzedSha":"cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042","analyzedAt":"2026-08-15T19:58:31.583Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}