hashicorp/terraform · error

must use lowercase hex digits

Error message

must use lowercase hex digits

What it means

Returned by addrs.ParseDeposedKey when the raw string is exactly 8 characters and decodes as hex, but contains uppercase letters (raw != strings.ToLower(raw)). DeposedKey is canonicalised to lowercase, so any uppercase input is rejected. Returns the zero key "00000000" on failure.

Solutions

  1. Lowercase the input before calling ParseDeposedKey (strings.ToLower), or supply lowercase to begin with.
  2. Display deposed keys in lowercase in your UI to prevent round-trip breakage.
  3. Validate with ^[0-9a-f]{8}$ (lowercase only) to surface a clear error early.

Example fix

// before
k, err := addrs.ParseDeposedKey(input) // input = "A1B2C3D4"

// after
k, err := addrs.ParseDeposedKey(strings.ToLower(input))
Defensive patterns

Strategy: validation

Validate before calling

raw := strings.ToLower(strings.TrimSpace(input))
if !regexp.MustCompile(`^[0-9a-f]{8}$`).MatchString(raw) {
    return fmt.Errorf("deposed key must be 8 lowercase hex digits")
}

Type guard

func isLowercaseHexDeposedKey(s string) bool {
    s = strings.TrimSpace(s)
    return s == strings.ToLower(s) && regexp.MustCompile(`^[0-9a-f]{8}$`).MatchString(s)
}

Try / catch

input = strings.ToLower(strings.TrimSpace(input))
k, err := addrs.ParseDeposedKey(input)

Prevention

When it happens

Trigger: ParseDeposedKey at resource.go:622-624 fires when len==8 and hex.DecodeString would succeed but the string is not all-lowercase. Reached from parsing deposed keys in CLI/state/addresses.

Common situations: User typed a deposed key in uppercase (e.g. 'A1B2C3D4'); tooling uppercased the key for display and it was round-tripped back; copy-paste from a formatter that capitalised hex.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/aa4cfa6011827535. Report an issue: GitHub.

Appendix: source

Thrown at internal/addrs/resource.go:623

var deposedKeyRand = rand.New(rand.NewSource(time.Now().UnixNano()))

// NewDeposedKey generates a pseudo-random deposed key. Because of the short
// length of these keys, uniqueness is not a natural consequence and so the
// caller should test to see if the generated key is already in use and generate
// another if so, until a unique key is found.
func NewDeposedKey() DeposedKey {
	v := deposedKeyRand.Uint32()
	return DeposedKey(fmt.Sprintf("%08x", v))
}

// ParseDeposedKey parses a string that is expected to be a deposed key,
// returning an error if it doesn't conform to the expected syntax.
func ParseDeposedKey(raw string) (DeposedKey, error) {
	if len(raw) != 8 {
		return "00000000", fmt.Errorf("must be eight hexadecimal digits")
	}
	if raw != strings.ToLower(raw) {
		return "00000000", fmt.Errorf("must use lowercase hex digits")
	}
	_, err := hex.DecodeString(raw)
	if err != nil {
		return "00000000", fmt.Errorf("must be eight hexadecimal digits")
	}
	return DeposedKey(raw), nil
}

func (k DeposedKey) String() string {
	return string(k)
}

func (k DeposedKey) GoString() string {
	ks := string(k)
	switch {
	case ks == "":
		return "states.NotDeposed"
	default:

View on GitHub (pinned to d32a084675)