multica-ai/multica · error · lark.ErrInstallationNotFound
ErrInstallationNotFound
ErrInstallationNotFound
Error message
lark installation not found
What it means
Lark ErrInstallationNotFound: no lark installation row matches the lookup in the given workspace. It is a distinct sentinel (not raw pgx.ErrNoRows) so HTTP handlers can map it straight to 404 without importing pgx.
Source
Thrown at server/internal/integrations/lark/installation.go:134
if errors.Is(err, pgx.ErrNoRows) {
return Installation{}, ErrInstallationNotFound
}
return Installation{}, err
}
return row, nil
}
// ListByWorkspace returns every installation rooted at the workspace,
// active and revoked, oldest first. The status column lets the UI
// distinguish "wired up" from "torn down but kept for audit".
func (s *InstallationService) ListByWorkspace(ctx context.Context, workspaceID pgtype.UUID) ([]Installation, error) {
return s.queries.ListLarkInstallationsByWorkspace(ctx, workspaceID)
}
// ErrInstallationNotFound surfaces "no row matches in this workspace"
// — used by the HTTP layer to return 404. Distinct from a plain
// pgx.ErrNoRows so handlers do not need to import pgx.
var ErrInstallationNotFound = errors.New("lark installation not found")
func validateInstallationParams(p InstallationParams) error {
switch {
case !p.WorkspaceID.Valid:
return errors.New("workspace_id is required")
case !p.AgentID.Valid:
return errors.New("agent_id is required")
case !p.InstallerUserID.Valid:
return errors.New("installer_user_id is required")
case p.AppID == "":
return errors.New("app_id is required")
case p.AppSecret == "":
return errors.New("app_secret is required")
case p.BotOpenID == "":
return errors.New("bot_open_id is required")
}
return nil
}View on GitHub (pinned to 2c0912b6ec)
Solutions
- Re-list installations via ListByWorkspace for the workspace and use the current installation id from that result.
- Verify the workspace_id parameter matches the workspace the installation was created in.
- If the row was revoked, treat it as gone for actionable paths and re-run the lark install flow to create a new installation.
Example fix
// before
inst, err := svc.Get(ctx, workspaceID, installationID)
if err != nil {
log.Fatal(err) // raw no-rows leaks to caller
}
// after
inst, err := svc.Get(ctx, workspaceID, installationID)
if errors.Is(err, lark.ErrInstallationNotFound) {
http.NotFound(w, r)
return
} Defensive patterns
Strategy: try-catch
Validate before calling
insts, err := svc.ListByWorkspace(ctx, workspaceID)
if err != nil { return err }
known := map[pgtype.UUID]bool{}
for _, i := range insts { known[i.ID] = true }
if !known[wantID] {
// refresh UI instead of a doomed get
return respondList(insts)
} Try / catch
inst, err := svc.Get(ctx, workspaceID, id)
if err != nil {
if errors.Is(err, lark.ErrInstallationNotFound) {
http.NotFound(w, r) // client refetches the list
return
}
return err
} Prevention
- Drive installation UIs off ListByWorkspace snapshots rather than cached ids.
- Treat 404 on an installation as a signal to refresh, not an error state.
- Scope all lookups by workspace_id to avoid cross-workspace id confusion.
When it happens
Trigger: Calling InstallationService Get/Revoke-style lookups (e.g. GetLarkInstallationInWorkspace) with a workspace_id + installation_id pair that has no row, or an installation that was revoked/removed. Distinct from ListByWorkspace, which returns both active and revoked installations and never errors on empties.
Common situations: Stale URL or cached UI referencing an installation deleted after a workspace re-setup; wrong workspace parameter (installation exists but in another workspace); revoked installation being addressed through a get-by-id path instead of the list view.
Related errors
- slack installation not found
- lark open_id is already bound to a different user
- redeemer is not a workspace member
- slack: binding token invalid or expired
- slack: user id is already bound to a different user
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/516e0b23a07bd35e.
Report an issue: GitHub.