hashicorp/nomad · error
unable to move to %q - alloc dir is not built
Error message
unable to move to %q - alloc dir is not built
What it means
AllocDir.Move relocates a previous allocation's shared data and task-local directories into a new allocation directory. The struct enforces the invariant that Build() was called first: moving into an unbuilt alloc dir returns this error immediately. Previously this was an implicit assumption; now it is explicit validation.
Source
Thrown at client/allocdir/alloc_dir.go:282
// the snapshotting side closed the connect
// prematurely and won't try to use the tar
// anyway.
a.logger.Warn("snapshotting failed and unable to write error marker", "error", writeErr)
}
return fmt.Errorf("failed to snapshot %s: %w", path, err)
}
}
return nil
}
// Move other alloc directory's shared path and local dir to this alloc dir.
func (a *AllocDir) Move(other Interface, tasks []*structs.Task) error {
a.mu.RLock()
if !a.built {
// Enforce the invariant that Build is called before Move
a.mu.RUnlock()
return fmt.Errorf("unable to move to %q - alloc dir is not built", a.AllocDir)
}
// Moving is slow and only reads immutable fields, so unlock during heavy IO
a.mu.RUnlock()
// Move the data directory
otherDataDir := filepath.Join(other.ShareDirPath(), SharedDataDir)
dataDir := filepath.Join(a.SharedDir, SharedDataDir)
if fileInfo, err := os.Stat(otherDataDir); fileInfo != nil && err == nil {
os.Remove(dataDir) // remove an empty data dir if it exists
if err := os.Rename(otherDataDir, dataDir); err != nil {
return fmt.Errorf("error moving data dir: %w", err)
}
}
// Move the task directories
for _, task := range tasks {
otherTaskDir := filepath.Join(other.AllocDirPath(), task.Name)View on GitHub (pinned to 482b49bf1a)
Solutions
- Ensure AllocDir.Build is called (and succeeds) before Move
- Check logs for an earlier Build failure and fix its root cause (disk full, permissions)
- Re-run the allocation restore so the client rebuilds the dir
- In tests, call Build before Move
Example fix
// before
allocDir.Move(oldAllocDir, tasks) // allocDir never built
// after
if err := allocDir.Build(); err != nil { return err }
return allocDir.Move(oldAllocDir, tasks) Defensive patterns
Strategy: validation
Validate before calling
if !a.built { return errors.New("call Build() before Move()") } // replicate the invariant in caller code Type guard
func built(d *allocdir.AllocDir) bool { return d != nil } // ensure Build succeeded before Move; track build error explicitly Try / catch
if err := allocDir.Move(old, tasks); err != nil && strings.Contains(err.Error(), "alloc dir is not built") {
return fmt.Errorf("restore failed: build alloc dir first: %w", err)
} Prevention
- Always call Build immediately after NewAllocDir and check its error
- Never swallow Build errors upstream in restore paths
- In tests, assert Build was invoked before Move
When it happens
Trigger: Calling Move (as the client runner does during allocation restoration/migration) on an AllocDir whose Build was skipped, failed earlier, or whose built flag was reset — e.g. after a Build error was swallowed upstream.
Common situations: Allocation restore paths where directory build failed silently; tests or custom code constructing AllocDir manually without Build; upgrades where build invariants changed.
Related errors
- error moving data dir: %w
- error creating task %q dir: %w
- error moving task %q local dir: %w
- error building alloc dir for previous alloc %q: %w
- unable to read rooted allocation directory
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/9345cf8d773223f2.
Report an issue: GitHub.