restic/restic · warning

write failed: %w

Error message

write failed: %w

What it means

PosixClearCurrentLine failed to write its ANSI escape sequence (cursor-home + clear-line) to the progress-display writer. This is status-line cosmetics for interactive terminals; the wrapped error is virtually always EPIPE (reader gone) or ENOSPC/EIO when output is redirected.

Source

Thrown at internal/terminal/terminal_posix.go:26

const (
	// PosixControlMoveCursorHome moves cursor to the first column
	PosixControlMoveCursorHome = "\r"
	// PosixControlMoveCursorUp moves cursor up one line
	PosixControlMoveCursorUp = "\x1b[1A"
	// PosixControlMoveCursorDown moves cursor down one line
	PosixControlMoveCursorDown = "\x1b[1B"
	// PosixControlClearLine clears the current line
	PosixControlClearLine = "\x1b[2K"
)

// PosixClearCurrentLine removes all characters from the current line and resets the
// cursor position to the first column.
func PosixClearCurrentLine(wr io.Writer, _ uintptr) error {
	// clear current line
	_, err := wr.Write([]byte(PosixControlMoveCursorHome + PosixControlClearLine))
	if err != nil {
		return fmt.Errorf("write failed: %w", err)
	}
	return nil
}

// PosixMoveCursorUp moves the cursor to the line n lines above the current one.
func PosixMoveCursorUp(wr io.Writer, _ uintptr, n int) error {
	data := []byte(PosixControlMoveCursorHome)
	data = append(data, bytes.Repeat([]byte(PosixControlMoveCursorUp), n)...)
	_, err := wr.Write(data)
	if err != nil {
		return fmt.Errorf("write failed: %w", err)
	}
	return nil
}

// PosixMoveCursorDown moves the cursor to the line n lines below the current one.
func PosixMoveCursorDown(wr io.Writer, _ uintptr, n int) error {
	data := []byte(PosixControlMoveCursorHome)

View on GitHub (pinned to a80be1478a)

Solutions

  1. Keep the downstream reader alive for the whole run, or redirect to a file instead of a pipe
  2. Free disk space when redirecting to a full filesystem
  3. Use --json (or quiet mode) in scripts to disable in-place status rendering entirely
  4. Treat it as non-fatal if your own tooling consumed enough output — the backup itself usually continues

Example fix

# before
restic backup /data | head -5
# Error: write failed: broken pipe

# after (file redirect or quiet mode in scripts)
restic backup /data --quiet >> /var/log/restic.log 2>&1
Defensive patterns

Strategy: try-catch

Validate before calling

// only render interactive status when the writer is a real terminal
if term.IsTerminal(int(os.Stdout.Fd())) {
    _ = terminal.PosixClearCurrentLine(os.Stdout, fd)
}

Try / catch

if err := terminal.PosixClearCurrentLine(w, fd); err != nil {
    if errors.Is(err, syscall.EPIPE) || errors.Is(err, syscall.ENOSPC) {
        return // cosmetic: drop progress rendering, keep the operation running
    }
    return err
}

Prevention

When it happens

Trigger: restic's stdout is piped into a program that exits early (head, grep -m1) so writes hit a closed pipe; output redirected to a file on a full disk; terminal window closed while a long operation renders progress.

Common situations: Piping restic through head in scripts; killing the pager but not restic; /tmp or log volume full when redirecting; CI capturing output with small buffers that close early.

Related errors


AI-assisted analysis of restic/restic@a80be1478a (2026-08-15). Data as JSON: /api/errors/d15303113fdb15b8. Report an issue: GitHub.