d2lang/d2 · error

failed to read user input: %w

Error message

failed to read user input: %w

What it means

InitPlaywrightWithPrompt reads the user's confirmation from stdin before installing Chromium; if bufio.Reader.ReadString('\n') returns an error (most commonly EOF because stdin is closed or not a terminal), the error is wrapped with this message. It exists so interactive consent cannot silently fail.

Source

Thrown at lib/png/png.go:112

	return startPlaywright(pw)
}

func InitPlaywrightWithPrompt() (Playwright, error) {
	if os.Getenv("CI") != "" {
		return InitPlaywright()
	}

	// Just try running first. This only works if drivers and browsers are already installed
	pw, err := playwright.Run()
	if err == nil {
		return startPlaywright(pw)
	}

	fmt.Print("D2 needs to install Chromium v149.0.7827.55 to render non-SVG images. Continue? (y/N): ")
	reader := bufio.NewReader(os.Stdin)
	response, err := reader.ReadString('\n')
	if err != nil {
		return Playwright{}, fmt.Errorf("failed to read user input: %w", err)
	}
	response = strings.TrimSpace(strings.ToLower(response))
	if response != "y" && response != "yes" {
		return Playwright{}, fmt.Errorf("chromium installation cancelled by user")
	}

	return InitPlaywright()
}

type timeoutSetter interface {
	SetDefaultTimeout(float64)
	SetDefaultNavigationTimeout(float64)
}

func configureTimeout(target timeoutSetter) {
	seconds, ok := env.Timeout()
	if !ok {
		return

View on GitHub (pinned to 0d69dca6f5)

Solutions

  1. Provide stdin (e.g. echo y | d2 ...) when an interactive prompt is expected
  2. Use InitPlaywright() directly for non-interactive/automated environments — the library already skips the prompt when CI env var is set
  3. Set the CI environment variable to bypass the prompt
  4. Check the wrapped cause (%w) — EOF usually means no interactive session

Example fix

// before
d2 file.d2 out.png  # hangs/fails waiting on stdin in CI
// after
CI=1 d2 file.d2 out.png  # skips prompt, installs/uses chromium non-interactively
Defensive patterns

Strategy: fallback

Validate before calling

stat, _ := os.Stdin.Stat()
interactive := (stat.Mode() & os.ModeCharDevice) != 0
if !interactive { os.Setenv("CI", "1") }

Type guard

func stdinIsTTY() bool { st, _ := os.Stdin.Stat(); return (st.Mode() & os.ModeCharDevice) != 0 }

Try / catch

pw, err := InitPlaywrightWithPrompt()
if err != nil {
    if strings.Contains(err.Error(), "failed to read user input") {
        return fallbackToSVG() // no interactive stdin
    }
    return err
}

Prevention

When it happens

Trigger: Calling InitPlaywrightWithPrompt() with stdin closed, redirected from an empty stream, or EOF reached before a newline; ReadString returning any non-nil error.

Common situations: Running in CI or scripts where stdin is /dev/null; piping commands without input; background services with no TTY.

Related errors


AI-assisted analysis of d2lang/d2@0d69dca6f5 (2026-08-31). Data as JSON: /api/errors/300006e38c398423. Report an issue: GitHub.