dagger/dagger · error

session %q not initialized

Error message

session %q not initialized

What it means

The session ID exists in the registry but its lifecycle state is still sessionStateUninitialized, meaning it has been registered but its session-level initialization has not completed. The server refuses to hand out clients from a not-yet-initialized session rather than racing with initialization.

Source

Thrown at engine/server/session.go:1125

		// where for instance the first client cancels and closes its session while others
		// are waiting on the result. In this case its safe to retry the operation again with
		// the still connected client metadata.
		err := flightcontrol.RetryableError{Err: fmt.Errorf("session %q not found", sessID)}
		return nil, err
	}

	// Gate on the session's lifecycle state via a lock-free atomic read (never
	// lifecycleMu), so this lookup can't block on a session that is initializing
	// or tearing down. A client is inserted into sess.clients only after it is
	// fully initialized, so a clientMu read can't observe a half-initialized one.
	switch st := sess.state.Load(); st {
	case sessionStateInitialized:
		// continue
	case sessionStateRemoved:
		err := flightcontrol.RetryableError{Err: fmt.Errorf("session %q not found", sessID)}
		return nil, err
	case sessionStateUninitialized:
		return nil, fmt.Errorf("session %q not initialized", sessID)
	default:
		return nil, fmt.Errorf("session %q has unknown state %s", sessID, st)
	}

	sess.clientMu.RLock()
	client, ok := sess.clients[clientID]
	sess.clientMu.RUnlock()
	if !ok {
		return nil, fmt.Errorf("client %q not found", clientID)
	}

	// Re-check state: if the session flipped to removed while we read the clients
	// map, treat it as not-found rather than handing back a client whose session
	// is tearing down. This is a lock-free atomic read, so it never blocks.
	if sess.state.Load() == sessionStateRemoved {
		return nil, flightcontrol.RetryableError{Err: fmt.Errorf("session %q not found", sessID)}
	}

View on GitHub (pinned to 82ba2681db)

Solutions

  1. Retry after a short delay — initialization is in progress and will mark the session initialized
  2. Ensure all calls for a session go through getOrInitClient, not raw lookups, when the session may be new
  3. Serialize the first call of a session in client code before fanning out concurrent operations
  4. Report as a bug if persistent — the state machine should make this window unreachable for normal clients
Defensive patterns

Strategy: retry

Validate before calling

if !sessionReady(sessID) { time.Sleep(50 * time.Millisecond) }

Type guard

func isNotInitializedErr(err error) bool { return err != nil && strings.Contains(err.Error(), "not initialized") }

Try / catch

if err := op(ctx); err != nil {
    if strings.Contains(err.Error(), "session %q not initialized") { return retryUntilInitialized(op) }
    return err
}

Prevention

When it happens

Trigger: A lookup (clientFromIDs/clientFromContext) races with getOrInitClient: another goroutine created the session entry but is still inside initializeDaggerSession; observation of the registry before initialization finishes.

Common situations: Concurrent first calls into a brand-new session where one path observes the raw session; instrumentation or internal calls that hit clientFromIDs before the main client finished connecting.

Related errors


AI-assisted analysis of dagger/dagger@82ba2681db (2026-09-05). Data as JSON: /api/errors/add0c2a9af903205. Report an issue: GitHub.