kopia/kopia · error

invalid content hash

Error message

invalid content hash

What it means

ParseID converts a string ID back into an ID structure; after length checks it hex-decodes the remaining string into the fixed-size data buffer. If hex.Decode returns an error (odd-length string or non-hex characters), the error is wrapped as "invalid content hash". This means the ID string is not a valid hex encoding of a content hash.

Solutions

  1. Verify the ID string contains only hex characters [0-9a-f] and has even length before calling ParseID.
  2. Re-obtain the ID from its authoritative source (listing contents, manifest) instead of retyping it.
  3. Trim surrounding whitespace/quotes and undo any URL-encoding applied to the ID string.
  4. Check the data source (config, metadata blob) for corruption if the ID came from a file.

Example fix

// before
id, err := ParseID(userInput) // may contain non-hex chars
// after
trimmed := strings.TrimSpace(userInput)
if !isHexString(trimmed) || len(trimmed)%2 != 0 {
    return fmt.Errorf("not a valid hex ID: %q", trimmed)
}
id, err := ParseID(trimmed)
Defensive patterns

Strategy: validation

Validate before calling

func isHexID(s string) bool {
    if len(s) == 0 || len(s)%2 != 0 { return false }
    for _, c := range s {
        if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) { return false }
    }
    return true
}
if isHexID(s) { id, err := ParseID(s) }

Type guard

func looksLikeContentID(s string) bool { return len(s) > 0 && isHexString(s) }

Try / catch

id, err := ParseID(s)
if err != nil && strings.Contains(err.Error(), "invalid content hash") {
    return fmt.Errorf("ID %q is not valid hex", s)
}

Prevention

When it happens

Trigger: Calling content.ParseID(s) with a string containing non-hex characters (e.g. 'g'-'z') or an odd number of hex digits, after the length pre-checks pass.

Common situations: Hand-typed or truncated IDs pasted from logs; IDs mangled by shells or URLs; corrupted metadata files; parsing IDs from older/foreign formats that weren't hex-encoded.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of kopia/kopia@82495e54b5 (2026-09-07). Data as JSON: /api/errors/2ed4958c6c3084a3. Report an issue: GitHub.

Appendix: source

Thrown at repo/content/index/id.go:243

	if len(s)%2 == 1 {
		id.prefix = s[0]

		if id.prefix < 'g' || id.prefix > 'z' {
			return id, errors.New("invalid content prefix")
		}

		s = s[1:]
	}

	if len(s) > 2*len(id.data) {
		return id, errors.New("hash too long")
	}

	n, err := hex.Decode(id.data[:], []byte(s))
	switch {
	case err != nil:
		return id, errors.Wrap(err, "invalid content hash")
	case n == 0:
		return id, errors.Errorf("id too short: %q", s0)
	case n > len(id.data):
		impossible.PanicOnError(errors.Errorf("id too large: %d, %q", n, s))
	}

	_ = uint(maxUInt8 - len(id.data))
	id.idLen = byte(n) //nolint:gosec // n <= len(id.data) <= 255

	return id, nil
}

View on GitHub (pinned to 82495e54b5)