sipeed/picoclaw · error

read input: %w

Error message

read input: %w

What it means

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.

Source

Thrown at cmd/picoclaw/internal/model/add.go:135

	return upsertModelDefault(opt.apiBase, opt.apiKey, opt.alias, selected, opt.stdout)
}

func pickModel(stdin io.Reader, stdout io.Writer, entries []modelEntry) (string, error) {
	fmt.Fprintf(stdout, "\n%d model(s) available:\n", len(entries))
	for i, m := range entries {
		line := m.ID
		if m.Name != "" && m.Name != m.ID {
			line = fmt.Sprintf("%s (%s)", m.ID, m.Name)
		}
		fmt.Fprintf(stdout, "  %3d) %s\n", i+1, line)
	}

	scanner := bufio.NewScanner(stdin)
	for {
		fmt.Fprint(stdout, "Pick a model (number or id): ")
		if !scanner.Scan() {
			if err := scanner.Err(); err != nil {
				return "", fmt.Errorf("read input: %w", err)
			}
			return "", fmt.Errorf("no selection provided")
		}
		text := strings.TrimSpace(scanner.Text())
		if text == "" {
			continue
		}
		if idx, err := strconv.Atoi(text); err == nil {
			if idx < 1 || idx > len(entries) {
				fmt.Fprintf(stdout, "Out of range. Enter 1-%d.\n", len(entries))
				continue
			}
			return entries[idx-1].ID, nil
		}
		for _, m := range entries {
			if m.ID == text {
				return m.ID, nil
			}

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Pass the model id directly: picoclaw model add -m <model-id> — no stdin needed
  2. Interactively, run the command in a real TTY
  3. When scripting, feed a complete answer: printf '1\n' | picoclaw model add ...
  4. Read the wrapped error text — it names the underlying read failure (pipe closed, I/O timeout, etc.)

Example fix

# before (CI job; stdin is a broken pipe)
$ picoclaw model add -b https://api.example.com/v1 -k sk-...
read input: file already closed

# after
$ picoclaw model add -b https://api.example.com/v1 -k sk-... -m gpt-4o-mini
Defensive patterns

Strategy: validation

Validate before calling

func stdinIsInteractive() bool {
  fi, err := os.Stdin.Stat()
  return err == nil && fi.Mode()&os.ModeCharDevice != 0
}

if !stdinIsInteractive() && modelID == "" {
  return fmt.Errorf("no TTY on stdin; pass -m <model-id> to skip the interactive picker")
}

Type guard

func isReadInputError(err error) bool {
  return err != nil && strings.Contains(err.Error(), "read input:")
}

Try / catch

if err := runAdd(opt); err != nil {
  if isReadInputError(err) {
    // stdin is broken: retry with -m instead of prompting again
  }
  return err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/9395b2eebc800f25. Report an issue: GitHub.