larksuite/cli · error

app registration returned credentials with a contradictory t

Error message

app registration returned credentials with a contradictory tenant brand %q

What it means

Thrown by RegisterAppWithDiscovery at internal/auth/app_registration.go:289 when a poll returns complete credentials (client_id + client_secret) whose user_info.tenant_brand parses to a brand different from the domain that actually issued them (effectiveBrand). The library treats the issuing domain as authoritative, so a contradicting final tenant report is a server protocol violation and registration aborts instead of silently overriding the brand.

Source

Thrown at internal/auth/app_registration.go:289

		errStr := getStr(data, "error")
		if errStr == "" {
			result := &AppRegistrationResult{
				ClientID:     getStr(data, "client_id"),
				ClientSecret: getStr(data, "client_secret"),
			}
			if userInfoRaw, ok := data["user_info"].(map[string]interface{}); ok {
				result.UserInfo = &AppRegUserInfo{
					OpenID:      getStr(userInfoRaw, "open_id"),
					TenantBrand: getStr(userInfoRaw, "tenant_brand"),
				}
			}

			if result.ClientID != "" && result.ClientSecret != "" {
				// The issuing domain is authoritative; a contradictory final
				// tenant report is a protocol violation, not a brand override.
				if result.UserInfo != nil && result.UserInfo.TenantBrand != "" &&
					core.ParseBrand(result.UserInfo.TenantBrand) != effectiveBrand {
					return nil, effectiveBrand, fmt.Errorf("app registration returned credentials with a contradictory tenant brand %q", result.UserInfo.TenantBrand)
				}
				return result, effectiveBrand, nil
			}
			// Incomplete credentials without an error: keep polling.
			continue
		}

		switch errStr {
		case "authorization_pending":
			continue
		case "slow_down":
			interval = minInt(interval+5, maxPollIntervalSeconds)
			fmt.Fprintf(errOut, "[lark-cli] app-registration: slow_down, interval increased to %ds\n", interval)
			continue
		case "access_denied":
			return nil, effectiveBrand, ErrRegistrationDenied
		case "expired_token", "invalid_grant":
			return nil, effectiveBrand, ErrRegistrationExpired

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Restart the registration flow — a transient race may resolve on the next run.
  2. Ensure the account you approve in the browser belongs to the brand matching your region (Feishu vs Lark); approve with the correct account/tenant.
  3. Update the CLI and retry in case the server bug was fixed; check for known issues.
  4. If reproducible with a specific tenant, report the tenant_brand/issuing-domain pair to the service maintainers — this indicates a server protocol violation.
Defensive patterns

Strategy: try-catch

Type guard

func tenantBrandMatches(brand core.LarkBrand, ui *AppRegUserInfo) bool {
    return ui == nil || ui.TenantBrand == "" || core.ParseBrand(ui.TenantBrand) == brand
}

Try / catch

result, brand, err := RegisterAppWithDiscovery(ctx, client, resp, errOut)
if err != nil {
    if strings.Contains(err.Error(), "contradictory tenant brand") {
        // protocol violation: restart the flow and approve with the brand-correct account
    }
    return err
}

Prevention

When it happens

Trigger: Server returns credentials on domain A (e.g. feishu) while the final user_info.tenant_brand says brand B (e.g. lark), after the one permitted cross-brand switch already happened (switched=true prevents further switches but a later contradictory report still triggers this).

Common situations: User belongs to a tenant on the opposite brand from the polled domain at the exact moment credentials are issued; server-side brand-propagation race; tenant migrated brands mid-registration; backend bug emitting a stale tenant_brand in the final successful poll.

Related errors


AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04). Data as JSON: /api/errors/da87ba568a75bc82. Report an issue: GitHub.