juanfont/headscale · error · parseErr (ErrPreAuthKeyFailedToParse|ErrOAuthClientFailedToParse|ErrAccessTokenFailedToParse|ErrAPIKeyFailedToParse)
%w: key too short, expected at least %d chars after prefix,
Error message
%w: key too short, expected at least %d chars after prefix, got %d
What it means
parsePrefixedKey rejected a credential because the string after the 'hskey-*-' scheme prefix is shorter than the minimum of prefixLen + 1 separator + secretLen characters (77 chars for today's 12/64 keys). The library-specific parseErr sentinel is wrapped so callers can classify it. Fixed-length parsing is used deliberately because base64 URL-safe secrets may contain dashes.
Source
Thrown at hscontrol/db/preauth_keys.go:246
}
return &pak, nil
}
// parsePrefixedKey splits the prefix-and-secret portion of a new-format key
// (the part after the "hskey-*-" prefix) into its fixed-length prefix and
// secret components, validating the length, separator position, and that both
// components are base64 URL-safe. Fixed-length parsing is used instead of
// separator-based to handle dashes in base64 URL-safe characters.
func parsePrefixedKey(
prefixAndSecret string,
//nolint:unparam // kept explicit though every credential kind uses a 12-char prefix and 64-char secret today
prefixLen, secretLen int,
parseErr error,
) (string, string, error) {
expectedMinLength := prefixLen + 1 + secretLen
if len(prefixAndSecret) < expectedMinLength {
return "", "", fmt.Errorf(
"%w: key too short, expected at least %d chars after prefix, got %d",
parseErr,
expectedMinLength,
len(prefixAndSecret),
)
}
prefix := prefixAndSecret[:prefixLen]
// Validate separator at expected position
if prefixAndSecret[prefixLen] != '-' {
return "", "", fmt.Errorf(
"%w: expected separator '-' at position %d, got '%c'",
parseErr,
prefixLen,
prefixAndSecret[prefixLen],
)
}View on GitHub (pinned to 565fd254d0)
Solutions
- Regenerate the key and copy it as a single unbroken line.
- Validate length before submitting: after 'hskey-<kind>-', expect exactly 12 chars + '-' + 64 chars.
- If using a legacy key, create a new key with the current headscale version.
Example fix
// before
key := strings.TrimSpace(cfg.AuthKey)
node, err :=client.Up(ctx, key) // fails if truncated
// after
parts := strings.SplitN(cfg.AuthKey, "-", 3) // hskey, kind, rest
if len(parts) < 3 || len(parts[2]) != 77 {
return fmt.Errorf("auth key truncated: expected 77 chars after scheme prefix")
} Defensive patterns
Strategy: validation
Validate before calling
const (
prefixLen = 12
secretLen = 64
)
func checkKeyShape(rest string) error {
if len(rest) < prefixLen+1+secretLen {
return fmt.Errorf("key truncated: need %d chars after scheme prefix, got %d", prefixLen+1+secretLen, len(rest))
}
return nil
} Prevention
- Validate key length client-side before sending a registration request.
- Keep auth keys on one line in configs and env vars.
- Automate key transport with copy/paste or APIs, never manual transcription.
When it happens
Trigger: Passing a truncated key to any API that parses new-format credentials (pre-auth key verification): a key cut off during copy/paste, a shell variable that swallowed part of the string, or an old/legacy key format with a shorter secret.
Common situations: Line wrapping in terminals, chat apps, or YAML files truncating the key; scripts building the key from parts getting the secret length wrong; using an old key generated before the current 64-char secret scheme.
Related errors
- %w: expected separator '-' at position %d, got '%c'
- %w: secret length mismatch, expected %d chars, got %d
- %w: prefix contains invalid characters (expected base64 URL-
- %w: secret contains invalid characters (expected base64 URL-
- failed to parse auth-key
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/2e20314e1b5fa0a0.
Report an issue: GitHub.