derailed/k9s · warning
osc52 clipboard unavailable: stdout is not a tty or TERM=dum
Error message
osc52 clipboard unavailable: stdout is not a tty or TERM=dumb
What it means
OSC52 clipboard copy (terminal escape-sequence clipboard) was attempted, but canTryOSC52() found stdout is not a character device or TERM=dumb. OSC52 works only when output goes to a real terminal that supports the escape sequence; piped output or dumb terminals cannot host a clipboard.
Source
Thrown at internal/view/clipboard.go:91
func termValue() string {
return strings.ToLower(strings.TrimSpace(os.Getenv(termEnv)))
}
func isTTY(f *os.File) bool {
if f == nil {
return false
}
fi, err := f.Stat()
if err != nil {
return false
}
return fi.Mode()&os.ModeCharDevice != 0
}
func writeOSC52(text string) error {
if !canTryOSC52() {
return fmt.Errorf("osc52 clipboard unavailable: stdout is not a tty or TERM=dumb")
}
encoded := base64.StdEncoding.EncodeToString([]byte(text))
maxLen := osc52MaxEncodedLen()
if len(encoded) > maxLen {
return fmt.Errorf("osc52 payload exceeds encoded size limit (%d > %d)", len(encoded), maxLen)
}
term := termValue()
seq := osc52Sequence(encoded, os.Getenv(tmuxEnv) != "", strings.HasPrefix(term, screenTermPrefix))
_, err := os.Stdout.WriteString(seq)
return err
}
func osc52MaxEncodedLen() int {
v := strings.TrimSpace(os.Getenv(osc52MaxEnv))
if v == "" {View on GitHub (pinned to 2d3ccc6ba2)
Solutions
- Run in a real terminal without piping stdout; unset TERM=dumb (export TERM=xterm-256color or similar).
- Prefer the system clipboard integration: install xclip/xsel (Linux), pbcopy (macOS), or wl-copy (Wayland) so the app can shell out instead of OSC52.
- Inside tmux/screen, make sure TERM is set to the inner terminal's value (tmux-256color) so detection passes.
- If scripting, skip clipboard features: avoid the copy keybindings or run a non-interactive subcommand.
Example fix
// before: OSC52 only
if err := writeOSC52(text); err != nil { flash.Err(err) }
// after: fall back to system clipboard tool
if err := writeOSC52(text); err != nil {
if err2 := writeExternalClipboard(text); err2 != nil {
flash.Err(err2)
}
} Defensive patterns
Strategy: fallback
Validate before calling
if !canTryOSC52() {
if err := writeExternalClipboard(text); err != nil {
return fmt.Errorf("no clipboard available (osc52 and system tool both failed): %w", err)
}
return nil
}
return writeOSC52(text) Type guard
func hasTTYClipboard() bool {
if strings.TrimSpace(os.Getenv(termEnv)) == dumbTerm { return false }
fi, err := os.Stdout.Stat()
return err == nil && fi.Mode()&os.ModeCharDevice != 0
} Try / catch
if err := writeOSC52(text); err != nil {
if strings.Contains(err.Error(), "osc52 clipboard unavailable") {
copyViaSystemTool(text) // xclip/pbcopy/wl-copy fallback
}
} Prevention
- Install a system clipboard binary so the app has a non-tty path.
- Never run interactive sessions with stdout piped; redirect logs elsewhere.
- Set TERM to a real terminal value; avoid TERM=dumb.
When it happens
Trigger: Running the TUI with stdout piped/redirected (k9s | tee log), inside wrappers that allocate a pty incorrectly, or with TERM=dumb set. Also when the process is spawned by an IDE task/CI without a tty.
Common situations: CI or scripted runs invoking the binary; debugging with output redirection; environments where TERM is unset and defaults to dumb; some CI-injected shells.
Related errors
- osc52 payload exceeds encoded size limit (%d > %d)
- kubectl command must not be in the current working directory
- kubectl command is not in your path: %w
- unable to run command
AI-assisted analysis of derailed/k9s@2d3ccc6ba2 (2026-08-15).
Data as JSON: /api/errors/208aabe1a66eb890.
Report an issue: GitHub.