{"record":{"id":"5cd901f5be9f79a4","repo":"hashicorp/nomad","slug":"taskconfig-with-id-s-already-started","errorCode":null,"errorMessage":"taskConfig with ID '%s' already started","messagePattern":"taskConfig with ID '(.+?)' already started","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"drivers/qemu/driver.go","lineNumber":458,"sourceCode":"\tif len(pluginConfigAllowList) > 0 {\n\t\tallowed := map[string]struct{}{}\n\t\tfor _, arg := range pluginConfigAllowList {\n\t\t\tallowed[arg] = struct{}{}\n\t\t}\n\t\tfor _, arg := range args {\n\t\t\tif strings.HasPrefix(strings.TrimSpace(arg), \"-\") {\n\t\t\t\tif _, ok := allowed[arg]; !ok {\n\t\t\t\t\treturn fmt.Errorf(\"%q is not in args_allowlist\", arg)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drivers.DriverNetwork, error) {\n\tif _, ok := d.tasks.Get(cfg.ID); ok {\n\t\treturn nil, nil, fmt.Errorf(\"taskConfig with ID '%s' already started\", cfg.ID)\n\t}\n\n\tvar driverConfig TaskConfig\n\n\tif err := cfg.DecodeDriverConfig(&driverConfig); err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"failed to decode driver config: %v\", err)\n\t}\n\n\t// ensure that PortMap variables are populated early on\n\tcfg.Env = taskenv.SetPortMapEnvs(cfg.Env, driverConfig.PortMap)\n\n\thandle := drivers.NewTaskHandle(taskHandleVersion)\n\thandle.Config = cfg\n\n\tif err := validateEmulator(driverConfig.Emulator, d.config.EmulatorsAllowList); err != nil {\n\t\treturn nil, nil, err\n\t}\n","sourceCodeStart":440,"sourceCodeEnd":476,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/drivers/qemu/driver.go#L440-L476","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\ndriver.StartTask(cfg) // panics with 'already started' if handle exists\n// after\nif _, err := driver.DestroyTask(cfg.ID, true); err != nil { /* log */ }\nhandle, net, err := driver.StartTask(cfg)","handlingStrategy":"try-catch","validationCode":"if _, exists := knownTaskIDs[cfg.ID]; exists {\n    return fmt.Errorf(\"task %s already started; destroy it first\", cfg.ID)\n}","typeGuard":null,"tryCatchPattern":"_, _, err := d.StartTask(cfg)\nif err != nil && strings.Contains(err.Error(), \"already started\") {\n    _ = d.DestroyTask(cfg.ID, true) // force cleanup, then retry once\n    handle, net, err = d.StartTask(cfg)\n}","preventionTips":["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"],"tags":["qemu","nomad","duplicate-task","lifecycle"],"backgroundTag":"task-already-running","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}