{"record":{"id":"0fd8b7ac0e0fff1e","repo":"multica-ai/multica","slug":"slack-user-id-is-already-bound-to-a-different-use","errorCode":null,"errorMessage":"slack: user id is already bound to a different user","messagePattern":"slack: user id is already bound to a different user","errorType":"http","errorClass":"ErrBindingAlreadyAssigned","httpStatus":409,"severity":"error","filePath":"server/internal/integrations/slack/binding.go","lineNumber":37,"sourceCode":"\n// This file is the Slack user-binding token flow: an unbound Slack user who\n// messages the bot gets a \"link your account\" prompt (minted here, delivered by\n// the OutboundReplier), clicks through to the in-product redeem page, and their\n// Slack user id is bound to their Multica account. It mirrors\n// lark.BindingTokenService but runs on the generic channel_* queries with\n// channel_type='slack' (lark's ChannelStore hardcodes 'feishu').\n\n// BindingTokenTTL bounds a token's life. The channel_binding_token CHECK\n// enforces the same 15-minute cap so a misconfigured caller cannot mint longer.\nconst BindingTokenTTL = 15 * time.Minute\n\nvar (\n\t// ErrBindingTokenInvalid: token unknown / already consumed / expired. One\n\t// opaque error for all three avoids a replay timing oracle.\n\tErrBindingTokenInvalid = errors.New(\"slack: binding token invalid or expired\")\n\t// ErrBindingAlreadyAssigned: this Slack user id is already bound to a\n\t// different Multica user (account transfer must go through explicit unbind).\n\tErrBindingAlreadyAssigned = errors.New(\"slack: user id is already bound to a different user\")\n\t// ErrBindingNotWorkspaceMember: the redeemer is not a member of the token's\n\t// workspace. Translated to 403 at the HTTP boundary.\n\tErrBindingNotWorkspaceMember = errors.New(\"slack: redeemer is not a workspace member\")\n)\n\n// BindingToken is a freshly minted token. The raw value is returned exactly\n// once (embedded in the binding URL); only its hash is persisted.\ntype BindingToken struct {\n\tRaw       string\n\tExpiresAt time.Time\n}\n\n// RedeemedBindingToken is returned after a successful redemption.\ntype RedeemedBindingToken struct {\n\tWorkspaceID    pgtype.UUID\n\tInstallationID pgtype.UUID\n\tSlackUserID    string\n}","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/internal/integrations/slack/binding.go#L19-L55","documentation":"Slack binding sentinel error: the Slack user id being bound is already bound to a different Multica user. As with lark, a binding token cannot transfer an already-bound Slack identity; account transfer must go through an explicit unbind flow.","triggerScenarios":"Redeeming a Slack binding token while a channel user-binding row already maps this Slack user id to another Multica user. Happens when the same person has two Multica accounts, or a Slack account is shared and one member already completed the link.","commonSituations":"User changed Multica accounts (left company A instance, joined with new account) and re-runs binding; shared/team Slack login already bound by a colleague; test environments reusing one Slack user against multiple accounts.","solutions":["Sign in as the Multica user that currently owns the binding and unbind the Slack user id via the explicit unbind flow, then request a new binding link.","Confirm the intended owner by looking up the existing channel user-binding row for the Slack user id before retrying.","For shared Slack accounts, agree on a single owning Multica account instead of round-robining the binding."],"exampleFix":"// before\nres, err := svc.Redeem(ctx, rawToken)\n// -> \"slack: user id is already bound to a different user\"\n\n// after\nres, err := svc.Redeem(ctx, rawToken)\nif errors.Is(err, slack.ErrBindingAlreadyAssigned) {\n\trenderUnbindRequired(w) // point user at the unbind flow\n\treturn\n}","handlingStrategy":"try-catch","validationCode":"owner, err := store.GetChannelUserBinding(ctx, \"slack\", slackUserID)\nif err == nil && owner != redeemingUserID {\n\treturn renderUnbindRequired(w)\n}\n_ = slackSvc.Redeem(ctx, rawToken)","typeGuard":null,"tryCatchPattern":"res, err := slackSvc.Redeem(ctx, rawToken)\nif err != nil {\n\tif errors.Is(err, slack.ErrBindingAlreadyAssigned) {\n\t\treturn respondConflict(w, \"bound to another account — unbind first\")\n\t}\n\treturn err\n}","preventionTips":["Bind one Slack user id to exactly one Multica account; use distinct Slack test users per test account.","Expose the unbind flow next to the binding status so transfer never goes through a token.","Include the Slack user id in error logs to locate the conflicting binding fast."],"tags":["slack","binding","account-linking","integration","go"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}