hashicorp/nomad · error

unable to marshal ACL token: %w

Error message

unable to marshal ACL token: %w

What it means

encodeACLToken wraps a json.Marshal failure when serializing a *consulapi.ACLToken before it is base64-encoded and written to disk. Marshal of a plain struct essentially only fails on unsupported values (e.g. invalid fields produced by custom marshalers) or an out-of-memory-ish encoder error, so this is rare. It guarantees the caller setConsulTokens gets a wrapped, identifiable error instead of the bare json error.

Source

Thrown at client/allocrunner/consul_hook.go:361

func decodeACLToken(b64ACLToken string, token *consulapi.ACLToken) error {
	decodedBytes, err := base64.StdEncoding.DecodeString(b64ACLToken)
	if err != nil {
		return fmt.Errorf("unable to process ACLToken: %w", err)
	}

	if len(decodedBytes) != 0 {
		if err := json.Unmarshal(decodedBytes, token); err != nil {
			return fmt.Errorf("unable to unmarshal ACLToken: %w", err)
		}
	}

	return nil
}

func encodeACLToken(token *consulapi.ACLToken) (string, error) {
	jsonBytes, err := json.Marshal(token)
	if err != nil {
		return "", fmt.Errorf("unable to marshal ACL token: %w", err)
	}

	return base64.StdEncoding.EncodeToString(jsonBytes), nil
}

// This function will never return nil, even in case of error
func (rs *resourcesBackend) loadAllocTokens() (map[string]map[string]*consulapi.ACLToken, error) {
	allocTokens := map[string]map[string]*consulapi.ACLToken{}

	ts, err := rs.db.GetAllocConsulACLTokens(rs.allocID)
	if err != nil {
		return allocTokens, err
	}

	var mErr *multierror.Error
	for _, st := range ts {

		token := &consulapi.ACLToken{}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Inspect the wrapped json error to identify which field of the ACLToken cannot be marshaled
  2. Re-fetch the token from Consul to get a well-formed *consulapi.ACLToken instead of reusing the local one
  3. Verify the token came from a supported Consul API version compatible with the Nomad build
  4. Upgrade Nomad/Consul API library if the struct was modified or versions are mismatched

Example fix

// before
token := deriveTokenSomewhere()
enc, err := encodeACLToken(token)

// after: validate the token before encoding
if token == nil || token.SecretID == "" {
	return fmt.Errorf("invalid ACL token: missing SecretID")
}
enc, err := encodeACLToken(token)
Defensive patterns

Strategy: validation

Validate before calling

if token == nil {
	return errors.New("ACL token is nil; cannot encode")
}
if _, err := json.Marshal(token); err != nil {
	return fmt.Errorf("token not encodable: %w", err)
}

Type guard

func isEncodableToken(t *consulapi.ACLToken) bool {
	_, err := json.Marshal(t)
	return t != nil && err == nil
}

Try / catch

enc, err := encodeACLToken(token)
if err != nil {
	if strings.Contains(err.Error(), "marshal ACL token") {
		// re-fetch token from Consul instead of reusing the local object
	}
	return err
}

Prevention

When it happens

Trigger: setConsulToken(s) receives a *consulapi.ACLToken and calls encodeACLToken; json.Marshal(token) returns a non-nil error (e.g. json: unsupported type resulting from a corrupted/nil-embedded token struct or a custom field type without a marshaler).

Common situations: Programmatic callers constructing an ACLToken with unusual types; an accessor returning a nil/corrupt token from Consul's response; running patched Nomad builds that changed the ACLToken struct.

Understand the failure class

Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.

Related errors


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