{"record":{"id":"2718ce88dca593ac","repo":"langgenius/dify","slug":"app-unavailable-2718ce","errorCode":"app_unavailable","errorMessage":"App unavailable, please check your app configurations.","messagePattern":"App unavailable, please check your app configurations\\.","errorType":"console","errorClass":"AppUnavailableError","httpStatus":400,"severity":"error","filePath":"api/controllers/console/explore/completion.py","lineNumber":102,"sourceCode":"    \"/installed-apps/<uuid:installed_app_id>/completion-messages\",\n    endpoint=\"installed_app_completion\",\n)\nclass CompletionApi(InstalledAppResource):\n    @console_ns.expect(console_ns.models[CompletionMessageExplorePayload.__name__])\n    @console_ns.response(200, \"Success\")\n    @with_current_user\n    @with_session\n    @model_validate(CompletionMessageExplorePayload)\n    def post(\n        self,\n        req_data: CompletionMessageExplorePayload,\n        session: Session,\n        current_user: Account,\n        installed_app: InstalledApp,\n    ):\n        app_model = installed_app.app_with_session(session=session)\n        if app_model is None:\n            raise AppUnavailableError()\n        if app_model.mode != AppMode.COMPLETION:\n            raise NotCompletionAppError()\n\n        args = req_data.model_dump(exclude_none=True)\n\n        streaming = req_data.response_mode == \"streaming\"\n        args[\"auto_generate_name\"] = False\n\n        installed_app.last_used_at = naive_utc_now()\n        db.session.commit()\n\n        try:\n            response = AppGenerateService.generate(\n                session=session,\n                app_model=app_model,\n                user=current_user,\n                args=args,\n                invoke_from=InvokeFrom.EXPLORE,","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/langgenius/dify/blob/ef8544b173fd6cd7a8e71df2cab576e52bebbfbc/api/controllers/console/explore/completion.py#L84-L120","documentation":"Raised by CompletionApi.post (POST /console/installed-apps/<installed_app_id>/completion-messages) when installed_app.app_with_session() returns None, meaning the InstalledApp record exists but its referenced App model is missing from the database. Returns HTTP 400 with error_code 'app_unavailable'. The Explore 'installed app' layer is a pointer to a real App; if that App was deleted, archived, or became tenant-inaccessible after installation, the pointer dangles and this guard fires.","triggerScenarios":"POST /console/installed-apps/<uuid:installed_app_id>/completion-messages where the InstalledApp's underlying App row is gone (deleted by owner, migrated away, or tenant mismatch). The installed_app_required decorator runs a best-effort cleanup check but uses a separate session, so a race or a session-cache miss lets the request reach this guard.","commonSituations":"App owner deleted the app from the Studio after other users installed it from Explore; workspace/tenant data migration left orphan InstalledApp rows; the app was duplicated/moved and the original ID no longer resolves; cross-tenant access where app_id belongs to a different tenant.","solutions":["Reinstall the app from the Explore marketplace so a fresh InstalledApp row points at a live App.","Confirm the installed_app_id in the URL is current and was not copied from a stale bookmark.","Ask the workspace admin to verify the App still exists in Studio and is published.","If you are the admin, audit InstalledApp rows for orphaned app_id values and remove them."],"exampleFix":"// before: calling completion on a stale installed_app_id\nawait fetch(`/console/installed-apps/${staleInstalledAppId}/completion-messages`, {method:'POST', body: ...})\n\n// after: re-fetch the installed app list first, then call with a known-good id\nconst installed = await fetch('/console/installed-apps').then(r => r.json())\nconst live = installed.data.find(i => i.app_id === expectedAppId)\nawait fetch(`/console/installed-apps/${live.id}/completion-messages`, {method:'POST', body: ...})","handlingStrategy":"validation","validationCode":"// Before posting to completion-messages, confirm the installed app resolves to a live app.\nasync function getLiveAppId(installedAppId, headers) {\n  const res = await fetch(`/console/installed-apps`, { headers });\n  if (!res.ok) return null;\n  const list = await res.json();\n  const entry = (list.data ?? []).find(i => i.id === installedAppId);\n  if (!entry) return null;\n  // Explore deletes InstalledApp rows whose app is gone, so presence implies a live app.\n  return entry.id;\n}\n// usage\nconst liveId = await getLiveAppId(installedAppId, headers);\nif (!liveId) { /* prompt reinstall instead of calling */ }","typeGuard":"function isUsableInstalledApp(entry) {\n  return Boolean(entry && entry.id && entry.app_id && entry.tenant_id);\n}","tryCatchPattern":"try {\n  await postCompletion(installedAppId, payload);\n} catch (err) {\n  if (err.code === 'app_unavailable') {\n    // reconcile: refetch installed apps, prompt reinstall, do not retry same id\n    await refreshInstalledApps();\n  } else { throw err; }\n}","preventionTips":["Cache installed_app_id only for the session; re-fetch the installed-apps list on reload to drop stale ids.","When an app owner deletes an app, propagate that to installed-app consumers via a refresh.","Do not hardcode installed_app_id values in clients; always resolve from the API."],"tags":["explore","installed-app","completion","app-configuration","data-integrity"],"backgroundTag":null,"analyzedSha":"ef8544b173fd6cd7a8e71df2cab576e52bebbfbc","analyzedAt":"2026-08-12T05:15:17.394Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}