github/copilot-sdk · warning

timed out gracefully shutting down runtime after

Error message

timed out gracefully shutting down runtime after %s

What it means

CopilotClient.Stop gives the runtime a fixed window (runtimeShutdownTimeout) to shut down gracefully. If the runtime does not finish in time, Stop appends this timeout error instead of waiting indefinitely.

Solutions

  1. Ensure sessions/tool calls are completed or cancelled before Stop.
  2. Check runtime logs for what is blocking shutdown.
  3. Increase runtimeShutdownTimeout if legitimately slow, or accept the timeout and force-terminate the process.
  4. Upgrade to a runtime version with faster shutdown.

Example fix

// before
client.Stop(ctx) // times out silently-ish
// after
if err := client.Stop(ctx); err != nil {
    if strings.Contains(err.Error(), "timed out gracefully shutting down runtime") {
        process.Kill() // force-terminate hung runtime
    }
}
Defensive patterns

Strategy: try-catch

Try / catch

if err := client.Stop(ctx); err != nil && strings.Contains(err.Error(), "timed out gracefully") { process.Kill() }

Prevention

When it happens

Trigger: Calling Stop when the runtime takes longer than runtimeShutdownTimeout to respond to runtime.shutdown.

Common situations: Runtime blocked on open tool calls or slow cleanup; heavily loaded host; large session state being torn down.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


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

Appendix: source

Thrown at go/client.go:614

		rpcClient := c.RPC
		runtimeShutdownStart := time.Now()
		shutdownDone := make(chan error, 1)
		go func() {
			_, err := rpcClient.Runtime.Shutdown(context.Background())
			shutdownDone <- err
		}()

		select {
		case err := <-shutdownDone:
			if err != nil {
				c.logDebugTiming(runtimeShutdownStart, "CopilotClient.Stop runtime shutdown failed")
				errs = append(errs, fmt.Errorf("failed to gracefully shut down runtime: %w", err))
			} else {
				c.logDebugTiming(runtimeShutdownStart, "CopilotClient.Stop runtime shutdown complete")
			}
		case <-time.After(runtimeShutdownTimeout):
			c.logDebugTiming(runtimeShutdownStart, "CopilotClient.Stop runtime shutdown timed out")
			errs = append(errs, fmt.Errorf("timed out gracefully shutting down runtime after %s", runtimeShutdownTimeout))
		}
	}

	// The runtime completes all cleanup before responding to runtime.shutdown
	// and then leaves termination to us; it deliberately keeps its JSON-RPC
	// server alive to send the response and never self-exits. Waiting for a
	// self-exit that will never come just wastes time, so terminate the child
	// immediately and only wait to reap it.
	if c.process != nil && !c.isExternalServer {
		if err := c.killProcessAndWait(); err != nil {
			errs = append(errs, err)
		}
	}
	c.process = nil

	// Tear down the in-process FFI host (closes the connection and shuts down the
	// native runtime). No child process to reap in this mode.
	if c.ffiHost != nil {

View on GitHub (pinned to cd8cf15dc3)