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).

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.

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