{"record":{"id":"1b4c9780202e44b1","repo":"apache/superset","slug":"slack-v1-file-uploads-are-no-longer-supported-beca","errorCode":null,"errorMessage":"Slack v1 file uploads are no longer supported because Slack retired `files.upload`. Enable `ALERT_REPORT_SLACK_V2` and grant the Slack bot both the `channels:read` and `groups:read` scopes so the recipient can be upgraded to Slack v2. Slack v2 upgrade failed: {update_error}","messagePattern":"Slack v1 file uploads are no longer supported because Slack retired `files\\.upload`\\. Enable `ALERT_REPORT_SLACK_V2` and grant the Slack bot both the `channels:read` and `groups:read` scopes so the recipient can be upgraded to Slack v2\\. Slack v2 upgrade failed: (.+?)","errorType":"exception","errorClass":"UpdateFailedError","httpStatus":500,"severity":"error","filePath":"superset/commands/report/slack_upgrade.py","lineNumber":169,"sourceCode":"        for recipient, recipient_config_json in resolved:\n            recipient.type = ReportRecipientType.SLACKV2\n            recipient.recipient_config_json = recipient_config_json\n\n    def send_fallback(\n        self,\n        notification: SlackNotification,\n        content: NotificationContent,\n        update_error: NotificationParamException | UpdateFailedError,\n    ) -> None:\n        \"\"\"Deliver text through Slack v1 and record the first successful fallback.\"\"\"\n        if content.has_attachments:\n            record_statsd_gauge_failure(\"reports.slack.send\", update_error)\n            message = (\n                f\"{SLACK_V1_FILE_UPLOAD_MESSAGE} \"\n                f\"Slack v2 upgrade failed: {update_error}\"\n            )\n            if isinstance(update_error, UpdateFailedError):\n                raise UpdateFailedError(message) from update_error\n            raise NotificationParamException(message) from update_error\n\n        notification.send_legacy_text()\n        if self._fallback_recorded:\n            return\n\n        self._execution_warnings.append(\n            \"Slack v2 upgrade unavailable; delivered the text-only report \"\n            f\"through deprecated Slack v1: {update_error}\"\n        )\n        app.config[\"STATS_LOGGER\"].incr(\"reports.slack.v1_fallback\")\n        if isinstance(update_error, UpdateFailedError):\n            app.config[\"STATS_LOGGER\"].incr(\"reports.slack.v1_fallback.system_error\")\n            logger.error(\n                \"Slack v2 upgrade failed with a system error; delivered the \"\n                \"text-only report through Slack v1 for this execution: %s\",\n                update_error,\n                extra={","sourceCodeStart":151,"sourceCodeEnd":187,"githubUrl":"https://github.com/apache/superset/blob/f4587218dd19d046c3e4d00063e7d27f8a2ed354/superset/commands/report/slack_upgrade.py#L151-L187","documentation":"Raised by SlackUpgradeCommand when a report/alert with attachments could not be delivered because Slack retired the files.upload API (v1) and the automatic upgrade to Slack v2 also failed (update_error). It re-raises NotificationParamException or UpdateFailedError with a message explaining that ALERT_REPORT_SLACK_V2 must be enabled and the bot must hold channels:read and groups:read scopes.","triggerScenarios":"A report or alert delivery that produces attachments (content.has_attachments) targets Slack while the ALERT_REPORT_SLACK_V2 feature flag is off, or the flag is on but the bot token lacks channels:read/groups:read so the recipient upgrade to a v2 channel ID fails; the send path then falls back to v1, which can no longer upload files.","commonSituations":"Upgrading Superset past the Slack files.upload retirement without re-authorizing the Slack app; creating a new Slack bot token without the v2 scopes; feature flag ALERT_REPORT_SLACK_V2 left disabled in superset_config.py; delivering chart screenshots/PDF reports to private channels the bot cannot list.","solutions":["Enable the ALERT_REPORT_SLACK_V2 feature flag in superset_config.py: FEATURE_FLAGS = {'ALERT_REPORT_SLACK_V2': True}","Re-authorize the Slack app and grant the bot both channels:read and groups:read scopes, then reinstall it in the workspace","Invite the Slack bot to the target channel/group so the upgraded lookup (conversations.list) can resolve the recipient","Re-run the report/alert and verify the recipient was persisted with a v2 channel ID; text-only reports still deliver via v1 and only log a warning"],"exampleFix":"# before\nFEATURE_FLAGS = {'ALERT_REPORT_SLACK_V2': False}\n\n# after\nFEATURE_FLAGS = {'ALERT_REPORT_SLACK_V2': True}\n# and in the Slack app config, add bot scopes: channels:read, groups:read","handlingStrategy":"fallback","validationCode":"from superset.utils.core import feature_flag_manager  # or app.config\nv2_ok = app.config['FEATURE_FLAGS'].get('ALERT_REPORT_SLACK_V2')\nscopes_ok = {'channels:read', 'groups:read'} <= set(bot_token_scopes)\nassert v2_ok and scopes_ok or not report_has_attachments","typeGuard":"def can_deliver_attachments_v2(ff: dict, scopes: set[str]) -> bool:\n    return ff.get('ALERT_REPORT_SLACK_V2', False) and {'channels:read', 'groups:read'} <= scopes","tryCatchPattern":"from superset.commands.exceptions import UpdateFailedError\nfrom superset.reports.notifications.exceptions import NotificationParamException\ntry:\n    execute_report_delivery(report)\nexcept (NotificationParamException, UpdateFailedError) as e:\n    if 'Slack v1 file uploads' in str(e):\n        downgrade_to_text_only(report); alert_ops(e)\n    else:\n        raise","preventionTips":["Keep ALERT_REPORT_SLACK_V2 enabled in every environment that sends attachment reports","Track the bot's granted scopes in config review; alert when channels:read/groups:read go missing","Smoke-test Slack delivery after any Slack app re-install or token rotation"],"tags":["slack","alerts-reports","feature-flag","oauth-scopes","deprecated-api"],"backgroundTag":null,"analyzedSha":"f4587218dd19d046c3e4d00063e7d27f8a2ed354","analyzedAt":"2026-08-14T22:39:27.425Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}