github/copilot-sdk · error

; stderr

Error message

%w; stderr: %s

What it means

This is the augmented form of the start-wait timeout error: after the base error (context deadline/cancellation) is created, the library appends the CLI server's captured stderr via '%w; stderr: %s'. The stderr content is the actual diagnostic; the base error only tells you the wait timed out.

Solutions

  1. Read the 'stderr: ...' suffix first — it contains the CLI's own error (bad flag, missing auth, port in use).
  2. Fix the underlying CLI issue indicated in the stderr text.
  3. If stderr is only warnings, raise the context timeout so the server can finish starting.
  4. Run the CLI manually with the same arguments to reproduce and see full output.
Defensive patterns

Strategy: try-catch

Try / catch

if err := client.Start(ctx); err != nil {
    if i := strings.Index(err.Error(), "stderr: "); i >= 0 {
        log.Errorf("CLI stderr during failed start: %s", err.Error()[i+8:])
    }
    if errors.Is(err, context.DeadlineExceeded) { /* retry with longer ctx */ }
    return err
}

Prevention

When it happens

Trigger: Context cancellation while the CLI server is starting AND the process wrote anything to stderr (warnings, panics, flag errors) before being killed.

Common situations: CLI failing fast with a startup error message while the parent's timeout also fires; CLI emitting warnings on stderr that got captured before the kill.

Related errors


AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09). Data as JSON: /api/errors/78b514afd4ea0d9f. Report an issue: GitHub.

Appendix: source

Thrown at go/client.go:2226

		}

		c.monitorProcess()

		proc := c.process
		scanner := bufio.NewScanner(stdout)
		portRegex := regexp.MustCompile(`listening on port (\d+)`)

		ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
		defer cancel()

		for {
			select {
			case <-ctx.Done():
				killErr := c.killProcess()
				baseErr := fmt.Errorf("failed waiting for CLI server to start: %w", ctx.Err())
				if buf, ok := proc.Stderr.(*truncbuffer.TruncBuffer); ok {
					if stderr := strings.TrimSpace(buf.String()); stderr != "" {
						baseErr = fmt.Errorf("%w; stderr: %s", baseErr, stderr)
					}
				}
				return errors.Join(baseErr, killErr)
			case <-c.processDone:
				killErr := c.killProcess()
				baseErr := errors.New("CLI server process exited before reporting port")
				if buf, ok := proc.Stderr.(*truncbuffer.TruncBuffer); ok {
					if stderr := strings.TrimSpace(buf.String()); stderr != "" {
						baseErr = fmt.Errorf("%w; stderr: %s", baseErr, stderr)
					}
				}
				return errors.Join(baseErr, killErr)
			default:
				if scanner.Scan() {
					line := scanner.Text()
					if matches := portRegex.FindStringSubmatch(line); len(matches) > 1 {
						port, err := strconv.Atoi(matches[1])
						if err != nil {

View on GitHub (pinned to cd8cf15dc3)