juanfont/headscale · error · parseErr (ErrPreAuthKeyFailedToParse|ErrOAuthClientFailedToParse|ErrAccessTokenFailedToParse|ErrAPIKeyFailedToParse)
%w: expected separator '-' at position %d, got '%c'
Error message
%w: expected separator '-' at position %d, got '%c'
What it means
parsePrefixedKey found no '-' separator at the expected position (character index prefixLen, i.e. position 12 in current keys). The new key format is fixed-layout: prefix, then '-', then secret. Because the secret itself may contain dashes, the separator must be exactly at prefixLen; anything else is malformed.
Source
Thrown at hscontrol/db/preauth_keys.go:258
//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],
)
}
secret := prefixAndSecret[prefixLen+1:]
// Validate secret length
if len(secret) != secretLen {
return "", "", fmt.Errorf(
"%w: secret length mismatch, expected %d chars, got %d",
parseErr,
secretLen,
len(secret),
)
}View on GitHub (pinned to 565fd254d0)
Solutions
- Regenerate the key and pass it through unmodified.
- If assembling programmatically, use fmt.Sprintf("%s-%s", prefix, secret) with a 12-char prefix and 64-char secret.
- Check for accidental character insertion/deletion around the 13th character after 'hskey-<kind>-'.
Defensive patterns
Strategy: validation
Validate before calling
func hasSeparatorAtPosition(rest string, pos int) bool {
return len(rest) > pos && rest[pos] == '-'
} Prevention
- Never hand-assemble keys; use the issued string verbatim.
- When concatenating parts programmatically, unit-test against a known-good key.
- Remember the secret may itself contain '-', so position (not search) defines the separator.
When it happens
Trigger: Submitting a key where the prefix is not exactly 12 characters (extra or missing character shifts the separator), a key assembled by concatenating parts with the wrong delimiter, or a hand-typed key with a typo in the prefix.
Common situations: Scripts that join prefix and secret with '_' or no dash; users deleting a character from the prefix during copy; mixed-format keys from older versions with different prefix lengths.
Related errors
- %w: key too short, expected at least %d chars after prefix,
- %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/ca35706764acfd8b.
Report an issue: GitHub.