github/copilot-sdk · error

failed waiting for CLI server to start

Error message

failed waiting for CLI server to start: %w

What it means

The library waits for the spawned CLI server to print its port on stdout. If the caller's context is cancelled or times out before the port is reported, the process is killed and this error is returned, with the context's error (DeadlineExceeded or Canceled) embedded. Any captured stderr is appended to aid diagnosis.

Solutions

  1. Increase the context timeout/deadline passed to the start call.
  2. Inspect the appended 'stderr:' portion for the real startup failure (bad flag, port conflict, missing config).
  3. Ensure no caller cancels the context prematurely (e.g. request handler scopes).
  4. If the CLI is slow on first run due to download/auth, perform that warm-up outside the start context.

Example fix

// before
client.Start(ctx) // ctx with 2s deadline
// after
startCtx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
client.Start(startCtx)
Defensive patterns

Strategy: try-catch

Validate before calling

if deadline, ok := ctx.Deadline(); ok {
    if time.Until(deadline) < 30*time.Second {
        return errors.New("context deadline too short for CLI server startup")
    }
}

Try / catch

if err := client.Start(ctx); err != nil {
    if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
        // inspect embedded stderr; retry with a longer timeout
    }
    return err
}

Prevention

When it happens

Trigger: Passing a ctx that is cancelled or expires while the CLI server is still starting, e.g. a too-short context timeout around client.Start().

Common situations: Slow machine or cold-start of the CLI binary exceeding a tight deadline; HTTP request timeouts propagating their context into the start call; test suites with short timeouts.

Related errors


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

Appendix: source

Thrown at go/client.go:2223

		if err := c.process.Start(); err != nil {
			return fmt.Errorf("failed to start CLI server: %w", err)
		}

		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()

View on GitHub (pinned to cd8cf15dc3)