{"record":{"id":"4a0f46ca4b8aa2e3","repo":"gastownhall/beads","slug":"failed-to-read-confirmation-w","errorCode":null,"errorMessage":"failed to read confirmation: %w","messagePattern":"failed to read confirmation: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/doctor/fix/repo_fingerprint.go","lineNumber":148,"sourceCode":"\n\tswitch response {\n\tcase \"1\":\n\t\treturn updateRepoIDInProcess(path, beadsDir, false)\n\n\tcase \"2\":\n\t\t// Detect backend to determine what to remove\n\t\tcfg, cfgErr := configfile.Load(beadsDir)\n\t\tif cfgErr != nil || cfg == nil {\n\t\t\tcfg = configfile.DefaultConfig()\n\t\t}\n\t\tdbPath := cfg.DatabasePath(beadsDir)\n\t\tisDolt := cfg.GetBackend() == configfile.BackendDolt\n\n\t\t// Confirm before destructive action\n\t\tfmt.Printf(\"  ⚠️  This will DELETE %s. Continue? [y/N]: \", dbPath)\n\t\tconfirm, err := repoFingerprintReadLine()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to read confirmation: %w\", err)\n\t\t}\n\t\tconfirm = strings.TrimSpace(strings.ToLower(confirm))\n\t\tif confirm != \"y\" && confirm != \"yes\" {\n\t\t\tfmt.Println(\"  → Skipped (canceled)\")\n\t\t\treturn nil\n\t\t}\n\n\t\t// Remove database and reinitialize in-process\n\t\tfmt.Printf(\"  → Removing %s...\\n\", dbPath)\n\t\tif isDolt {\n\t\t\tif err := os.RemoveAll(dbPath); err != nil && !os.IsNotExist(err) {\n\t\t\t\treturn fmt.Errorf(\"failed to remove Dolt database: %w\", err)\n\t\t\t}\n\t\t} else {\n\t\t\tif err := os.Remove(dbPath); err != nil && !os.IsNotExist(err) {\n\t\t\t\treturn fmt.Errorf(\"failed to remove database: %w\", err)\n\t\t\t}\n\t\t\t_ = os.Remove(dbPath + \"-wal\")","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/doctor/fix/repo_fingerprint.go#L130-L166","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Supply answers for BOTH prompts, e.g. `printf '2\\ny\\n' | bd ...` (or 'n' to cancel safely).","Use `--yes` mode instead, which skips all prompts and takes the safe non-destructive path.","Run interactively in a real terminal so both reads succeed.","If the underlying error indicates a broken TTY, fix the terminal/SSH session and retry."],"exampleFix":"// before: pipe closes after first answer, confirmation read hits EOF\necho \"2\" | bd doctor --fix\n// after: answer both prompts, or use auto-yes mode\nprintf '2\\ny\\n' | bd doctor --fix   # or: bd doctor --fix --yes","handlingStrategy":"try-catch","validationCode":"func canPrompt() error {\n\tfi, err := os.Stdin.Stat()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif (fi.Mode() & os.ModeCharDevice) == 0 && fi.Size() == 0 {\n\t\treturn errors.New(\"stdin is empty or not interactive; use --yes\")\n\t}\n\treturn nil\n}\n// call only if canPrompt() == nil, else pass autoYes=true","typeGuard":"if errors.Is(err, io.EOF) || errors.Is(err, os.ErrClosed) {\n\t// stdin unusable: fall back to non-interactive mode\n}","tryCatchPattern":"if err := fix.RepoFingerprint(path, false); err != nil {\n\tif errors.Is(err, io.EOF) || errors.Is(err, os.ErrClosed) {\n\t\terr = fix.RepoFingerprint(path, true) // retry non-interactively\n\t}\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}","preventionTips":["Prefer autoYes=true (or --yes) in scripts, CI, and daemons — never rely on stdin availability.","Check stdin is a character device before invoking interactive commands.","When feeding answers via pipes, supply every prompt's answer plus newline in order.","Unwrap the returned error (`errors.Is/As`) to distinguish EOF vs closed vs TTY errors."],"tags":["go","stdin","confirmation-prompt","cli"],"backgroundTag":"stdin-read-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}