henrygd/beszel · error
unsupported hash length: %d (expected 40 for SHA1 or 64 for
Error message
unsupported hash length: %d (expected 40 for SHA1 or 64 for SHA256)
What it means
Thrown by downloadFile when the expected checksum string length is neither 40 (SHA1) nor 64 (SHA256) hex characters. The tool selects the hash algorithm purely by length, so any other length is rejected as a configuration error and the temp file is cleaned up.
Source
Thrown at agent/tools/fetchsmartctl/main.go:86
tmp := dest + ".tmp"
f, err := os.OpenFile(tmp, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)
if err != nil {
return fmt.Errorf("open tmp: %w", err)
}
// Determine hash algorithm based on length (SHA1=40, SHA256=64)
var hasher hash.Hash
if shaHex := strings.TrimSpace(shaHex); shaHex != "" {
cleanSha := strings.ToLower(strings.ReplaceAll(shaHex, " ", ""))
switch len(cleanSha) {
case 40:
hasher = sha1.New()
case 64:
hasher = sha256.New()
default:
f.Close()
os.Remove(tmp)
return fmt.Errorf("unsupported hash length: %d (expected 40 for SHA1 or 64 for SHA256)", len(cleanSha))
}
}
var mw io.Writer = f
if hasher != nil {
mw = io.MultiWriter(f, hasher)
}
if _, err := io.Copy(mw, resp.Body); err != nil {
f.Close()
os.Remove(tmp)
return fmt.Errorf("write tmp: %w", err)
}
if err := f.Close(); err != nil {
os.Remove(tmp)
return fmt.Errorf("close tmp: %w", err)
}
if hasher != nil && shaHex != "" {View on GitHub (pinned to b38fb7dafa)
Solutions
- Count the checksum characters: must be exactly 40 (SHA1) or 64 (SHA256) hex digits
- Re-copy the checksum, excluding any filename or whitespace artifacts
- If only SHA512 is published, compute a SHA256 locally instead of passing it
- Strip prefixes like 'sha256=' before passing
Example fix
// before
cleanSha := strings.ToLower(strings.ReplaceAll(shaHex, " ", ""))
// after
cleanSha := strings.ToLower(strings.TrimSpace(shaHex))
if i := strings.IndexByte(cleanSha, ' '); i >= 0 {
cleanSha = cleanSha[:i] // drop trailing filename from 'checksum file' paste
} Defensive patterns
Strategy: validation
Validate before calling
sha := strings.TrimSpace(shaInput)
if sha != "" && len(sha) != 40 && len(sha) != 64 {
return fmt.Errorf("checksum must be 40 (SHA1) or 64 (SHA256) hex chars, got %d", len(sha))
}
for _, r := range sha {
if !((r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')) {
return fmt.Errorf("checksum contains non-hex character %q", r)
}
} Type guard
func isHexLen(s string, n int) bool {
if len(s) != n {
return false
}
_, err := hex.DecodeString(s)
return err == nil
} Try / catch
if err := downloadFile(url, dest, sha); err != nil {
if strings.Contains(err.Error(), "unsupported hash length") {
fmt.Printf("bad checksum %q (len %d): use SHA1(40) or SHA256(64)\n", sha, len(strings.TrimSpace(sha)))
}
return err
} Prevention
- Copy only the hex field from checksum files, never the trailing filename
- Verify length is 40 or 64 before passing a checksum
- Never paste SHA512/MD5 digests into a SHA1/SHA256 tool
- Trim whitespace and prefixes (sha256=) from pasted checksums
When it happens
Trigger: Passing a shaHex argument that is empty is fine (skips verification), but a non-empty checksum of wrong length — e.g. truncated SHA256, SHA512 (128 chars), MD5 (32 chars), or containing stray non-hex text pasted from a checksums page.
Common situations: Copy-pasting the wrong column from a checksums file; using a SHA512 checksum; shell variable truncation; including a filename in the pasted checksum line.
Related errors
- service name is required
- invalid token
- system missing required fields
- invalid container id
- create dir: %w
AI-assisted analysis of henrygd/beszel@b38fb7dafa (2026-08-31).
Data as JSON: /api/errors/6da1f00ad2b87383.
Report an issue: GitHub.