multica-ai/multica · error · ErrTeamOwnedByAnotherWorkspace

slack: this Slack app is already connected to a different Mu

Error message

slack: this Slack app is already connected to a different Multica workspace

What it means

Slack install ownership error: the pasted Slack app (identified by app id) is already connected to a live owner in a DIFFERENT Multica workspace, which would collide with the (channel_type, app_id) routing index. A Slack app is one bot identity mapped to one agent, so it must be disconnected in the other workspace first.

Source

Thrown at server/internal/integrations/slack/install.go:35

)

// This file is the Slack install backend (MUL-3666). Slack uses the
// bring-your-own-app (BYO) model: the workspace admin creates their own Slack
// app, installs it to their Slack workspace, and pastes its bot token (xoxb-) +
// app-level token (xapp-) into Multica (the paste path lives in byo_install.go).
// The InstallService owns the at-rest encryption of those tokens — so no caller
// can write a channel_installation with a plaintext token — plus the shared
// persistInstall transaction and the list / get / revoke management surface.

var (
	// ErrInstallationNotFound surfaces "no row matches in this workspace".
	ErrInstallationNotFound = errors.New("slack installation not found")
	// ErrTeamOwnedByAnotherWorkspace is returned when the pasted Slack app is
	// already connected to a live owner in a DIFFERENT Multica workspace — it
	// would collide with the (channel_type, app_id) routing index. A Slack app is
	// one bot identity and maps to one agent; reusing it here requires
	// disconnecting it in the other workspace first.
	ErrTeamOwnedByAnotherWorkspace = errors.New("slack: this Slack app is already connected to a different Multica workspace")
	// ErrTeamOwnedBySameWorkspace is returned when the app is already connected to
	// a DIFFERENT (live, non-archived) agent in the SAME workspace. The old
	// catch-all wrongly blamed "another workspace"; naming the same-workspace case
	// points the user at the Disconnect they can actually reach (#4810).
	ErrTeamOwnedBySameWorkspace = errors.New("slack: this Slack app is already connected to another agent in this workspace")
	// ErrTeamOwnedByArchivedAgent is returned when the app's owning agent is
	// archived (and so still holds the bot, since archiving is reversible). The
	// user recovers by restoring that agent or disconnecting its bot.
	ErrTeamOwnedByArchivedAgent = errors.New("slack: this Slack app is connected to an archived agent in this workspace")
)

// installQueries is the slice of generated queries InstallService needs. WithTx
// returns the same interface bound to a transaction so persistInstall runs its
// upsert atomically (and so tests can inject a fake without a real DB).
type installQueries interface {
	WithTx(tx pgx.Tx) installQueries
	UpsertChannelInstallation(ctx context.Context, arg db.UpsertChannelInstallationParams) (db.ChannelInstallation, error)
	ReclaimDeadChannelInstallationByAppID(ctx context.Context, arg db.ReclaimDeadChannelInstallationByAppIDParams) (pgtype.UUID, error)

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. In the OTHER Multica workspace that owns the app, run Disconnect on that installation to release it.
  2. If the other workspace is unreachable (org split, deleted tenant), have an operator revoke/remove the owning channel_installation row directly.
  3. Alternatively connect a DIFFERENT Slack app (new xoxb-/xapp- pair) to this workspace instead of sharing one.

Example fix

// before
_, err := installSvc.Register(ctx, params)
if err != nil {
	return err // surfaces raw to admin
}

// after
_, err := installSvc.Register(ctx, params)
switch {
case errors.Is(err, slack.ErrTeamOwnedByAnotherWorkspace):
	respond(w, 409, "disconnect this Slack app in its other workspace first")
case errors.Is(err, slack.ErrTeamOwnedBySameWorkspace):
	respond(w, 409, "disconnect it from the other agent in this workspace (Agents > Disconnect)")
}
Defensive patterns

Strategy: try-catch

Validate before calling

owner, err := queries.GetChannelInstallationOwnerByAppID(ctx, "slack", appID)
if err == nil && owner.WorkspaceID != thisWorkspace {
	return respondConflict(w, "disconnect this app in its other workspace first")
}
_ = installSvc.Register(ctx, params)

Try / catch

_, err := installSvc.Register(ctx, params)
if err != nil {
	if errors.Is(err, slack.ErrTeamOwnedByAnotherWorkspace) {
		return respondConflict(w, "owned by another workspace — disconnect it there first")
	}
	return err
}

Prevention

When it happens

Trigger: Calling RegisterBYO/persistInstall while a live (non-revoked) channel_installation for the same app id exists under another workspace's agent — e.g. the same Slack app was previously wired into a second Multica workspace or a leftover instance.

Common situations: Trialing in a sandbox workspace then moving to production without disconnecting; two teams independently connecting the same Slack app; dev/staging/prod Multica instances sharing one Slack app.

Related errors


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/35d7f2c5f8d48e9b. Report an issue: GitHub.