gohugoio/hugo · error

failed to acquire a build lock

Error message

failed to acquire a build lock: %s

What it means

create.NewContent wraps its work in a build lock (create/content.go:85 -> BaseFs.LockBuild). The lock is a file lock at <project>/.hugo_build.lock (lockedfile.MutexAt, basefs.go:489). This wraps any failure to acquire that file lock. It is skipped only when Configs.Base.NoBuildLock is set.

Solutions

  1. Ensure no other Hugo process is running against the same project (ps/pgrep for hugo).
  2. Verify write permission on the project root and on .hugo_build.lock; chmod/chown if needed.
  3. Remove a stale .hugo_build.lock file if no Hugo process is actually running.
  4. If locking is unnecessary for your workflow, set noBuildLock in the base config to disable it.

Example fix

# before: two concurrent processes fight over the lock
$ hugo server &
$ hugo new posts/x.md   # -> failed to acquire a build lock

# after: stop the server first (or set noBuildLock)
$ hugo new posts/x.md   # succeeds once the lock is free
Defensive patterns

Strategy: retry

Validate before calling

// Check the lock file is writable before attempting NewContent.
func CheckBuildLockWritable(workDir string) error {
    p := filepath.Join(workDir, ".hugo_build.lock")
    f, err := os.OpenFile(p, os.O_RDWR|os.O_CREATE, 0o644)
    if err != nil {
        return err
    }
    return f.Close()
}

Try / catch

// Retry briefly on lock contention; fail fast on permission errors.
var lastErr error
for i := 0; i < 3; i++ {
    err := create.NewContent(h, kind, path, force)
    if err == nil { return nil }
    lastErr = err
    if !strings.Contains(err.Error(), "build lock") { return err }
    // back off via a timer/channel; do not spin
}
return lastErr

Prevention

When it happens

Trigger: Another Hugo process (hugo server / hugo build / another hugo new) currently holds .hugo_build.lock; the project working dir or the lock file is not writable; a previously killed process left an OS-level lock; running on a read-only filesystem/container mount.

Common situations: Running `hugo new` concurrently with `hugo server`; CI running on a read-only checkout; stale lock after kill -9; lock file owned by another user.

Related errors


AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09). Data as JSON: /api/errors/596638430510b0e0. Report an issue: GitHub.

Appendix: source

Thrown at create/content.go:87

		sourceFs:    h.PathSpec.Fs.Source,
		ps:          h.PathSpec,
		h:           h,
		cf:          cf,

		kind:       kind,
		targetPath: targetPath,
		force:      force,
	}

	ext := paths.Ext(targetPath)

	b.setArcheTypeFilenameToUse(ext)

	withBuildLock := func() (string, error) {
		if !h.Configs.Base.NoBuildLock {
			unlock, err := h.BaseFs.LockBuild()
			if err != nil {
				return "", fmt.Errorf("failed to acquire a build lock: %s", err)
			}
			defer unlock()
		}

		if b.isDir {
			return "", b.buildDir()
		}

		if ext == "" {
			return "", fmt.Errorf("failed to resolve %q to an archetype template", targetPath)
		}

		if !h.Conf.ContentTypes().IsContentFile(b.targetPath) {
			return "", fmt.Errorf("target path %q is not a known content format", b.targetPath)
		}

		return b.buildFile()
	}

View on GitHub (pinned to 52c9bd7908)