gastownhall/beads · error

failed to read confirmation: %w

Error message

failed to read confirmation: %w

What it means

When the user chooses option [2] (reinitialize database), `RepoFingerprint` asks for a final confirmation before deleting the database file, reading the reply with `repoFingerprintReadLine()`. If that second stdin read fails, the destructive path is aborted and the underlying error is wrapped as `failed to read confirmation: %w`. Because the confirmation guards a DELETE, the error path deliberately performs no changes.

Source

Thrown at cmd/bd/doctor/fix/repo_fingerprint.go:148

	switch response {
	case "1":
		return updateRepoIDInProcess(path, beadsDir, false)

	case "2":
		// Detect backend to determine what to remove
		cfg, cfgErr := configfile.Load(beadsDir)
		if cfgErr != nil || cfg == nil {
			cfg = configfile.DefaultConfig()
		}
		dbPath := cfg.DatabasePath(beadsDir)
		isDolt := cfg.GetBackend() == configfile.BackendDolt

		// Confirm before destructive action
		fmt.Printf("  ⚠️  This will DELETE %s. Continue? [y/N]: ", dbPath)
		confirm, err := repoFingerprintReadLine()
		if err != nil {
			return fmt.Errorf("failed to read confirmation: %w", err)
		}
		confirm = strings.TrimSpace(strings.ToLower(confirm))
		if confirm != "y" && confirm != "yes" {
			fmt.Println("  → Skipped (canceled)")
			return nil
		}

		// Remove database and reinitialize in-process
		fmt.Printf("  → Removing %s...\n", dbPath)
		if isDolt {
			if err := os.RemoveAll(dbPath); err != nil && !os.IsNotExist(err) {
				return fmt.Errorf("failed to remove Dolt database: %w", err)
			}
		} else {
			if err := os.Remove(dbPath); err != nil && !os.IsNotExist(err) {
				return fmt.Errorf("failed to remove database: %w", err)
			}
			_ = os.Remove(dbPath + "-wal")

View on GitHub (pinned to 71377f2769)

Solutions

  1. Supply answers for BOTH prompts, e.g. `printf '2\ny\n' | bd ...` (or 'n' to cancel safely).
  2. Use `--yes` mode instead, which skips all prompts and takes the safe non-destructive path.
  3. Run interactively in a real terminal so both reads succeed.
  4. If the underlying error indicates a broken TTY, fix the terminal/SSH session and retry.

Example fix

// before: pipe closes after first answer, confirmation read hits EOF
echo "2" | bd doctor --fix
// after: answer both prompts, or use auto-yes mode
printf '2\ny\n' | bd doctor --fix   # or: bd doctor --fix --yes
Defensive patterns

Strategy: try-catch

Validate before calling

func canPrompt() error {
	fi, err := os.Stdin.Stat()
	if err != nil {
		return err
	}
	if (fi.Mode() & os.ModeCharDevice) == 0 && fi.Size() == 0 {
		return errors.New("stdin is empty or not interactive; use --yes")
	}
	return nil
}
// call only if canPrompt() == nil, else pass autoYes=true

Type guard

if errors.Is(err, io.EOF) || errors.Is(err, os.ErrClosed) {
	// stdin unusable: fall back to non-interactive mode
}

Try / catch

if err := fix.RepoFingerprint(path, false); err != nil {
	if errors.Is(err, io.EOF) || errors.Is(err, os.ErrClosed) {
		err = fix.RepoFingerprint(path, true) // retry non-interactively
	}
	if err != nil {
		log.Fatal(err)
	}
}

Prevention

When it happens

Trigger: User answered '2' at the choice prompt, then the confirmation read fails: stdin hit EOF (input pipe exhausted after the first answer), non-interactive environment (CI, /dev/null stdin), or terminal I/O error between the two prompts.

Common situations: Scripting the interactive flow with `echo 2 | bd ...` — the pipe delivers '2' but is at EOF for the confirmation read; automated harnesses that answer only the first prompt; dropping the terminal connection after the first answer.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/4a0f46ca4b8aa2e3. Report an issue: GitHub.