hashicorp/nomad · error

error changing owner/group: %w

Error message

error changing owner/group: %w

What it means

This error wraps a failure of os.Chown during the plugin Create operation on non-Windows platforms, after mkdir and chmod succeeded. Nomad attempts to set the requested uid/gid on the directory; on failure it removes the half-created directory (RemoveAll) and returns this wrapped error. The usual wrapped cause is EPERM: the nomad client process lacks the privileges (root or CAP_CHOWN) to change ownership to the requested uid/gid.

Source

Thrown at client/hostvolumemanager/host_volume_plugin.go:160

	if err != nil {
		log.Error("error setting directory permission mode", "error", err)
		return nil, fmt.Errorf("error setting directory permission mode: %w", err)
	}

	if runtime.GOOS != "windows" {
		// Chown note: A uid or gid of -1 means to not change that value.
		if err = os.Chown(path, params.Uid, params.Gid); err != nil {
			log.Error("error changing owner/group", "error", err, "uid", params.Uid, "gid", params.Gid)

			// Failing to change ownership is fatal for this plugin. Since we have
			// already created the directory, we should attempt to clean it.
			// Otherwise, the operator must do this manually.
			if err := os.RemoveAll(path); err != nil {
				log.Error("failed to remove directory after create failure",
					"error", err)
			}

			return nil, fmt.Errorf("error changing owner/group: %w", err)
		}
	}

	log.Debug("plugin ran successfully")
	return resp, nil
}

func decodeMkdirParams(in map[string]string) (HostVolumePluginMkdirParams, error) {
	// default values if their associated keys are not in the input map
	out := HostVolumePluginMkdirParams{
		Mode: 0o700, // "0700"
		Uid:  -1,    // this default translates to "do not set" in os.Chown
		Gid:  -1,    // ditto
	}
	var err error

	for param, val := range in {
		switch param {

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Run the nomad client as root or grant it CAP_CHOWN (setcap cap_chown+eip on the agent binary / run agent privileged) so chown of arbitrary uids succeeds.
  2. Request uid/gid values the agent user can set — either omit uid/gid (default to agent user) or align them with the agent's own uid/gid.
  3. Set client option to allow unprivileged chown is not possible: alternatively pre-create the directory with correct ownership outside Nomad and create the volume over it.
  4. Check the wrapped error for EPERM vs EINVAL to distinguish privilege vs argument problems.

Example fix

// before: agent runs as 'nomad' user, cannot chown
Uid: 1000, Gid: 1000  // EPERM

// after: run client with capability
// sudo setcap cap_chown+eip /usr/bin/nomad  (or run agent as root)
// or omit ownership:
Uid: -1, Gid: -1
Defensive patterns

Strategy: try-catch

Validate before calling

func canChown(uid, gid int) error {
    if uid == -1 && gid == -1 {
        return nil
    }
    if os.Geteuid() == 0 {
        return nil
    }
    if uid != -1 && uid != os.Geteuid() {
        return fmt.Errorf("running as uid %d; cannot chown to %d without CAP_CHOWN", os.Geteuid(), uid)
    }
    if gid != -1 && gid != os.Getegid() {
        return fmt.Errorf("running as gid %d; cannot chgrp to %d without CAP_CHOWN", os.Getegid(), gid)
    }
    return nil
}

Try / catch

var hvErr *hvapi.Error
if errors.As(err, &hvErr) && strings.Contains(hvErr.Error(), "error changing owner/group") {
    if errors.Is(err, fs.ErrPermission) {
        // run agent as root / grant CAP_CHOWN, or drop uid/gid from the request
    }
}

Prevention

When it happens

Trigger: Plugin Create runs on Linux/Unix with nonzero params.Uid or params.Gid while the nomad agent runs as an unprivileged user: os.Chown(path, params.Uid, params.Gid) returns EPERM. Also fires on EINVAL if uid/gid are out of range, or if the path vanished between chmod and chown.

Common situations: Operator requests a volume owned by uid 1000/gid 1000 but the nomad client agent runs as the 'nomad' user without root; containers/nomad running without CAP_CHOWN; requesting a uid that only root may set.

Related errors


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