pulumi/pulumi · error

analyzer has not had handshake called

Error message

analyzer has not had handshake called

What it means

The analyzer gRPC server requires that the Handshake RPC be called before any other RPC so it can learn the engine address and root directory. ConfigureStack is called before the handshake state is stored (srv.handshake == nil), meaning the engine and the analyzer plugin are speaking out of order or a non-Pulumi caller invoked the RPC directly. The library throws this to fail fast rather than constructing a context with a missing EngineAddress.

Source

Thrown at sdk/go/pulumi/policyx/server.go:277

			EnforcementLevel: pulumirpc.EnforcementLevel(p.EnforcementLevel()),
			ConfigSchema:     configSchema,
		})
	}
	return &pulumirpc.AnalyzerInfo{
		Name:           srv.policyPack.Name(),
		Version:        srv.policyPack.Version().String(),
		Policies:       policies,
		SupportsConfig: true,
		InitialConfig:  nil, /* TODO */
	}, nil
}

func (srv *analyzerServer) ConfigureStack(ctx context.Context,
	req *pulumirpc.AnalyzerStackConfigureRequest) (
	*pulumirpc.AnalyzerStackConfigureResponse, error,
) {
	if srv.handshake == nil {
		return nil, errors.New("analyzer has not had handshake called")
	}

	root := ""
	if srv.handshake.RootDirectory != nil {
		root = *srv.handshake.RootDirectory
	}

	info := pulumi.RunInfo{
		Stack:        req.Stack,
		Project:      req.Project,
		Organization: req.Organization,

		RootDirectory: root,

		MonitorAddr: "",
		EngineAddr:  srv.handshake.EngineAddress,

		Config:           req.Config,

View on GitHub (pinned to 793f7b2e16)

Solutions

  1. Upgrade the Pulumi CLI/engine so Handshake is always sent before AnalyzerStackConfigure
  2. If you invoke the analyzer server yourself, call Handshake first and pass its result (EngineAddress, RootDirectory) before ConfigureStack
  3. In tests, construct the analyzer via analyzerServe/handshake flow instead of calling srv.ConfigureStack directly

Example fix

// before
srv.ConfigureStack(ctx, req) // srv.handshake == nil
// after
hs, err := srv.Handshake(ctx, &pulumirpc.AnalyzerHandshakeRequest{EngineAddress: engineAddr})
if err != nil { return err }
// now ConfigureStack can proceed
srv.ConfigureStack(ctx, req)
Defensive patterns

Strategy: validation

Validate before calling

// when driving the analyzer directly
if srvHandshake == nil {
    return fmt.Errorf("call Handshake before ConfigureStack")
}
_ = srv.ConfigureStack(ctx, req)

Type guard

func handshakeDone(h *pulumirpc.AnalyzerHandshakeRequest) bool { return h != nil }

Try / catch

if err := srv.ConfigureStack(ctx, req); err != nil {
    if strings.Contains(err.Error(), "has not had handshake called") {
        // re-run Handshake then retry
    }
}

Prevention

When it happens

Trigger: Calling AnalyzerStackConfigure (ConfigureStack) on analyzerServer before Handshake has populated srv.handshake — e.g. a custom driver invoking RPCs in the wrong order, an outdated engine that does not send Handshake first, or a hand-rolled gRPC client/test that skips Handshake.

Common situations: Running an older `pulumi` CLI against a newer policy pack SDK that requires handshake; writing integration tests that call ConfigureStack directly; proxying or replaying analyzer RPC traffic out of order.

Understand the failure class

Related errors


AI-assisted analysis of pulumi/pulumi@793f7b2e16 (2026-08-31). Data as JSON: /api/errors/92f0841dc8899bcd. Report an issue: GitHub.