hashicorp/nomad · error

post-run hook failed

Error message

post-run hook %q failed: %w

What it means

During alloc shutdown, each post-run hook's Postrun() is executed and any error is accumulated into a multierror wrapped as "post-run hook \"<name>\" failed: <cause>". This indicates cleanup (e.g. directory cleanup, network teardown) failed while stopping the allocation.

Solutions

  1. Identify the failing hook from the message and the wrapped cause
  2. Check for leftover processes holding files/mounts in the alloc directory (lsof/grep mounts) and kill them
  3. Manually clean stale alloc dirs under the client data_dir if safe, and verify client disk/permissions
  4. Check CNI plugin health if the failure is network teardown; upgrade/fix plugins and garbage-collect (nomad system gc)
Defensive patterns

Strategy: try-catch

Try / catch

if err := stopAlloc(allocID); err != nil {
  if strings.Contains(err.Error(), "post-run hook") {
    // cleanup failed: inspect alloc dir mounts and CNI, then garbage collect
    nomadSystemGC()
  }
}

Prevention

When it happens

Trigger: Run() → postrun after task runners stop; any hook with Postrun() errors, e.g. alloc dir cleanup hook failing to remove directories or network isolation teardown failing.

Common situations: Files inside alloc_dir held open or mounted (won't unmount/remove) by leftover processes; permission errors on cleanup; CNI network teardown failure; disk I/O errors on the client host.

Related errors


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

Appendix: source

Thrown at client/allocrunner/alloc_runner_hooks.go:286

		}()
	}

	var merr multierror.Error
	for _, hook := range ar.runnerHooks {
		post, ok := hook.(interfaces.RunnerPostrunHook)
		if !ok {
			continue
		}

		name := post.Name()
		var start time.Time
		if ar.logger.IsTrace() {
			start = time.Now()
			ar.logger.Trace("running post-run hook", "name", name, "start", start)
		}

		if err := post.Postrun(); err != nil {
			merr.Errors = append(merr.Errors, fmt.Errorf("post-run hook %q failed: %w", name, err))
		}

		if ar.logger.IsTrace() {
			end := time.Now()
			ar.logger.Trace("finished post-run hooks", "name", name, "end", end, "duration", end.Sub(start))
		}
	}

	return helper.FlattenMultierror(merr.ErrorOrNil())
}

// destroy is used to run the runners destroy hooks. All hooks are run and
// errors are returned as a multierror.
func (ar *allocRunner) destroy() error {
	if ar.logger.IsTrace() {
		start := time.Now()
		ar.logger.Trace("running destroy hooks", "start", start)
		defer func() {

View on GitHub (pinned to 482b49bf1a)