{"record":{"id":"61adc8b50c256801","repo":"multica-ai/multica","slug":"slack-this-slack-app-is-connected-to-an-archived","errorCode":null,"errorMessage":"slack: this Slack app is connected to an archived agent in this workspace","messagePattern":"slack: this Slack app is connected to an archived agent in this workspace","errorType":"http","errorClass":"ErrTeamOwnedByArchivedAgent","httpStatus":409,"severity":"warning","filePath":"server/internal/integrations/slack/install.go","lineNumber":44,"sourceCode":"\nvar (\n\t// ErrInstallationNotFound surfaces \"no row matches in this workspace\".\n\tErrInstallationNotFound = errors.New(\"slack installation not found\")\n\t// ErrTeamOwnedByAnotherWorkspace is returned when the pasted Slack app is\n\t// already connected to a live owner in a DIFFERENT Multica workspace — it\n\t// would collide with the (channel_type, app_id) routing index. A Slack app is\n\t// one bot identity and maps to one agent; reusing it here requires\n\t// disconnecting it in the other workspace first.\n\tErrTeamOwnedByAnotherWorkspace = errors.New(\"slack: this Slack app is already connected to a different Multica workspace\")\n\t// ErrTeamOwnedBySameWorkspace is returned when the app is already connected to\n\t// a DIFFERENT (live, non-archived) agent in the SAME workspace. The old\n\t// catch-all wrongly blamed \"another workspace\"; naming the same-workspace case\n\t// points the user at the Disconnect they can actually reach (#4810).\n\tErrTeamOwnedBySameWorkspace = errors.New(\"slack: this Slack app is already connected to another agent in this workspace\")\n\t// ErrTeamOwnedByArchivedAgent is returned when the app's owning agent is\n\t// archived (and so still holds the bot, since archiving is reversible). The\n\t// user recovers by restoring that agent or disconnecting its bot.\n\tErrTeamOwnedByArchivedAgent = errors.New(\"slack: this Slack app is connected to an archived agent in this workspace\")\n)\n\n// installQueries is the slice of generated queries InstallService needs. WithTx\n// returns the same interface bound to a transaction so persistInstall runs its\n// upsert atomically (and so tests can inject a fake without a real DB).\ntype installQueries interface {\n\tWithTx(tx pgx.Tx) installQueries\n\tUpsertChannelInstallation(ctx context.Context, arg db.UpsertChannelInstallationParams) (db.ChannelInstallation, error)\n\tReclaimDeadChannelInstallationByAppID(ctx context.Context, arg db.ReclaimDeadChannelInstallationByAppIDParams) (pgtype.UUID, error)\n\tGetChannelInstallationOwnerByAppID(ctx context.Context, arg db.GetChannelInstallationOwnerByAppIDParams) (db.GetChannelInstallationOwnerByAppIDRow, error)\n\tListChannelInstallationsByWorkspace(ctx context.Context, arg db.ListChannelInstallationsByWorkspaceParams) ([]db.ChannelInstallation, error)\n\tGetChannelInstallationInWorkspace(ctx context.Context, arg db.GetChannelInstallationInWorkspaceParams) (db.ChannelInstallation, error)\n\tSetChannelInstallationStatus(ctx context.Context, arg db.SetChannelInstallationStatusParams) error\n}\n\n// dbInstallQueries adapts *db.Queries to installQueries — the generated WithTx\n// returns *db.Queries, so we wrap it to return the interface (the same adapter\n// pattern engine.ChatSession uses).","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/internal/integrations/slack/install.go#L26-L62","documentation":"Slack install ownership error: the Slack app's owning agent is ARCHIVED. Because archiving is reversible, the archived agent still holds the bot, so the app looks free in the agent list while it is not. Recovery is to restore that agent or disconnect its bot.","triggerScenarios":"Calling RegisterBYO while the live channel_installation for the app id belongs to an agent with archived status — typical after 'temporarily' archiving an agent that owned the Slack connection.","commonSituations":"Team archives an old agent during a reorg without disconnecting its Slack bot; later admin sees no live agent holding the app and cannot understand the collision.","solutions":["Restore the archived agent, then either keep it as the owner or disconnect the Slack app from it and connect to the desired agent.","If the agent should stay archived, restore it, disconnect the Slack app, and re-archive.","Communicate in-team that archived agents keep their integrations — build a pre-archive checklist that includes disconnecting bots."],"exampleFix":"// before\n_, err := installSvc.Register(ctx, params)\n// -> \"slack: this Slack app is connected to an archived agent in this workspace\"\n\n// after: guide the admin to the restore path\nif errors.Is(err, slack.ErrTeamOwnedByArchivedAgent) {\n\trespond(w, 409, \"this app is held by an archived agent — restore that agent, then disconnect the bot\")\n\treturn\n}","handlingStrategy":"try-catch","validationCode":"owner, err := queries.GetChannelInstallationOwnerByAppID(ctx, \"slack\", appID)\nif err == nil && owner.AgentArchived {\n\treturn respondConflict(w, \"restore the archived agent, then disconnect the bot\")\n}\n_ = installSvc.Register(ctx, params)","typeGuard":null,"tryCatchPattern":"_, err := installSvc.Register(ctx, params)\nif err != nil {\n\tif errors.Is(err, slack.ErrTeamOwnedByArchivedAgent) {\n\t\treturn respondConflict(w, \"app held by an archived agent — restore it or disconnect its bot\")\n\t}\n\treturn err\n}","preventionTips":["Add 'disconnect integrations' to the pre-archive checklist for agents.","When an app 'looks free' but install fails, check archived agents for hidden holders.","Restore, disconnect, re-archive is the safe sequence for retiring an agent that owns a bot."],"tags":["slack","byo","archived-agent","conflict","integration","go"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}