router-for-me/CLIProxyAPI · error
home jwt cluster_id is required
Error message
home jwt cluster_id is required
What it means
Claim-level validation in parseHomeJWTClaims: the JWT payload's cluster_id claim is empty or whitespace-only. The cluster_id identifies which home cluster the client enrolls into and is mandatory in the enrollment token contract.
Source
Thrown at internal/home/certificate.go:98
func parseHomeJWTClaims(rawJWT string) (homeJWTClaims, error) {
var claims homeJWTClaims
parts := strings.Split(strings.TrimSpace(rawJWT), ".")
if len(parts) != 3 {
return claims, fmt.Errorf("home jwt is invalid")
}
payload, errDecode := decodeJWTPart(parts[1])
if errDecode != nil {
return claims, errDecode
}
if errUnmarshal := json.Unmarshal(payload, &claims); errUnmarshal != nil {
return claims, errUnmarshal
}
if strings.TrimSpace(claims.CertificateID) == "" {
return claims, fmt.Errorf("home jwt certificate_id is required")
}
if strings.TrimSpace(claims.ClusterID) == "" {
return claims, fmt.Errorf("home jwt cluster_id is required")
}
if normalizeFingerprint(claims.CAFingerprint) == "" {
return claims, fmt.Errorf("home jwt ca_fingerprint is required")
}
if strings.TrimSpace(claims.EnrollmentSecret) == "" {
return claims, fmt.Errorf("home jwt enrollment_secret is required")
}
if strings.TrimSpace(claims.IP) == "" || claims.Port <= 0 {
return claims, fmt.Errorf("home jwt target address is invalid")
}
return claims, nil
}
func decodeJWTPart(part string) ([]byte, error) {
if decoded, errDecode := base64.RawURLEncoding.DecodeString(part); errDecode == nil {
return decoded, nil
}
return base64.URLEncoding.DecodeString(part)View on GitHub (pinned to 78f0c4079e)
Solutions
- Obtain a new enrollment JWT from the coordinator that includes cluster_id
- If self-minting, add the claim: "cluster_id": "<your-cluster>"
- Inspect part 2 of the token (base64url-decode) to verify all required claims: certificate_id, cluster_id, ca_fingerprint, enrollment_secret, ip, port
Example fix
// before (JWT payload)
{"certificate_id":"client-123","ca_fingerprint":"AA:BB"}
// after (JWT payload)
{"certificate_id":"client-123","cluster_id":"home-1","ca_fingerprint":"AA:BB"} Defensive patterns
Strategy: validation
Validate before calling
func requiredClaimsPresent(rawJWT string) error {
parts := strings.Split(strings.TrimSpace(rawJWT), ".")
if len(parts) != 3 {
return fmt.Errorf("not a compact JWT")
}
payload, _ := base64.RawURLEncoding.DecodeString(parts[1])
for _, claim := range []string{"certificate_id", "cluster_id", "ca_fingerprint", "enrollment_secret"} {
if !hasClaim(payload, claim) {
return fmt.Errorf("missing claim %s", claim)
}
}
return nil
} Prevention
- Validate all required claims client-side before startup so failures surface early with a clear message
- Regenerate tokens from the coordinator rather than editing payloads
- Alert on coordinator minting regressions (token schema checks in its tests)
When it happens
Trigger: The enrollment JWT decodes fine but its payload has no cluster_id (or only whitespace). Fires during home enrollment startup before any network activity.
Common situations: Coordinator bug or version mismatch minting incomplete tokens; test tokens hand-built without the claim; payload template drift.
Related errors
- home jwt is invalid
- home jwt certificate_id is required
- home jwt ca_fingerprint is required
- home jwt enrollment_secret is required
- home jwt target address is invalid
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/e3310161167ed0f9.
Report an issue: GitHub.