hashicorp/nomad · error

Invalid sentinel capability '%s'

Error message

Invalid sentinel capability '%s'

What it means

acl.Parse (acl/policy.go:710) checks each entry of the sentinel block's Capabilities list with isSentinelCapabilityValid, which only accepts deny, sentinel-read, sentinel-submit, and sentinel-delete. Any other capability string fails parsing and rejects the whole policy.

Source

Thrown at acl/policy.go:710

				return nil, fmt.Errorf("Invalid operator capability '%s'", cap)
			}
		}

		// Expand the short hand policy to the capabilities and
		// add to any existing capabilities
		if p.Operator.Policy != "" {
			extraCap := expandOperatorPolicy(p.Operator.Policy)
			p.Operator.Capabilities = append(p.Operator.Capabilities, extraCap...)
		}
	}

	if p.Sentinel != nil {
		if p.Sentinel.Policy != "" && !isPolicyValid(p.Sentinel.Policy) {
			return nil, fmt.Errorf("Invalid sentinel policy: %#v", p.Sentinel)
		}
		for _, cap := range p.Sentinel.Capabilities {
			if !isSentinelCapabilityValid(cap) {
				return nil, fmt.Errorf("Invalid sentinel capability '%s'", cap)
			}
		}

		// Expand the short hand policy to the capabilities and
		// add to any existing capabilities
		if p.Sentinel.Policy != "" {
			extraCap := expandSentinelPolicy(p.Sentinel.Policy)
			p.Sentinel.Capabilities = append(p.Sentinel.Capabilities, extraCap...)
		}
	}

	if p.Quota != nil && !isPolicyValid(p.Quota.Policy) {
		return nil, fmt.Errorf("Invalid quota policy: %#v", p.Quota)
	}

	if p.Plugin != nil && !p.Plugin.isValid() {
		return nil, fmt.Errorf("Invalid plugin policy: %#v", p.Plugin)
	}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Replace the invalid capability with one of exactly: deny, sentinel-read, sentinel-submit, sentinel-delete.
  2. Alternatively remove capabilities and use shorthand policy = "read"|"write"|"deny" (write expands to sentinel-read + sentinel-submit + sentinel-delete).
  3. The '%s' in the error names the exact rejected capability — fix that entry.

Example fix

// before
sentinel {
  capabilities = ["submit", "delete"]
}
// after
sentinel {
  capabilities = ["sentinel-submit", "sentinel-delete"]
}
Defensive patterns

Strategy: validation

Validate before calling

var validSentinelCaps = map[string]bool{"deny": true, "sentinel-read": true,
    "sentinel-submit": true, "sentinel-delete": true}
if policy.Sentinel != nil {
    for _, c := range policy.Sentinel.Capabilities {
        if !validSentinelCaps[c] {
            return fmt.Errorf("invalid sentinel capability %q", c)
        }
    }
}

Type guard

func isSentinelCapabilityValid(cap string) bool {
    switch cap {
    case "deny", "sentinel-read", "sentinel-submit", "sentinel-delete":
        return true
    }
    return false
}

Try / catch

_, err := acl.Parse(rules, acl.PolicyParseStrict)
if err != nil {
    if strings.Contains(err.Error(), "Invalid sentinel capability") {
        // prefix bare names with 'sentinel-' or use the policy shorthand
    }
    return err
}

Prevention

When it happens

Trigger: acl.Parse on a policy where sentinel { capabilities = [...] } contains an unrecognized string such as 'read', 'submit', 'sentinel-write', or a namespace capability name.

Common situations: Dropping the 'sentinel-' prefix (writing 'read'/'submit' instead of 'sentinel-read'/'sentinel-submit'); assuming a 'sentinel-write' exists; generating policies from generic ACL templates that don't know the sentinel capability vocabulary.

Related errors


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