{"record":{"id":"9395b2eebc800f25","repo":"sipeed/picoclaw","slug":"read-input-w","errorCode":null,"errorMessage":"read input: %w","messagePattern":"read input: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/picoclaw/internal/model/add.go","lineNumber":135,"sourceCode":"\treturn upsertModelDefault(opt.apiBase, opt.apiKey, opt.alias, selected, opt.stdout)\n}\n\nfunc pickModel(stdin io.Reader, stdout io.Writer, entries []modelEntry) (string, error) {\n\tfmt.Fprintf(stdout, \"\\n%d model(s) available:\\n\", len(entries))\n\tfor i, m := range entries {\n\t\tline := m.ID\n\t\tif m.Name != \"\" && m.Name != m.ID {\n\t\t\tline = fmt.Sprintf(\"%s (%s)\", m.ID, m.Name)\n\t\t}\n\t\tfmt.Fprintf(stdout, \"  %3d) %s\\n\", i+1, line)\n\t}\n\n\tscanner := bufio.NewScanner(stdin)\n\tfor {\n\t\tfmt.Fprint(stdout, \"Pick a model (number or id): \")\n\t\tif !scanner.Scan() {\n\t\t\tif err := scanner.Err(); err != nil {\n\t\t\t\treturn \"\", fmt.Errorf(\"read input: %w\", err)\n\t\t\t}\n\t\t\treturn \"\", fmt.Errorf(\"no selection provided\")\n\t\t}\n\t\ttext := strings.TrimSpace(scanner.Text())\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif idx, err := strconv.Atoi(text); err == nil {\n\t\t\tif idx < 1 || idx > len(entries) {\n\t\t\t\tfmt.Fprintf(stdout, \"Out of range. Enter 1-%d.\\n\", len(entries))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn entries[idx-1].ID, nil\n\t\t}\n\t\tfor _, m := range entries {\n\t\t\tif m.ID == text {\n\t\t\t\treturn m.ID, nil\n\t\t\t}","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/cmd/picoclaw/internal/model/add.go#L117-L153","documentation":"While the interactive model picker waited for input, bufio.Scanner over stdin returned an I/O error — not a clean EOF, which produces the separate 'no selection provided' message. The raw scanner error is wrapped with %w, so the text identifies the actual read failure. Only reachable when -m/--model is omitted, since a model id skips the picker entirely.","triggerScenarios":"The input pipe is closed or broken mid-read: an upstream process in a pipeline exiting early (head -0 | picoclaw model add), an SSH session dropping while the prompt is open, or stdin attached to a special file that errors on read.","commonSituations":"Running picoclaw model add in CI or scripts where stdin is a closed or absent pipe; piping input from a command that itself fails; terminal sessions killed during the prompt.","solutions":["Pass the model id directly: picoclaw model add -m <model-id> — no stdin needed","Interactively, run the command in a real TTY","When scripting, feed a complete answer: printf '1\\n' | picoclaw model add ...","Read the wrapped error text — it names the underlying read failure (pipe closed, I/O timeout, etc.)"],"exampleFix":"# before (CI job; stdin is a broken pipe)\n$ picoclaw model add -b https://api.example.com/v1 -k sk-...\nread input: file already closed\n\n# after\n$ picoclaw model add -b https://api.example.com/v1 -k sk-... -m gpt-4o-mini","handlingStrategy":"validation","validationCode":"func stdinIsInteractive() bool {\n  fi, err := os.Stdin.Stat()\n  return err == nil && fi.Mode()&os.ModeCharDevice != 0\n}\n\nif !stdinIsInteractive() && modelID == \"\" {\n  return fmt.Errorf(\"no TTY on stdin; pass -m <model-id> to skip the interactive picker\")\n}","typeGuard":"func isReadInputError(err error) bool {\n  return err != nil && strings.Contains(err.Error(), \"read input:\")\n}","tryCatchPattern":"if err := runAdd(opt); err != nil {\n  if isReadInputError(err) {\n    // stdin is broken: retry with -m instead of prompting again\n  }\n  return err\n}","preventionTips":["Never rely on interactive prompts in CI — always pass -m","Check that variables feeding the pipeline are set before piping into picoclaw","Run the command in a real terminal for interactive use"],"tags":["cli","model","stdin","io"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}