hashicorp/nomad · error
taskConfig with ID '%s' already started
Error message
taskConfig with ID '%s' already started
What it means
StartTask guards against starting two task instances with the same task ID: it first checks the driver's in-memory task handle store (d.tasks.Get(cfg.ID)), and if a handle already exists it refuses to start a duplicate. This keeps driver state consistent — each task ID maps to exactly one running QEMU process and handle.
Source
Thrown at drivers/qemu/driver.go:458
if len(pluginConfigAllowList) > 0 {
allowed := map[string]struct{}{}
for _, arg := range pluginConfigAllowList {
allowed[arg] = struct{}{}
}
for _, arg := range args {
if strings.HasPrefix(strings.TrimSpace(arg), "-") {
if _, ok := allowed[arg]; !ok {
return fmt.Errorf("%q is not in args_allowlist", arg)
}
}
}
}
return nil
}
func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drivers.DriverNetwork, error) {
if _, ok := d.tasks.Get(cfg.ID); ok {
return nil, nil, fmt.Errorf("taskConfig with ID '%s' already started", cfg.ID)
}
var driverConfig TaskConfig
if err := cfg.DecodeDriverConfig(&driverConfig); err != nil {
return nil, nil, fmt.Errorf("failed to decode driver config: %v", err)
}
// ensure that PortMap variables are populated early on
cfg.Env = taskenv.SetPortMapEnvs(cfg.Env, driverConfig.PortMap)
handle := drivers.NewTaskHandle(taskHandleVersion)
handle.Config = cfg
if err := validateEmulator(driverConfig.Emulator, d.config.EmulatorsAllowList); err != nil {
return nil, nil, err
}
View on GitHub (pinned to 482b49bf1a)
Solutions
- Call DestroyTask for the conflicting task ID (or wait for it to finish) before retrying StartTask.
- If the task is truly gone, recover the allocation (nomad alloc stop / client restart) so the driver's task map is cleared.
- Generate a fresh allocation (new ID) rather than reusing the old task ID.
Example fix
// before
driver.StartTask(cfg) // panics with 'already started' if handle exists
// after
if _, err := driver.DestroyTask(cfg.ID, true); err != nil { /* log */ }
handle, net, err := driver.StartTask(cfg) Defensive patterns
Strategy: try-catch
Validate before calling
if _, exists := knownTaskIDs[cfg.ID]; exists {
return fmt.Errorf("task %s already started; destroy it first", cfg.ID)
} Try / catch
_, _, err := d.StartTask(cfg)
if err != nil && strings.Contains(err.Error(), "already started") {
_ = d.DestroyTask(cfg.ID, true) // force cleanup, then retry once
handle, net, err = d.StartTask(cfg)
} Prevention
- Always call DestroyTask before reusing a task ID
- Track started task IDs in your orchestrator layer
- Handle client restarts with RecoverTask instead of blind StartTask retries
- Avoid allocation ID reuse across retries
When it happens
Trigger: Calling StartTask with a drivers.TaskConfig whose ID matches a task previously started by this driver instance and not yet destroyed; also occurs on client restarts/recovery paths where a handle was restored but the task still occupies the driver's task map.
Common situations: Retrying a failed allocation without DestroyTask succeeding first; duplicate allocation IDs after a scheduler race; recovering a client where the handle exists but the QEMU process was assumed dead.
Related errors
- QEMU graceful shutdown is unsupported on the Windows platfor
- users: release of unused uid/gid
- pre-run hook %q failed: %v
- update hook %q failed: %v
- plugin group already shutdown
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/5cd901f5be9f79a4.
Report an issue: GitHub.