hashicorp/nomad · error

both launch command and reattach config specified

Error message

both launch command and reattach config specified

What it means

dispensePlugin supports exactly one launch mode: spawn a command OR reattach to an existing process. Supplying both a non-empty cmd and a non-nil reattach config is contradictory and rejected with this error. It guards against ambiguous process ownership (killFn would be unclear).

Source

Thrown at helper/pluginutils/loader/loader.go:223

		return nil, fmt.Errorf("setting config for plugin %s failed: %v", id, err)
	}

	return instance, nil
}

// Reattach reattaches to a previously launched external plugin.
func (l *PluginLoader) Reattach(name, pluginType string, config *plugin.ReattachConfig) (PluginInstance, error) {
	return l.dispensePlugin(pluginType, "", "", nil, config, l.logger)
}

// dispensePlugin is used to launch or reattach to an external plugin.
func (l *PluginLoader) dispensePlugin(
	pluginType, apiVersion, cmd string, args []string, reattach *plugin.ReattachConfig,
	logger log.Logger) (PluginInstance, error) {

	var pluginCmd *exec.Cmd
	if cmd != "" && reattach != nil {
		return nil, fmt.Errorf("both launch command and reattach config specified")
	} else if cmd == "" && reattach == nil {
		return nil, fmt.Errorf("one of launch command or reattach config must be specified")
	} else if cmd != "" {
		pluginCmd = exec.Command(cmd, args...)
	}

	client := plugin.NewClient(&plugin.ClientConfig{
		HandshakeConfig:  base.Handshake,
		Plugins:          getPluginMap(pluginType, logger),
		Cmd:              pluginCmd,
		AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
		Logger:           logger,
		Reattach:         reattach,
	})

	// Connect via RPC
	rpcClient, err := client.Client()
	if err != nil {

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Pass exactly one of cmd or reattach to dispensePlugin; nil the other.
  2. When reattaching, pass reattach config with an empty cmd string.
  3. Fix state-persistence code so it records either launch info or reattach info, not both.
  4. Audit callers (Dispense, Reattach, anonymous factories) for accidental double-population of arguments.

Example fix

// before
dispensePlugin("driver", ver, exePath, args, reattachCfg, logger) // both set
// after
dispensePlugin("driver", ver, "", nil, reattachCfg, logger)
Defensive patterns

Strategy: validation

Validate before calling

func validDispenseArgs(cmd string, reattach *plugin.ReattachConfig) error {
    if cmd != "" && reattach != nil {
        return errors.New("pass either cmd or reattach, not both")
    }
    return nil
}

Try / catch

if err := dispensePlugin(pType, ver, exePath, args, reattachCfg, logger); err != nil {
    if strings.Contains(err.Error(), "both launch command and reattach") {
        return fmt.Errorf("caller bug: clear one launch mode: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling dispensePlugin with both cmd != "" and reattach != nil. From public APIs this happens if Dispense is combined with reattach logic or Reattach is called with a non-empty cmd derived from stored plugin info plus a reattach config — usually a bug in caller wiring, not user config.

Common situations: Reattach plumbing bugs where a persisted plugin command is passed alongside a ReattachConfig; tests hand-crafting dispensePlugin arguments; custom integrations resuming from state files that store both fields.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/36135cd13e7ba4f7. Report an issue: GitHub.