{"record":{"id":"5824951def4872fe","repo":"multica-ai/multica","slug":"slack-installation-not-found","errorCode":null,"errorMessage":"slack installation not found","messagePattern":"slack installation not found","errorType":"http","errorClass":"ErrInstallationNotFound","httpStatus":404,"severity":"error","filePath":"server/internal/integrations/slack/install.go","lineNumber":29,"sourceCode":"\t\"github.com/jackc/pgx/v5/pgconn\"\n\t\"github.com/jackc/pgx/v5/pgtype\"\n\n\t\"github.com/multica-ai/multica/server/internal/integrations/channel/engine\"\n\t\"github.com/multica-ai/multica/server/internal/util/secretbox\"\n\tdb \"github.com/multica-ai/multica/server/pkg/db/generated\"\n)\n\n// This file is the Slack install backend (MUL-3666). Slack uses the\n// bring-your-own-app (BYO) model: the workspace admin creates their own Slack\n// app, installs it to their Slack workspace, and pastes its bot token (xoxb-) +\n// app-level token (xapp-) into Multica (the paste path lives in byo_install.go).\n// The InstallService owns the at-rest encryption of those tokens — so no caller\n// can write a channel_installation with a plaintext token — plus the shared\n// persistInstall transaction and the list / get / revoke management surface.\n\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","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/internal/integrations/slack/install.go#L11-L47","documentation":"Slack ErrInstallationNotFound: no channel_installation row (channel_type='slack') matches the lookup in the given workspace. Sentinel exists so HTTP handlers return 404 without importing pgx; distinct from the ownership-collision errors raised during install.","triggerScenarios":"Calling InstallService get/revoke with a (workspace_id, installation id or app id) pair with no matching row — wrong workspace, already-disconnected app, or a stale id held by the UI.","commonSituations":"Frontend holds a cached installation id after the admin disconnected the app in another tab; workspace-scoped lookup receives the installation's id from a different workspace; deleted/re-installed app leaves old references behind.","solutions":["Refresh the installation list via ListChannelInstallationsByWorkspace and address the current row.","Verify the workspace_id parameter matches the workspace the Slack app was connected in.","If the app was disconnected, run the BYO connect flow again to create a new installation."],"exampleFix":"// before\ninst, err := svc.Get(ctx, workspaceID, id)\nif err != nil {\n\treturn err\n}\n\n// after\ninst, err := svc.Get(ctx, workspaceID, id)\nif errors.Is(err, slack.ErrInstallationNotFound) {\n\thttp.NotFound(w, r)\n\treturn\n}","handlingStrategy":"try-catch","validationCode":"insts, _ := installSvc.List(ctx, workspaceID)\nhave := false\nfor _, i := range insts { if i.ID == wantID { have = true } }\nif !have { return refreshList(w) }","typeGuard":null,"tryCatchPattern":"inst, err := installSvc.Get(ctx, workspaceID, id)\nif err != nil {\n\tif errors.Is(err, slack.ErrInstallationNotFound) {\n\t\thttp.NotFound(w, r)\n\t\treturn\n\t}\n\treturn err\n}","preventionTips":["Invalidate cached installation ids after a disconnect anywhere in the UI.","Refetch the installation list on focus/visibility so stale ids self-heal.","Always pair installation id with workspace id in URLs."],"tags":["slack","installation","not-found","integration","go"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}