multica-ai/multica · error
wecom: user id is already bound to a different user
Error message
wecom: user id is already bound to a different user
What it means
WeCom binding sentinel error: this WeCom userid is already bound to a different Multica user. Account transfer requires an explicit unbind, which is NOT implemented in iteration 1 — the documented remedy is for an admin to DELETE the binding row directly.
Source
Thrown at server/internal/integrations/wecom/binding.go:75
//
// Sixty seconds does the job the throttle was written for: six lines typed in
// one breath still write one row, at a cost of at most one row a minute for a
// user who keeps going, against rows that expire in fifteen. The price of
// being wrong is one more message, not ten minutes of a bot insisting it
// already answered.
//
// It must stay comfortably inside BindingTokenTTL so a link a throttled user
// is pointed back at still has real time left on it.
const BindingTokenMintInterval = time.Minute
var (
// ErrBindingTokenInvalid: token unknown / already consumed / expired.
// One opaque error for all three avoids a replay timing oracle.
ErrBindingTokenInvalid = errors.New("wecom: binding token invalid or expired")
// ErrBindingAlreadyAssigned: this WeCom userid is already bound to a
// different Multica user (account transfer must go through explicit
// unbind, not implemented in iter 1 — an admin can DELETE the row).
ErrBindingAlreadyAssigned = errors.New("wecom: user id is already bound to a different user")
// ErrBindingNotWorkspaceMember: the redeemer is not a member of the
// token's workspace. Translated to 403 at the HTTP boundary.
ErrBindingNotWorkspaceMember = errors.New("wecom: redeemer is not a workspace member")
)
// BindingToken is a freshly minted token. The raw value is returned exactly
// once (embedded in the binding URL); only its hash is persisted.
type BindingToken struct {
Raw string
ExpiresAt time.Time
// Reused says the throttle suppressed the mint because a live link is
// already sitting in the user's chat. Raw is empty in that case and there
// is no way to recover it — the table only ever held the hash — so the
// caller must point the user back at the earlier message rather than
// building a URL. ExpiresAt carries the live token's expiry, not a fresh
// one's.
Reused boolView on GitHub (pinned to 2c0912b6ec)
Solutions
- Have an admin delete the existing wecom/channel user-binding row for that userid (the iteration-1 stand-in for unbind), then request a fresh binding link.
- Alternatively, sign in as the Multica account that already owns the binding and simply use it.
- Track the unbind flow's implementation status before promising self-service transfer in the UI.
Example fix
// before
_, err := svc.Redeem(ctx, rawToken)
// -> "wecom: user id is already bound to a different user"
// after: iteration-1 remedy — admin removes the row, user re-links
if errors.Is(err, wecom.ErrBindingAlreadyAssigned) {
respond(w, 409, "already bound to another account — an admin must remove the binding before re-linking")
return
} Defensive patterns
Strategy: try-catch
Validate before calling
owner, err := store.GetChannelUserBinding(ctx, "wecom", wecomUserID)
if err == nil && owner != redeemingUserID {
return respondConflict(w, "already bound — admin must remove the binding row (unbind flow not implemented in iter 1)")
}
_ = wecomSvc.Redeem(ctx, rawToken) Try / catch
_, err := wecomSvc.Redeem(ctx, rawToken)
if err != nil {
if errors.Is(err, wecom.ErrBindingAlreadyAssigned) {
return respondConflict(w, "bound to another account — an admin must DELETE the binding row")
}
return err
} Prevention
- Keep WeCom userids one-to-one with Multica accounts in every environment.
- Until the unbind flow ships, give admins a tool/SQL path to remove binding rows on request.
- Test account migrations with a fresh WeCom userid rather than rebinding an owned one.
When it happens
Trigger: Redeeming a WeCom binding token while an existing channel user-binding maps the same WeCom userid to another Multica user — e.g. the person previously linked their WeCom to a first account and now redeems a link while signed in as a second.
Common situations: Account migration (employee got a new Multica account, WeCom identity unchanged); shared WeCom bot across teams where a colleague already bound the id; test environments reusing one WeCom userid.
Related errors
- lark open_id is already bound to a different user
- slack: user id is already bound to a different user
- wecom: binding token invalid or expired
- slack: binding token invalid or expired
- wecom: redeemer is not a workspace member
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/da642d060740ab0f.
Report an issue: GitHub.