hashicorp/nomad · error

Unexpected task coordinator state %d

Error message

Unexpected task coordinator state %d

What it means

The String() method for coordinatorState translates the state enum to a human-readable name and panics when handed a value outside the known cases. This means the coordinator state variable was corrupted, a new state was added without updating String(), or an uninitialized/invalid state value flowed into logging or hook-label code.

Source

Thrown at client/allocrunner/tasklifecycle/coordinator.go:41

	coordinatorStatePoststop
)

func (s coordinatorState) String() string {
	switch s {
	case coordinatorStateInit:
		return "init"
	case coordinatorStatePrestart:
		return "prestart"
	case coordinatorStateMain:
		return "main"
	case coordinatorStatePoststart:
		return "poststart"
	case coordinatorStateWaitAlloc:
		return "wait_alloc"
	case coordinatorStatePoststop:
		return "poststart"
	}
	panic(fmt.Sprintf("Unexpected task coordinator state %d", s))
}

// lifecycleStage represents a lifecycle configuration used for task
// coordination.
//
// Not all possible combinations of hook X sidecar are defined, only the ones
// that are relevant for coordinating task initialization order. For example, a
// main task with sidecar set to `true` starts at the same time as a
// non-sidecar main task, so there is no need to treat them differently.
type lifecycleStage uint8

const (
	// lifecycleStagePrestartEphemeral are tasks with the "prestart" hook and
	// sidecar set to "false".
	lifecycleStagePrestartEphemeral lifecycleStage = iota

	// lifecycleStagePrestartSidecar are tasks with the "prestart" hook and
	// sidecar set to "true".

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Update to a library version where String() covers all coordinatorState values
  2. If you added a new state constant, add the corresponding case to String()
  3. Ensure coordinators are created via their constructor so state is initialized properly
  4. Avoid casting raw ints to coordinatorState; keep state assignment inside the coordinator

Example fix

// before
panic(fmt.Sprintf("Unexpected task coordinator state %d", s))
// after
default:
	return fmt.Sprintf("unknown(%d)", s) // or return error instead of panicking
Defensive patterns

Strategy: type-guard

Validate before calling

// Validate the state before formatting it
func validCoordinatorState(s coordinatorState) bool {
	switch s {
	case coordinatorStatePrestart, coordinatorStatePoststart,
		coordinatorStateWaitAlloc, coordinatorStatePoststop:
		return true
	}
	return false
}

Type guard

func isKnownCoordinatorState(s coordinatorState) bool {
	switch s {
	case coordinatorStatePrestart, coordinatorStatePoststart,
		coordinatorStateWaitAlloc, coordinatorStatePoststop:
		return true
	default:
		return false
	}
}

Try / catch

// Go: recover when rendering state for logs
func stateLabel(s coordinatorState) (str string, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("bad coordinator state: %v", r)
		}
	}()
	return s.String(), nil
}

Prevention

When it happens

Trigger: Calling String() (directly or via %s/%v formatting in logs and hook labels) with a coordinatorState value not covered by the switch — e.g., a zero-value/uninitialized coordinator, or a newly added coordinatorState constant missing a case after a library upgrade.

Common situations: Upgrading the library to a version that introduced a new coordinator state while custom code switches on state values; constructing a coordinator struct manually leaving the state field zero; custom logging that casts ints to coordinatorState.

Related errors


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