multica-ai/multica · error · ErrTeamOwnedBySameWorkspace

slack: this Slack app is already connected to another agent

Error message

slack: this Slack app is already connected to another agent in this workspace

What it means

Slack install ownership error: the Slack app is already connected to a DIFFERENT live (non-archived) agent in the SAME workspace. Introduced (per #4810) because the old catch-all wrongly blamed 'another workspace'; naming the same-workspace case points the user at the Disconnect action they can actually reach.

Source

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

// 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)
	GetChannelInstallationOwnerByAppID(ctx context.Context, arg db.GetChannelInstallationOwnerByAppIDParams) (db.GetChannelInstallationOwnerByAppIDRow, error)
	ListChannelInstallationsByWorkspace(ctx context.Context, arg db.ListChannelInstallationsByWorkspaceParams) ([]db.ChannelInstallation, error)
	GetChannelInstallationInWorkspace(ctx context.Context, arg db.GetChannelInstallationInWorkspaceParams) (db.ChannelInstallation, error)
	SetChannelInstallationStatus(ctx context.Context, arg db.SetChannelInstallationStatusParams) error
}

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Open the currently connected agent's settings in THIS workspace and disconnect its Slack app, then retry the connect for the new agent.
  2. If the wrong agent won the connection, disconnect from it and immediately connect to the intended agent (order matters).
  3. Check for accidentally duplicated agents; archive/delete the stray one after freeing the app.

Example fix

// before: raw error shown
// "slack: this Slack app is already connected to a different Multica workspace" (old catch-all, misleading for same-workspace case)

// after
if errors.Is(err, slack.ErrTeamOwnedBySameWorkspace) {
	// same-workspace case: the fix is one click away
	respond(w, 409, "already connected to another agent here — disconnect it first")
	return
}
Defensive patterns

Strategy: try-catch

Validate before calling

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

Try / catch

_, err := installSvc.Register(ctx, params)
if err != nil {
	if errors.Is(err, slack.ErrTeamOwnedBySameWorkspace) {
		// #4810: name the SAME-workspace case so the user finds the reachable Disconnect
		return respondConflict(w, "already connected to another agent here — use Disconnect on that agent")
	}
	return err
}

Prevention

When it happens

Trigger: Calling RegisterBYO to connect a Slack app to agent B while the same app id is live on agent A in the same workspace. The (channel_type, app_id) uniqueness forbids one Slack app feeding two agents.

Common situations: Admin swaps which agent owns the Slack bot and forgets to disconnect first; duplicate agent created during setup and the app wired to the wrong one; re-running the connect dialog against a second agent.

Related errors


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