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

  1. Run in a real terminal without piping stdout; unset TERM=dumb (export TERM=xterm-256color or similar).
  2. Prefer the system clipboard integration: install xclip/xsel (Linux), pbcopy (macOS), or wl-copy (Wayland) so the app can shell out instead of OSC52.
  3. Inside tmux/screen, make sure TERM is set to the inner terminal's value (tmux-256color) so detection passes.
  4. 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

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


AI-assisted analysis of derailed/k9s@2d3ccc6ba2 (2026-08-15). Data as JSON: /api/errors/208aabe1a66eb890. Report an issue: GitHub.