juanfont/headscale · warning · ErrAPIKeyFailedToParse
failed to parse ApiKey: invalid display prefix format
Error message
failed to parse ApiKey: invalid display prefix format
What it means
Returned by ParseAPIKeyPrefix when a display string starts with the 'hskey-api-' literal but strings.Cut fails to split on it. Because strings.HasPrefix is checked immediately before, Cut can never fail here - this branch is defensive dead code guarding against future refactors of the prefix constant. In practice you should never see it; if you do, apiKeyPrefix and the Cut separator have diverged.
Source
Thrown at hscontrol/db/api_key.go:170
Where("id = ?", keyID).
Update("user_id", uint(userID)).Error
}
// ParseAPIKeyPrefix extracts the database prefix from a display prefix.
// Handles formats: "hskey-api-{12chars}-***", "hskey-api-{12chars}", or just "{12chars}".
// Returns the 12-character prefix suitable for database lookup.
func ParseAPIKeyPrefix(displayPrefix string) (string, error) {
// If it's already just the 12-character prefix, return it
if len(displayPrefix) == apiKeyPrefixLength && isValidBase64URLSafe(displayPrefix) {
return displayPrefix, nil
}
// If it starts with the API key prefix, parse it
if strings.HasPrefix(displayPrefix, apiKeyPrefix) {
// Remove the "hskey-api-" prefix
_, remainder, found := strings.Cut(displayPrefix, apiKeyPrefix)
if !found {
return "", fmt.Errorf("%w: invalid display prefix format", ErrAPIKeyFailedToParse)
}
// Extract just the first 12 characters (the actual prefix)
if len(remainder) < apiKeyPrefixLength {
return "", fmt.Errorf("%w: prefix too short", ErrAPIKeyFailedToParse)
}
prefix := remainder[:apiKeyPrefixLength]
// Validate it's base64 URL-safe
if !isValidBase64URLSafe(prefix) {
return "", fmt.Errorf("%w: prefix contains invalid characters", ErrAPIKeyFailedToParse)
}
return prefix, nil
}
// For legacy 7-character prefixes or other formats, return as-isView on GitHub (pinned to 565fd254d0)
Solutions
- If running a fork: verify apiKeyPrefix is a single constant used in both HasPrefix and Cut - they cannot mismatch in stock code.
- Treat hitting this error as a code regression: file a bug rather than working around it.
- Use the standard 12-character bare prefix or full 'hskey-api-...' display form to bypass this branch entirely.
Defensive patterns
Strategy: try-catch
Try / catch
prefix, err := db.ParseAPIKeyPrefix(display)
if err != nil {
return fmt.Errorf("parsing API key prefix: %w", err)
} Prevention
- In forks, keep apiKeyPrefix as a single shared constant for both HasPrefix and Cut.
- Treat this specific variant as a code bug - report it upstream rather than catching it in product code.
When it happens
Trigger: Calling ParseAPIKeyPrefix with a string that passes HasPrefix(displayPrefix, apiKeyPrefix) but where strings.Cut(displayPrefix, apiKeyPrefix) reports found=false - only possible if the two constants differ (e.g. a code edit changed one but not the other, or a custom build altered apiKeyPrefix).
Common situations: Forked/custom headscale builds where the key prefix constant was modified inconsistently; never occurs in stock builds.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to parse ApiKey: prefix too short
- failed to parse ApiKey
- failed to parse ApiKey: prefix contains invalid characters
- parsing %s: %w
- HEADSCALE_CLI_API_KEY environment variable needs to be set
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/7b09aa070864811b.
Report an issue: GitHub.