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 {
returnView on GitHub (pinned to 0d69dca6f5)
Solutions
- Provide stdin (e.g. echo y | d2 ...) when an interactive prompt is expected
- Use InitPlaywright() directly for non-interactive/automated environments — the library already skips the prompt when CI env var is set
- Set the CI environment variable to bypass the prompt
- 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
- Set CI=1 in all non-interactive environments
- Pipe input explicitly when scripting (echo y | cmd)
- Prefer InitPlaywright in headless services
- Check TTY availability before prompting flows
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
- no color stops in gradient
- cannot read a directory
- %s is not a supported format. Supported formats are: %s
- failed to fully compile (partial render written) %s: %w
- failed to compile %s: %w
AI-assisted analysis of d2lang/d2@0d69dca6f5 (2026-08-31).
Data as JSON: /api/errors/300006e38c398423.
Report an issue: GitHub.