github/copilot-sdk · error

CLI server process exited before reporting port

Error message

CLI server process exited before reporting port

What it means

While waiting for the CLI server to report its port, the process terminated on its own (processDone channel closed). The library surfaces a base error that is enriched with captured stderr (if any) and joined with the kill error. This indicates the CLI server crashed or exited early during startup.

Solutions

  1. Inspect the joined stderr in the returned error to find the process's own failure message
  2. Verify the runtime binary path/COPILOT_CLI_PATH points to a working, version-compatible executable
  3. Check for sandbox/memory limits killing the child process and increase them
  4. Retry startup; if persistent, reinstall or rebuild the runtime bundle
Defensive patterns

Strategy: retry

Validate before calling

if _, err := os.Stat(cliPath); err != nil {
    return fmt.Errorf("CLI server binary not found at %s", cliPath)
}

Try / catch

if err := client.Start(ctx); err != nil {
    if strings.Contains(err.Error(), "exited before reporting port") {
        log.Errorf("CLI server failed to start: %v", err) // stderr is embedded
        // inspect stderr / retry once with backoff
    }
}

Prevention

When it happens

Trigger: Starting a Client whose CLI server subprocess dies before printing its listening port — bad binary, missing runtime assets, fatal startup error in stderr, port/binding failure.

Common situations: Incompatible or corrupt copilot binary; runtime.node missing; the process being OOM-killed or killed by a sandbox; a stale/incompatible binary version after an upgrade.

Related errors


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

Appendix: source

Thrown at go/client.go:2232

		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 {
							killErr := c.killProcess()
							return errors.Join(fmt.Errorf("failed to parse port: %w", err), killErr)
						}
						c.actualPort = port
						return nil
					}

View on GitHub (pinned to cd8cf15dc3)