hashicorp/nomad · error

failed to send signal: %w

Error message

failed to send signal: %w

What it means

After successfully parsing the vault change_signal, the hook calls h.lifecycle.Signal(event, signal) to deliver the signal to the running task. If the task driver fails to send the signal (task already exited, driver does not support signaling), handleRenewalFailure returns this wrapped error.

Source

Thrown at client/allocrunner/taskrunner/vault_hook.go:277

	if err != nil {
		return "", 0, err
	}
	if err := h.writeToken(token); err != nil {
		return "", 0, fmt.Errorf("failed to write Vault token to disk: %w", err)
	}

	var event *structs.TaskEvent
	switch h.vaultBlock.ChangeMode {
	case structs.VaultChangeModeSignal:
		s, err := signals.Parse(h.vaultBlock.ChangeSignal)
		if err != nil {
			return "", 0, fmt.Errorf("failed to parse signal: %w", err)
		}

		event := structs.NewTaskEvent(structs.TaskSignaling).
			SetTaskSignal(s).SetDisplayMessage("Vault: new Vault token acquired")
		if err := h.lifecycle.Signal(event, h.vaultBlock.ChangeSignal); err != nil {
			return "", 0, fmt.Errorf("failed to send signal: %w", err)
		}
	case structs.VaultChangeModeRestart:
		event = structs.NewTaskEvent(structs.TaskRestartSignal).
			SetDisplayMessage("Vault: new Vault token acquired")
		h.lifecycle.Restart(ctx, event, false)
	case structs.VaultChangeModeNoop:
		// True to its name, this is a noop!
	default:
		h.logger.Error("invalid Vault change mode", "mode", h.vaultBlock.ChangeMode)
	}

	h.updater.updatedVaultToken(token)
	return token, time.Second * time.Duration(duration), nil
}

func (h *vaultHook) renewWithBackoff(ctx context.Context, token string) (time.Duration, error) {
	var attempts uint64

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Check whether the task exited before the signal was sent; fix the task's crash/exit first
  2. Use change_mode = "restart" instead of "signal" if the driver does not support signaling
  3. Verify the driver in use implements Signal (e.g. docker/exec do, some raw drivers may not)
  4. Inspect taskrunner/driver logs at the renewal time for the underlying Signal error

Example fix

// before
vault {
  change_mode   = "signal"
  change_signal = "SIGHUP"
}
// after
vault {
  change_mode = "restart"
}
Defensive patterns

Strategy: retry

Validate before calling

// ensure the driver supports signaling before using change_mode = "signal"
caps, err := driver.Capabilities()
if err != nil || !caps.Signal { /* use restart mode instead */ }

Try / catch

if err := hookSignal(); err != nil {
    if strings.Contains(err.Error(), "failed to send signal") {
        // task likely exited; wait for restart/retry once
        time.Sleep(retryDelay)
        return hookSignal()
    }
    return err
}

Prevention

When it happens

Trigger: change_mode = "signal" with a valid change_signal, a new Vault token arrives, and lifecycle.Signal returns an error — typically because the task has exited/is not running, or the driver's Signal implementation rejects the call.

Common situations: Short-lived tasks that finish before token renewal; drivers without signal support being used with change_mode=signal; task crashed and was mid-restart when the renewed token arrived.

Related errors


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