Tencent/WeKnora · error
workspace mismatch: got %d want %d
Error message
workspace mismatch: got %d want %d
What it means
verifyExternalUserJWT in internal/middleware/auth.go:649 rejects tokens whose tenant/workspace claim does not match the tenant resolved for the request. principalTenantIDFromClaims extracts the workspace id from the claims; if it differs from the request's tenant ID the token is refused, preventing cross-workspace token reuse.
Source
Thrown at internal/middleware/auth.go:649
})
if err != nil {
return "", err
}
if token == nil || !token.Valid {
return "", errors.New("invalid external user token")
}
exp, err := claims.GetExpirationTime()
if err != nil || exp == nil {
return "", errors.New("missing expiration")
}
if time.Until(exp.Time) > maxExternalUserTokenTTL {
return "", fmt.Errorf("token lifetime exceeds %s", maxExternalUserTokenTTL)
}
if nbf, nbfErr := claims.GetNotBefore(); nbfErr == nil && nbf != nil && time.Now().Before(nbf.Time) {
return "", errors.New("token not yet valid")
}
if got := principalTenantIDFromClaims(claims); got != tenantID {
return "", fmt.Errorf("workspace mismatch: got %d want %d", got, tenantID)
}
sub, _ := claims["sub"].(string)
sub = strings.TrimSpace(sub)
if sub == "" {
return "", errors.New("missing subject")
}
return sub, nil
}
func validateExternalUserID(id string) error {
id = strings.TrimSpace(id)
if id == "" {
return errors.New("empty external user id")
}
if len(id) > maxExternalUserIDLen {
return fmt.Errorf("external user id too long (max %d)", maxExternalUserIDLen)
}
for _, r := range id {View on GitHub (pinned to 988cbb0330)
Solutions
- Issue a token per workspace with the correct tenant/workspace claim.
- Re-mint tokens after a workspace is recreated or its ID changes.
- Verify the client is pointed at the intended tenant's API endpoint.
Example fix
// before
claims := jwt.MapClaims{"aud": "weknora", "sub": uid} // no/mismatched workspace claim
// after
claims := jwt.MapClaims{"aud": "weknora", "sub": uid, "workspace_id": tenantID} Defensive patterns
Strategy: validation
Validate before calling
if got := workspaceIDFromClaims(claims); got != requestTenantID { // mint token for the right workspace
} Try / catch
if err != nil {
if strings.Contains(err.Error(), "workspace mismatch") {
// obtain a token bound to the current tenant/workspace id
}
} Prevention
- Embed the workspace/tenant claim per token; never share tokens across workspaces.
- Invalidate cached tokens when a workspace is recreated with a new ID.
- Verify tenant resolution on both client and server after config changes.
When it happens
Trigger: A correctly signed token whose workspace/tenant claim is for tenant A is presented on a request authenticated as tenant B; principalTenantIDFromClaims returns a value != tenantID.
Common situations: Reusing one token across multiple workspaces; tenant ID changed (e.g. recreated workspace with new ID) but cached tokens still carry the old ID; client misconfigured with another workspace's token endpoint.
Related errors
- id_token missing sub claim
- token not yet valid
- missing subject
- rbac: resource not found
- rbac: ownership or role insufficient
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/0925509704d8d084.
Report an issue: GitHub.