docker/compose · critical

restoring env for %q: %w

Error message

restoring env for %q: %w

What it means

To keep public OTEL init functions from reading OS env vars, traceClientFromDockerContext temporarily unsets all OTEL_* variables and restores them afterwards in a defer. If os.Setenv fails while restoring a key, the function panics with 'restoring env for %q: %w'. This is a hard crash, not a returned error, and it can leave the env half-restored.

Source

Thrown at internal/tracing/docker_context.go:55

func traceClientFromDockerContext(dockerCli command.Cli, otelEnv envMap) (otlptrace.Client, error) {
	// attempt to extract an OTEL config from the Docker context to enable
	// automatic integration with Docker Desktop;
	cfg, err := ConfigFromDockerContext(dockerCli.ContextStore(), dockerCli.CurrentContext())
	if err != nil {
		return nil, fmt.Errorf("loading otel config from docker context metadata: %w", err)
	}

	if cfg.Endpoint == "" {
		return nil, nil
	}

	// HACK: unfortunately _all_ public OTEL initialization functions
	// 	implicitly read from the OS env, so temporarily unset them all and
	// 	restore afterwards
	defer func() {
		for k, v := range otelEnv {
			if err := os.Setenv(k, v); err != nil {
				panic(fmt.Errorf("restoring env for %q: %w", k, err))
			}
		}
	}()
	for k := range otelEnv {
		if err := os.Unsetenv(k); err != nil {
			return nil, fmt.Errorf("stashing env for %q: %w", k, err)
		}
	}

	conn, err := grpc.NewClient(cfg.Endpoint,
		grpc.WithContextDialer(memnet.DialEndpoint),
		// this dial is restricted to using a local Unix socket / named pipe,
		// so there is no need for TLS
		grpc.WithTransportCredentials(insecure.NewCredentials()),
	)
	if err != nil {
		return nil, fmt.Errorf("initializing otel connection from docker context metadata: %w", err)
	}

View on GitHub (pinned to ddc4b044b6)

Solutions

  1. Check the wrapped %w (typically OOM-related) and address memory pressure / raise limits for the compose process
  2. Eliminate concurrent env mutation: do not run code that sets OTEL_* vars in parallel with startup
  3. If seen in tests, serialize env-touching tests or unset OTEL_* before invoking compose APIs
  4. Upgrade compose — a panic here is a last-resort guard, and newer builds may harden this path
Defensive patterns

Strategy: try-catch

Validate before calling

null // a panic in library code cannot be pre-validated

Try / catch

// wrap InitTracing in a recover at the top-level CLI boundary; log and continue without tracing rather than crashing the run
 defer func() { if r := recover(); r != nil { log.Warnf("tracing init crashed: %v", r) } }()

Prevention

When it happens

Trigger: os.Setenv(k, v) failing during the deferred restore loop: process out of memory allocating the new env string, or (on Windows) a concurrent env mutation race; reachable only after an OTEL endpoint was configured via Docker context and the client init path executed.

Common situations: Extreme memory pressure at tracing init; rare platform-level env failures; tests that mutate the environment from parallel goroutines while compose initializes tracing.

Related errors


AI-assisted analysis of docker/compose@ddc4b044b6 (2026-08-15). Data as JSON: /api/errors/dcbdb3611eea1b7f. Report an issue: GitHub.