projectdiscovery/nuclei · error

grpc: dialers not initialized for executionId %q

Error message

grpc: dialers not initialized for executionId %q

What it means

An executionId was present but protocolstate.GetDialersWithId(executionID) returned nil or lacked Fastdialer: no per-execution dialer set is registered (never initialized, already torn down, or an id mismatch), so the gRPC connection cannot be routed through fastdialer and host policy.

Source

Thrown at pkg/js/libs/grpc/invoke.go:58

// (instead of gRPC's built in DNS resolver), keeping resolution and policy
// enforcement inside fastdialer.
func dialTarget(ctx context.Context, executionID, target string, cfg connConfig) (*grpc.ClientConn, error) {
	host, _, err := net.SplitHostPort(target)
	if err != nil {
		return nil, fmt.Errorf("invalid grpc target %q (expected host:port): %w", target, err)
	}
	if host == "" {
		return nil, fmt.Errorf("grpc target host cannot be empty")
	}
	if executionID == "" {
		return nil, fmt.Errorf("grpc: refusing to dial without executionId")
	}
	if !protocolstate.IsHostAllowed(executionID, host) {
		return nil, protocolstate.ErrHostDenied.Msgf(host)
	}
	dialers := protocolstate.GetDialersWithId(executionID)
	if dialers == nil || dialers.Fastdialer == nil {
		return nil, fmt.Errorf("grpc: dialers not initialized for executionId %q", executionID)
	}

	contextDialer := func(dialCtx context.Context, addr string) (net.Conn, error) {
		return dialers.Fastdialer.Dial(dialCtx, "tcp", addr)
	}

	var creds credentials.TransportCredentials
	if cfg.plaintext {
		creds = insecure.NewCredentials()
	} else {
		serverName := cfg.serverName
		if serverName == "" {
			serverName = host
		}
		creds = credentials.NewTLS(&tls.Config{
			InsecureSkipVerify: cfg.insecureSkipVerify,
			ServerName:         serverName,
			MinVersion:         tls.VersionTLS12,

View on GitHub (pinned to 265b3a3dec)

Solutions

  1. Create the grpc.Client, invoke, and Close() within a single execution lifetime
  2. Ensure protocolstate dialers are initialized for the execution before the first Connect/Invoke
  3. Do not cache clients across scans; re-create them per execution

Example fix

// before: client cached across executions, dialers of the first execution long gone
let cached;
function probe(t) {
  cached = cached || new grpc.Client(t);
  return cached.Invoke('acme.v1.Svc/Get', '{}');
}

// after: construct, use, close inside one execution
function probe(t) {
  const c = new grpc.Client(t);
  try { return c.Invoke('acme.v1.Svc/Get', '{}'); }
  finally { c.Close(); }
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
  const c = new grpc.Client(target, opts);
  try { return c.Invoke(method, msg); } finally { c.Close(); }
} catch (e) {
  if (/dialers not initialized/.test(e.message || '')) {
    // execution lifecycle bug: re-create the client inside a live execution; do not cache across scans
  }
}

Prevention

When it happens

Trigger: Client.Connect()/Invoke() racing with execution teardown; an SDK flow that sets an executionId but skips protocolstate initialization; a Client cached from a previous execution being reused after its dialers were released.

Common situations: Holding a grpc.Client in a module-level variable across template executions; embedded nuclei flows that bypass the standard engine initialization; cleanup goroutines closing dialers while reflection is still running.

Related errors


AI-assisted analysis of projectdiscovery/nuclei@265b3a3dec (2026-08-15). Data as JSON: /api/errors/aa43de3dc9521ce7. Report an issue: GitHub.