multica-ai/multica · error
wecom_bot_owned_by_same_workspace
wecom_bot_owned_by_same_workspace
Error message
wecom: this bot is already connected to another agent in this workspace
What it means
WeCom install ownership error: another agent in the SAME workspace already holds this bot. The UpsertChannelInstallation ON CONFLICT targets (workspace_id, agent_id, channel_type), but the UNIQUE index on (channel_type, config->>'app_id') makes a second agent trip the index instead; this sentinel replaces the raw Postgres duplicate-key text with a message naming the reachable fix. One bot = one live long-connection subscriber, so it cannot be shared.
Source
Thrown at server/internal/integrations/wecom/installation.go:302
}
return Installation{}, nil
}
// Sentinels for the one conflict Upsert cannot resolve: the bot is already
// connected somewhere else. UpsertChannelInstallation conflicts on
// (workspace_id, agent_id, channel_type), but idx_channel_installation_type_appid
// is UNIQUE on (channel_type, config->>'app_id') — so connecting the SAME bot to
// a second agent misses the ON CONFLICT clause entirely and trips the index
// instead. Without these the admin reads the raw Postgres text
// ("duplicate key value violates unique constraint …") in a toast.
//
// One bot is one connection: the WeCom long connection allows a single
// live subscriber per bot, so two agents cannot share one. The way out is
// always to free the bot first, which is what each message says.
var (
// ErrBotOwnedBySameWorkspace — another agent in the admin's own workspace
// holds the bot. Reversible from the same settings screen.
ErrBotOwnedBySameWorkspace = errors.New("wecom: this bot is already connected to another agent in this workspace")
// ErrBotOwnedByArchivedAgent — the holder is archived, so it does not show
// up in the agent list and the bot looks free while it is not.
ErrBotOwnedByArchivedAgent = errors.New("wecom: this bot is connected to an archived agent in this workspace")
// ErrBotOwnedByAnotherWorkspace — the holder is out of sight entirely and
// only someone with access there can release it.
ErrBotOwnedByAnotherWorkspace = errors.New("wecom: this bot is already connected to a different Multica workspace")
)
// pgUniqueViolation is Postgres' unique_violation SQLSTATE.
const pgUniqueViolation = "23505"
// botSlotConflictErr answers one question: may this install act on the
// (wecom, bot_id) routing slot at all? It returns the sentinel to refuse with,
// or nil to proceed. MUST be called inside the transaction holding
// LockChannelInstallationAppIDSlot — outside it the answer is a guess that a
// concurrent install or reconnect can invalidate before it is used.View on GitHub (pinned to 2c0912b6ec)
Solutions
- Disconnect the bot from the other agent in this workspace (settings screen), then connect it to the intended agent.
- If unsure who holds it, list installations for the workspace and find the (channel_type='wecom', same app_id) row.
- For genuinely shared needs, connect each agent to a different WeCom bot.
Example fix
// before: raw Postgres error reaches the admin
// "duplicate key value violates unique constraint idx_channel_installation_type_appid"
// after: service maps 23505 to actionable sentinels
if errors.Is(err, wecom.ErrBotOwnedBySameWorkspace) {
respond(w, 409, "free the bot first: disconnect it from the other agent in this workspace")
return
} Defensive patterns
Strategy: try-catch
Validate before calling
owner, err := queries.GetChannelInstallationOwnerByAppID(ctx, "wecom", appID)
if err == nil && owner.WorkspaceID == thisWorkspace && owner.AgentID != params.AgentID {
return respondConflict(w, "disconnect the bot from its current agent first")
}
_ = installSvc.Install(ctx, params) Try / catch
if err := installSvc.Install(ctx, params); err != nil {
if errors.Is(err, wecom.ErrBotOwnedBySameWorkspace) {
// 23505 on (channel_type, app_id) mapped to an actionable message
return respondConflict(w, "free the bot: disconnect it from the other agent in this workspace")
}
return err
} Prevention
- One WeCom bot per agent, always: disconnect before reconnecting elsewhere.
- When a 23505 surfaces anywhere in the install path, translate it to these sentinels rather than showing raw Postgres text.
- Remember the ON CONFLICT target differs from the unique index — a second agent never upserts, it trips the index.
When it happens
Trigger: Connecting a WeCom bot to agent B while agent A in the same workspace already has it installed — the upsert misses the ON CONFLICT clause and raises unique_violation (SQLSTATE 23505) on idx_channel_installation_type_appid, which the service translates to this error.
Common situations: Reassigning which agent fronts a WeCom bot without disconnecting first; duplicated agents during setup; admin assumes bots are shareable across agents.
Related errors
- ErrInstallationNotFound
- slack installation not found
- slack: this Slack app is already connected to a different Mu
- slack: this Slack app is already connected to another agent
- slack: this Slack app is connected to an archived agent in t
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/fb83e3827cd9a858.
Report an issue: GitHub.