projectdiscovery/nuclei · error

grpc: dialers not initialized for executionId

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.

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.

Appendix: 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)