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
- Pass exactly one of cmd or reattach to dispensePlugin; nil the other.
- When reattaching, pass reattach config with an empty cmd string.
- Fix state-persistence code so it records either launch info or reattach info, not both.
- 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
- Structure callers so launch and reattach modes are mutually exclusive branches
- Persist either launch info or reattach info in state, never both
- Add unit tests covering each mode combination
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
- one of launch command or reattach config must be specified
- missing NodeID
- missing AllocID
- missing allocation ID
- missing AllocID
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/36135cd13e7ba4f7.
Report an issue: GitHub.