hashicorp/nomad · error

unexpected TTL type: %v

Error message

unexpected TTL type: %v

What it means

This error is returned by the SetahlTTL-style mapstructure decode hook / UnmarshalJSON helper in HashiCorp Nomad's ACL token struct when the TTL value supplied for an ACL token (e.g. a client token introduction/expiration TTL) arrives as a JSON/encoding type other than the accepted string-with-duration or float64 form. The decoder switches on the Go type of the raw value; float64 is cast to time.Duration nanoseconds, but any other type (bool, nil, string handled elsewhere) falls through to the default branch. It signals that the caller sent a structurally invalid TTL value for the ACL API.

Source

Thrown at nomad/structs/acl.go:2530

		*Alias
	}{
		Alias: (*Alias)(a),
	}
	if err = json.Unmarshal(data, &aux); err != nil {
		return err
	}
	if aux.TTL != nil {
		switch v := aux.TTL.(type) {
		case string:
			if v != "" {
				if a.TTL, err = time.ParseDuration(v); err != nil {
					return err
				}
			}
		case float64:
			a.TTL = time.Duration(v)
		default:
			return fmt.Errorf("unexpected TTL type: %v", v)
		}
	}
	return nil
}

// ACLCreateClientIntroductionTokenResponse is the response object used within the ACL
// client introduction RPC handler.
type ACLCreateClientIntroductionTokenResponse struct {

	// JWT is the signed identity token that can be used as an introduction
	// token for a new client node to register with the Nomad cluster.
	JWT string
}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Inspect the payload sent to the ACL API and set the TTL field to a valid duration string (e.g. "30s") or a plain JSON number.
  2. Check for template/config generation bugs that emit the wrong JSON type (bool/null/object) for the TTL key.
  3. Upgrade the Nomad client/SDK so the decode hook matches the server's accepted TTL formats.
  4. If writing custom tooling, parse the value into time.Duration before submitting it.

Example fix

// before
{ "Name": "deploy", "Type": "client", "TTL": true }
// after
{ "Name": "deploy", "Type": "client", "TTL": "30s" }
Defensive patterns

Strategy: type-guard

Validate before calling

if v, ok := rawTTL.(string); !ok && !isJSONNumber(rawTTL) {
    return fmt.Errorf("TTL must be a duration string or number, got %T", rawTTL)
}

Type guard

func validTTL(v interface{}) bool {
    switch v.(type) {
    case string, float64, int64, int:
        return true
    default:
        return false
    }
}

Try / catch

if err := token.Validate(); err != nil {
    if strings.Contains(err.Error(), "unexpected TTL type") {
        // reject payload, log the offending field type and re-serialize with a duration string
    }
    return err
}

Prevention

When it happens

Trigger: Calling the ACL token creation/introduction API (or feeding a Nomad config/JSON blob through structs.DecodeACLToken-style decoding) with a TTL field typed as something other than an accepted duration representation - e.g. a boolean, null, nested object, or array. Typically seen when an automation tool emits the wrong JSON type for the TTL field.

Common situations: Terraform/CLI tooling generating JSON with a wrong-typed TTL (e.g. "ttl": true or an object); template rendering producing quoted numbers where the decoder path doesn't accept strings; SDK clients marshaling Go ints (not float64) into a hook that only handles float64 after JSON decoding.

Related errors


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