hashicorp/nomad · error

handle cannot be nil

Error message

handle cannot be nil

What it means

The mock driver's RecoverTask restores a task from a drivers.TaskHandle persisted across agent restarts. A nil handle carries no state to restore, so the driver rejects the call immediately. This is a defensive check against a programming error in the caller (usually the client/runtime), not an environment problem.

Source

Thrown at drivers/mock/driver.go:361

	if !d.shutdownFingerprintTime.IsZero() && time.Now().After(d.shutdownFingerprintTime) {
		health = drivers.HealthStateUndetected
		desc = "disabled"
	} else {
		health = drivers.HealthStateHealthy
		attrs["driver.mock_driver"] = pstructs.NewBoolAttribute(true)
		desc = drivers.DriverHealthy
	}

	return &drivers.Fingerprint{
		Attributes:        attrs,
		Health:            health,
		HealthDescription: desc,
	}
}

func (d *Driver) RecoverTask(handle *drivers.TaskHandle) error {
	if handle == nil {
		return fmt.Errorf("handle cannot be nil")
	}

	// Unmarshall the driver state and create a new handle
	var taskState MockTaskState
	if err := handle.GetDriverState(&taskState); err != nil {
		d.logger.Error("failed to decode task state from handle", "error", err, "task_id", handle.Config.ID)
		return fmt.Errorf("failed to decode task state from handle: %v", err)
	}

	taskState.Command.parseDurations()
	if taskState.ExecCommand != nil {
		taskState.ExecCommand.parseDurations()
	}

	// Correct the run_for time based on how long it has already been running
	now := time.Now()
	if !taskState.StartedAt.IsZero() {
		taskState.Command.runForDuration = taskState.Command.runForDuration - now.Sub(taskState.StartedAt)

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Ensure the caller obtains the handle from the driver's task store/persisted state and only calls RecoverTask with a non-nil *drivers.TaskHandle.
  2. Guard the call site: skip or log when handle == nil before invoking RecoverTask.
  3. If this happens during agent restore, check that task state was correctly persisted and restored upstream.

Example fix

// before
if err := d.RecoverTask(handles[i]); err != nil {...}
// after
if handles[i] == nil {
    continue // nothing to recover
}
if err := d.RecoverTask(handles[i]); err != nil {...}
Defensive patterns

Strategy: validation

Validate before calling

if handle == nil {
    return errors.New("cannot recover task: handle is nil")
}
err := driver.RecoverTask(handle)

Type guard

func recoverable(h *drivers.TaskHandle) bool { return h != nil && h.Config != nil }

Try / catch

if err := d.RecoverTask(h); err != nil {
    if err.Error() == "handle cannot be nil" {
        return errors.New("caller bug: nil handle passed to RecoverTask")
    }
    return err
}

Prevention

When it happens

Trigger: Calling Driver.RecoverTask(nil) on drivers/mock_driver, e.g. during plugin reattach/restore when the handle was never created or was lost and not re-checked for nil.

Common situations: Custom Nomad forks or test harnesses restoring drivers after agent restart with a corrupt/empty task store; unit tests invoking RecoverTask directly with a nil handle.

Related errors


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