{"owner":"slackapi","repo":"python-slack-sdk","hasSkills":true,"totalSkillsCount":3,"totalTokensCount":4806,"categories":["anthropic-skill","root-instruction","claude-rule"],"hasMcp":false,"mcpConfig":null,"found":[".claude/skills/managing-dependencies/SKILL.md","AGENTS.md",".claude/CLAUDE.md"],"skills":{".claude/skills/managing-dependencies/SKILL.md":"---\nname: managing-dependencies\ndescription: >-\n  Use when adding, bumping, pinning, or reviewing a dependency in requirements.txt or requirements/*.txt, including taking or fixing a Dependabot PR. Also use when pip install fails on some Python versions in CI but not others (\"Could not find a version that satisfies the requirement\", \"No matching distribution found\", \"Requires-Python\", \"ResolutionImpossible\"), including PyPy-only failures, and when dropping support for an old Python version.\n---\n\n# Managing dependencies\n\nThis repo supports a range of Python versions, and one set of requirements files has to install on all of them. Dependabot proposes the newest release, so its bumps often raise a lower bound past what the oldest Pythons can install, and CI fails at the install step on those jobs only. The fix is to split the requirement with PEP 508 `python_version` markers so each Python gets the newest release it can install. Dropping an old Python to turn CI green is not the fix.\n\n## Where the facts live\n\nRead these instead of trusting memory; they change often.\n\n- **Supported Pythons:** `requires-python` and the `Programming Language :: Python :: 3.X` classifiers in `pyproject.toml`, and the CI matrix.\n- **Who installs a file, on which Python:** `grep -rn requirements .github/workflows scripts requirements pyproject.toml`. This also catches `-r` includes. For a hit in a script, find the CI job that runs it and read its `python-version`.\n- **Whether a file ships to users:** a file that `pyproject.toml` reads into package metadata (`dependencies` or `optional-dependencies` under `[tool.setuptools.dynamic]`) ends up in the published wheel.\n\n## Workflow\n\n- [ ] 1. Find which Pythons install the file. A file installed only on the newest Python takes the bump as-is.\n- [ ] 2. Find the newest release each affected Python can install (see \"Finding the ceiling\").\n- [ ] 3. If every Python the line covers can install the new lower bound, take the bump as one line. Done.\n- [ ] 4. Otherwise split the line (see \"Splitting a line\").\n- [ ] 5. Re-read the section: no Python matches two lines, and the newest line is open-ended.\n\nChange only the section you are fixing; don't reformat other sections in the same change.\n\n## File layout\n\nEach file starts with a `# pip install -r <path>` header, then one section per dependency: a `# <name>` line, optional `# Note:` lines, the requirement line(s), and a blank line.\n\n```\n# pip install -r requirements/testing.txt\n\n# pkg\npkg>=1.4,<2; python_version < \"3.9\"\npkg>=2.0,<2.3; python_version == \"3.9\"\npkg>=2.5.1,<3; python_version >= \"3.10\"\n\n# other-pkg\n# Note: other-pkg 4.x breaks on Python 3.8 but declares no requires_python, so pip can't filter it.\nother-pkg>=3,<4\n```\n\n**Notes:** write one only for what the lines can't say: a cap that isn't obvious, a coupling with another package, or a release with wrong metadata. Don't restate versions or which Python gets which release; a routine split needs no note. Existing notes that only restate their lines are legacy: drop one when you edit its section, and don't copy it.\n\n## Finding the ceiling\n\nAn old Python's ceiling is the first release that dropped it, not the release Dependabot proposes. If `pkg` dropped 3.9 at 2.3 and the target is 2.5.1, the 3.9 line is `<2.3`, since 2.3 through 2.5.0 are 3.10-only too.\n\n- **From a red CI job:** the failing install log is ground truth. The lowest version on its `Ignored the following versions that require a different python version` line is that Python's ceiling.\n- **For any Python:** `pip index versions <pkg> --python-version 3.9` lists what 3.9 can install, newest first. Run it again for the next Python up; the first release newer than 3.9's newest is 3.9's ceiling.\n- **The pre-bump lower bound** is in the Dependabot PR title (`update pkg requirement from <3,>=2.0 to >=2.5.1,<3`) or `git diff`.\n\n## Splitting a line\n\nSplit only the Pythons that the bumped line covers and the new lower bound excludes. Leave existing bands for older Pythons alone, even when they are coarser than they could be.\n\n- Lines run oldest Python to newest. The newest line takes the bump and stays open-ended (`>= \"X.Y\"`).\n- Each band that splits off keeps the line's pre-bump lower bound and gets its ceiling from \"Finding the ceiling\". Pythons that share a ceiling share a band.\n- Marker style: `pkg>=1,<2; python_version == \"3.9\"`. Spaces around operators, double-quoted `\"X.Y\"`, one space after `;`. A one-Python band is `== \"X.Y\"`; a band spanning several is `>= \"X.Y\" and < \"X.Z\"`, or `< \"X.Z\"` when it is the oldest. Boundaries use `>=` and `<` so each Python lands on exactly one side. `python_version` is major.minor only (3.9.5 reports `\"3.9\"`), so `== \"3.9\"` covers every 3.9 patch; don't use `python_full_version` here, where `== \"3.9\"` matches only 3.9.0.\n\nA section with a single gated line (installed only on the newest Python, or only on PyPy) is intentional; don't fill in the other Pythons.\n\n## Gotchas\n\n- **Dependabot rewrites only the last line** of a split section, the open-ended one. When it bumps a split package, check which Pythons that line covers; the new lower bound may exclude the oldest of them, which then splits off.\n- **Releases with no `requires_python`** can't be filtered by pip, so they install on Pythons they don't support and fail at import or test time rather than at install. `pip index` lists them too. Pin an explicit ceiling with a note saying why. When the culprit comes in loosely through another package, pin it in that package's section so the coupling stays visible.\n- **Only PyPy jobs fail:** the release usually dropped PyPy wheels. Gate on the implementation instead of downgrading CPython: `pkg<46; implementation_name == \"pypy\" and python_version == \"3.10\"`. Lowercase `pypy`, and `implementation_name`, not `platform_python_implementation`. No unmarked companion line is needed.\n- **Files that ship:** full-line comments only; an inline comment breaks the package build. Their lower bounds are the minimum users must install, so raise one only for a reason.\n- **Not this pattern:** a real test failure, or an install failure on every Python. Investigate the bump normally.\n\n## Dropping a Python version\n\nWhen a Python leaves the classifiers and the CI matrix, merge any band that only served it into its neighbor. If a section is left with one line, remove its marker and the `;`.\n\n## Leave alone\n\n- `requires-python`, the classifiers, and the CI matrix. Changing which Pythons are supported is its own decision.\n- Packages under `ignore` in `.github/dependabot.yml`: maintainers bump those by hand on purpose. Prefer a marker split over adding a new `ignore`, which freezes the package on every Python.\n","AGENTS.md":"# AGENTS.md — Python Slack SDK\n\n## Project Overview\n\nThe Python Slack SDK (`slack_sdk`) is a modular Python library for interacting with the Slack platform APIs. It is published on PyPI as `slack-sdk`. The SDK provides independent packages for each Slack API surface: Web API, Webhooks, Socket Mode, OAuth, Audit Logs, SCIM, RTM, Block Kit models, and request signature verification.\n\n- **Repository**: <https://github.com/slackapi/python-slack-sdk>\n- **Documentation**: <https://docs.slack.dev/tools/python-slack-sdk/>\n- **PyPI**: <https://pypi.org/project/slack-sdk/>\n- **Current version**: defined in `slack_sdk/version.py`\n\n## Critical Rules\n\nThese are the most important constraints in this project. Violating any of them will break CI or corrupt auto-generated code:\n\n1. **Never edit auto-generated files.** The following files are produced by `scripts/codegen.py` and must not be modified directly:\n   - `slack_sdk/web/async_client.py`\n   - `slack_sdk/web/legacy_client.py`\n   - `slack_sdk/web/async_chat_stream.py`\n\n   Edit the source files (`client.py` or `chat_stream.py`) instead, then run codegen (see [Code Generation](#code-generation-critical-pattern)).\n\n2. **Zero runtime dependencies.** The core sync Web API client must have no required runtime dependencies. Do not add entries to `install_requires` / `dependencies` in `pyproject.toml`.\n\n3. **Do not modify the legacy `slack/` package.** It is in maintenance mode and only re-exports from `slack_sdk` with deprecation warnings. All new development goes in `slack_sdk/`.\n\n4. **Always run codegen + format after editing `client.py` or `chat_stream.py`:**\n\n   ```sh\n   python scripts/codegen.py --path .\n   ./scripts/format.sh\n   ```\n\n5. **Use project scripts, not raw tool commands.** Run `./scripts/run_tests.sh`, not `pytest` directly. The scripts handle codegen and formatting.\n\n## Architecture\n\n### Package Structure\n\nThe SDK is organized into independent sub-packages:\n\n- **`slack_sdk/web/`** — Web API client (sync, async, legacy). Contains auto-generated files (see [Code Generation](#code-generation-critical-pattern))\n- **`slack_sdk/webhook/`** — Incoming Webhooks\n- **`slack_sdk/socket_mode/`** — Socket Mode with pluggable backends\n- **`slack_sdk/oauth/`** — OAuth flows and token storage\n- **`slack_sdk/models/`** — Block Kit UI builders\n- **`slack_sdk/audit_logs/`**, **`slack_sdk/scim/`** — Enterprise APIs\n- **`slack_sdk/signature/`** — Request verification\n- **`slack_sdk/http_retry/`** — Retry handlers\n- **`slack/`** — Legacy package (maintenance mode, do not modify)\n\nSee the repository structure for the complete package layout.\n\n### Code Generation (Critical Pattern)\n\n**NEVER edit these auto-generated files:**\n\n- `slack_sdk/web/async_client.py`\n- `slack_sdk/web/legacy_client.py`\n- `slack_sdk/web/async_chat_stream.py`\n\nEach contains a header warning:\n```text\n# DO NOT EDIT THIS FILE\n# 1) Modify slack_sdk/web/client.py\n# 2) Run `python scripts/codegen.py`\n# 3) Run `ruff format slack_sdk/`\n```\n\n**How it works:**\n\n1. Edit `slack_sdk/web/client.py` (canonical source for Web API methods)\n2. Edit `slack_sdk/web/chat_stream.py` (canonical source for streaming chat)\n3. Run `python scripts/codegen.py --path .` to generate async/legacy variants\n4. Run `./scripts/format.sh` to format the generated code\n\nThe codegen script (`scripts/codegen.py`) automatically transforms sync code into async variants by adding `async def`, `await`, and replacing classes with async equivalents.\n\n### Web API Method Pattern\n\nEvery Web API method in `client.py` follows this pattern:\n\n```python\ndef method_name(\n    self,\n    *,                              # keyword-only arguments\n    required_param: str,\n    optional_param: Optional[str] = None,\n    **kwargs,\n) -> SlackResponse:\n    \"\"\"Description of the API method\n    https://docs.slack.dev/reference/methods/method.name\n    \"\"\"\n    kwargs.update({\"required_param\": required_param})\n    if optional_param is not None:\n        kwargs.update({\"optional_param\": optional_param})\n    return self.api_call(\"method.name\", params=kwargs)\n```\n\nKey conventions:\n\n- All parameters are keyword-only (after `*`)\n- Required params have no default; optional params default to `None`\n- `**kwargs` captures additional/undocumented parameters\n- Parameters are collected into `kwargs` dict and passed to `self.api_call()`\n- The `api_call` method name uses Slack's dot-notation (e.g., `\"chat.postMessage\"`)\n- Docstrings include a link to the Slack API reference\n\n### Error Types\n\nAll SDK exceptions are defined in `slack_sdk/errors/__init__.py` and inherit from `SlackClientError`.\n\nKey exceptions to be aware of:\n\n- **`SlackApiError`** — Raised when the API returns an error response (carries the `response` object)\n- **`SlackRequestError`** — Raised when the HTTP request itself fails\n- **`BotUserAccessError`** — Raised when using a bot token (`xoxb-*`) for a user-only method\n\nSee `slack_sdk/errors/__init__.py` for the complete list of exception types and their usage.\n\n### HTTP Retry Handlers\n\nThe `slack_sdk/http_retry/` module provides built-in retry strategies for connection errors, rate limiting (HTTP 429), and server errors (HTTP 500/503).\n\nKey handlers:\n\n- **`ConnectionErrorRetryHandler`** / **`AsyncConnectionErrorRetryHandler`**\n- **`RateLimitErrorRetryHandler`** / **`AsyncRateLimitErrorRetryHandler`** (respects `Retry-After` header)\n- **`ServerErrorRetryHandler`** / **`AsyncServerErrorRetryHandler`**\n\nRetry intervals can be configured with `BackoffRetryIntervalCalculator` (exponential backoff) or `FixedValueRetryIntervalCalculator`. See `slack_sdk/http_retry/` for implementation details.\n\n### Test Patterns\n\nTests use `unittest.TestCase` with a mock web API server:\n\n```python\nimport unittest\nfrom slack_sdk import WebClient\nfrom tests.slack_sdk.web.mock_web_api_handler import MockHandler\nfrom tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server\n\nclass TestFeature(unittest.TestCase):\n    def setUp(self):\n        setup_mock_web_api_server(self, MockHandler)\n        self.client = WebClient(\n            token=\"xoxb-api_test\",\n            base_url=\"http://localhost:8888\",\n        )\n\n    def tearDown(self):\n        cleanup_mock_web_api_server(self)\n\n    def test_something(self):\n        resp = self.client.api_test()\n        self.assertTrue(resp[\"ok\"])\n```\n\nEach sub-package has its own `MockHandler` (e.g., `tests/slack_sdk/webhook/mock_web_api_handler.py`, `tests/slack_sdk/scim/mock_web_api_handler.py`). Use the handler from the matching sub-package.\n\nTest directories:\n\n- `tests/` — Unit tests (mirroring `slack_sdk/` structure)\n  - `tests/slack_sdk/` — Sync tests\n  - `tests/slack_sdk_async/` — Async variants\n  - `tests/slack_sdk_fixture/` — Pytest fixtures and test data\n  - `tests/data/` — JSON fixture files\n  - `tests/mock_web_api_server/` — Mock Slack API server\n- `integration_tests/` — Tests against real Slack APIs (require env tokens)\n\n## Development Commands\n\n### Setup\n\n**Prerequisites:** A Python virtual environment must be activated. See `.github/maintainers_guide.md` for detailed setup instructions using `pyenv` and `venv`.\n\n**Quick check:** Verify venv is active with `echo $VIRTUAL_ENV` (should output a path).\n\nAlways use the project scripts instead of calling tools like `pytest` directly.\n\n### Install Dependencies\n\n```sh\n./scripts/install.sh\n```\n\nInstalls all project dependencies (testing, optional, and tools) via pip. This script is called automatically by `run_validation.sh`, `run_integration_tests.sh`, and `run_mypy.sh`, so you typically don't need to run it separately.\n\n### Full Validation\n\n```sh\n./scripts/run_validation.sh\n```\n\nThis is the canonical check — CI runs this and the PR template asks contributors to run it. It installs requirements, runs codegen, formats, lints, runs tests with coverage, and runs mypy type checking on the latest supported Python version (check the `LATEST_SUPPORTED_PY` environment variable in `.github/workflows/ci-build.yml`).\n\n### Individual Commands\n\nAvailable scripts in the `scripts/` directory:\n\n| Task | Command |\n| --- | --- |\n| Install dependencies | `./scripts/install.sh` |\n| Uninstall all packages | `./scripts/uninstall_all.sh` |\n| Format code | `./scripts/format.sh` |\n| Lint (check formatting) | `./scripts/lint.sh` |\n| Run all unit tests | `./scripts/run_tests.sh` |\n| Run a specific test | `./scripts/run_tests.sh tests/slack_sdk/web/test_web_client.py` |\n| Run type checking | `./scripts/run_mypy.sh` |\n| Generate async/legacy code | `python scripts/codegen.py --path .` |\n| Build PyPI package | `./scripts/build_pypi_package.sh` |\n| Generate API docs | `./scripts/generate_api_docs.sh` |\n\n## Code Style & Tooling\n\nAll tooling configuration is defined in the following files:\n\n- **Formatter**: `ruff format` — see `[tool.ruff]` in `pyproject.toml`\n- **Linter**: `ruff check` — see `[tool.ruff.lint]` in `pyproject.toml`\n- **Type checker**: `mypy` — see `[tool.mypy]` in `pyproject.toml`\n- **Test runner**: `pytest` — see `[tool.pytest.ini_options]` in `pyproject.toml`\n- **Coverage**: `pytest-cov` reporting to Codecov\n- **Build system**: see `[build-system]` and `[project]` in `pyproject.toml`\n\n**Dependencies:**\n\n- Testing: `requirements/testing.txt`\n- Optional runtime: `requirements/optional.txt`\n- Dev tools (ruff, mypy): `requirements/tools.txt`\n\n## CI Pipeline (GitHub Actions)\n\nDefined in `.github/workflows/ci-build.yml`, runs on push to `main`, all PRs, and daily schedule. Check the workflow file for the current Python version matrix.\n\n## Key Files & Directories\n\n**Source Code:**\n\n- `slack_sdk/` — Main package (active development)\n- `slack_sdk/web/client.py` — **CANONICAL SOURCE** for all Web API methods\n- `slack_sdk/web/chat_stream.py` — Canonical streaming chat client\n- `slack_sdk/version.py` — Single source of truth for version\n- `slack/` — Legacy package (maintenance mode, DO NOT MODIFY)\n\n**Configuration:**\n\n- `pyproject.toml` — Project metadata, build config, tool settings (ruff, pytest, mypy)\n- `requirements/*.txt` — Dependency specifications\n\n**Tooling:**\n\n- `scripts/codegen.py` — Generates async/legacy client variants\n- `scripts/*.sh` — Development and CI helper scripts\n\n**GitHub & CI/CD:**\n\n- `.github/` — GitHub-specific configuration and documentation\n- `.github/workflows/` — Continuous integration pipeline definitions that run on GitHub Actions\n- `.github/maintainers_guide.md` — Maintainer workflows and release process\n\n**Documentation:**\n\n- `README.md` — Project overview, installation, and usage examples\n- `.github/maintainers_guide.md` — Maintainer workflows and release process\n\n## Common Contribution Workflows\n\n### Adding a New Web API Method\n\n1. Add the method to `slack_sdk/web/client.py` following the existing pattern\n2. Run code generation: `python scripts/codegen.py --path .`\n3. Run formatter: `./scripts/format.sh`\n4. Add tests in `tests/slack_sdk/web/`\n5. Validate: `./scripts/run_validation.sh`\n\n### Adding a New Feature to a Non-Web Module\n\n1. Implement the sync version in the appropriate `slack_sdk/` subpackage\n2. If the module has async variants, implement those as well (not auto-generated for non-web modules)\n3. Add tests mirroring the module structure\n4. Validate: `./scripts/run_validation.sh`\n\n### Adding a New Block Kit Type\n\nSee [`slack_sdk/models/AGENTS.md`](slack_sdk/models/AGENTS.md) for detailed steps on defining, registering, exporting, and testing new Block Kit blocks, elements, and composition objects.\n\n### Fixing a Bug\n\n1. Write a test that reproduces the bug\n2. Fix the code (if in `client.py`, run codegen afterward)\n3. Validate: `./scripts/run_validation.sh`\n\n## Versioning & Releases\n\n- Use the new version mentioned by the maintainer; if they do not provide it, prompt them for it.\n- Ensure the new version follows [Semantic Versioning](http://semver.org/) via [PEP 440](https://peps.python.org/pep-0440/)\n- Version lives in `slack_sdk/version.py` and is dynamically read by `pyproject.toml`\n- Releases are triggered by publishing a GitHub Release, which triggers the PyPI deployment workflow; the maintainer will take care of this\n- Commit message format for releases: `chore(release): version X.Y.Z`\n\n## Dependencies\n\nThe SDK has **zero required runtime dependencies** for the core sync Web API client and it is imperative it remains that way. Optional dependencies enable additional functionality:\n\n- `aiohttp` — async HTTP client\n- `websockets` / `websocket-client` — Socket Mode backends\n- `SQLAlchemy` — OAuth token storage\n- `boto3` — S3/DynamoDB token storage\n- `aiodns` — faster DNS resolution for async\n\nVersion constraints for optional and dev dependencies are pinned in the `requirements/` directory.\n\nBefore adding, bumping, pinning, or reviewing any dependency in `requirements/*.txt`, whether a Dependabot PR or a manual edit, follow the `managing-dependencies` skill in `.claude/skills/`. It defines the layout and `python_version` marker conventions that keep one set of pins working across the full Python matrix.\n",".claude/CLAUDE.md":"@../AGENTS.md\n"},"files":{".claude/skills/managing-dependencies/SKILL.md":"---\nname: managing-dependencies\ndescription: >-\n  Use when adding, bumping, pinning, or reviewing a dependency in requirements.txt or requirements/*.txt, including taking or fixing a Dependabot PR. Also use when pip install fails on some Python versions in CI but not others (\"Could not find a version that satisfies the requirement\", \"No matching distribution found\", \"Requires-Python\", \"ResolutionImpossible\"), including PyPy-only failures, and when dropping support for an old Python version.\n---\n\n# Managing dependencies\n\nThis repo supports a range of Python versions, and one set of requirements files has to install on all of them. Dependabot proposes the newest release, so its bumps often raise a lower bound past what the oldest Pythons can install, and CI fails at the install step on those jobs only. The fix is to split the requirement with PEP 508 `python_version` markers so each Python gets the newest release it can install. Dropping an old Python to turn CI green is not the fix.\n\n## Where the facts live\n\nRead these instead of trusting memory; they change often.\n\n- **Supported Pythons:** `requires-python` and the `Programming Language :: Python :: 3.X` classifiers in `pyproject.toml`, and the CI matrix.\n- **Who installs a file, on which Python:** `grep -rn requirements .github/workflows scripts requirements pyproject.toml`. This also catches `-r` includes. For a hit in a script, find the CI job that runs it and read its `python-version`.\n- **Whether a file ships to users:** a file that `pyproject.toml` reads into package metadata (`dependencies` or `optional-dependencies` under `[tool.setuptools.dynamic]`) ends up in the published wheel.\n\n## Workflow\n\n- [ ] 1. Find which Pythons install the file. A file installed only on the newest Python takes the bump as-is.\n- [ ] 2. Find the newest release each affected Python can install (see \"Finding the ceiling\").\n- [ ] 3. If every Python the line covers can install the new lower bound, take the bump as one line. Done.\n- [ ] 4. Otherwise split the line (see \"Splitting a line\").\n- [ ] 5. Re-read the section: no Python matches two lines, and the newest line is open-ended.\n\nChange only the section you are fixing; don't reformat other sections in the same change.\n\n## File layout\n\nEach file starts with a `# pip install -r <path>` header, then one section per dependency: a `# <name>` line, optional `# Note:` lines, the requirement line(s), and a blank line.\n\n```\n# pip install -r requirements/testing.txt\n\n# pkg\npkg>=1.4,<2; python_version < \"3.9\"\npkg>=2.0,<2.3; python_version == \"3.9\"\npkg>=2.5.1,<3; python_version >= \"3.10\"\n\n# other-pkg\n# Note: other-pkg 4.x breaks on Python 3.8 but declares no requires_python, so pip can't filter it.\nother-pkg>=3,<4\n```\n\n**Notes:** write one only for what the lines can't say: a cap that isn't obvious, a coupling with another package, or a release with wrong metadata. Don't restate versions or which Python gets which release; a routine split needs no note. Existing notes that only restate their lines are legacy: drop one when you edit its section, and don't copy it.\n\n## Finding the ceiling\n\nAn old Python's ceiling is the first release that dropped it, not the release Dependabot proposes. If `pkg` dropped 3.9 at 2.3 and the target is 2.5.1, the 3.9 line is `<2.3`, since 2.3 through 2.5.0 are 3.10-only too.\n\n- **From a red CI job:** the failing install log is ground truth. The lowest version on its `Ignored the following versions that require a different python version` line is that Python's ceiling.\n- **For any Python:** `pip index versions <pkg> --python-version 3.9` lists what 3.9 can install, newest first. Run it again for the next Python up; the first release newer than 3.9's newest is 3.9's ceiling.\n- **The pre-bump lower bound** is in the Dependabot PR title (`update pkg requirement from <3,>=2.0 to >=2.5.1,<3`) or `git diff`.\n\n## Splitting a line\n\nSplit only the Pythons that the bumped line covers and the new lower bound excludes. Leave existing bands for older Pythons alone, even when they are coarser than they could be.\n\n- Lines run oldest Python to newest. The newest line takes the bump and stays open-ended (`>= \"X.Y\"`).\n- Each band that splits off keeps the line's pre-bump lower bound and gets its ceiling from \"Finding the ceiling\". Pythons that share a ceiling share a band.\n- Marker style: `pkg>=1,<2; python_version == \"3.9\"`. Spaces around operators, double-quoted `\"X.Y\"`, one space after `;`. A one-Python band is `== \"X.Y\"`; a band spanning several is `>= \"X.Y\" and < \"X.Z\"`, or `< \"X.Z\"` when it is the oldest. Boundaries use `>=` and `<` so each Python lands on exactly one side. `python_version` is major.minor only (3.9.5 reports `\"3.9\"`), so `== \"3.9\"` covers every 3.9 patch; don't use `python_full_version` here, where `== \"3.9\"` matches only 3.9.0.\n\nA section with a single gated line (installed only on the newest Python, or only on PyPy) is intentional; don't fill in the other Pythons.\n\n## Gotchas\n\n- **Dependabot rewrites only the last line** of a split section, the open-ended one. When it bumps a split package, check which Pythons that line covers; the new lower bound may exclude the oldest of them, which then splits off.\n- **Releases with no `requires_python`** can't be filtered by pip, so they install on Pythons they don't support and fail at import or test time rather than at install. `pip index` lists them too. Pin an explicit ceiling with a note saying why. When the culprit comes in loosely through another package, pin it in that package's section so the coupling stays visible.\n- **Only PyPy jobs fail:** the release usually dropped PyPy wheels. Gate on the implementation instead of downgrading CPython: `pkg<46; implementation_name == \"pypy\" and python_version == \"3.10\"`. Lowercase `pypy`, and `implementation_name`, not `platform_python_implementation`. No unmarked companion line is needed.\n- **Files that ship:** full-line comments only; an inline comment breaks the package build. Their lower bounds are the minimum users must install, so raise one only for a reason.\n- **Not this pattern:** a real test failure, or an install failure on every Python. Investigate the bump normally.\n\n## Dropping a Python version\n\nWhen a Python leaves the classifiers and the CI matrix, merge any band that only served it into its neighbor. If a section is left with one line, remove its marker and the `;`.\n\n## Leave alone\n\n- `requires-python`, the classifiers, and the CI matrix. Changing which Pythons are supported is its own decision.\n- Packages under `ignore` in `.github/dependabot.yml`: maintainers bump those by hand on purpose. Prefer a marker split over adding a new `ignore`, which freezes the package on every Python.\n","AGENTS.md":"# AGENTS.md — Python Slack SDK\n\n## Project Overview\n\nThe Python Slack SDK (`slack_sdk`) is a modular Python library for interacting with the Slack platform APIs. It is published on PyPI as `slack-sdk`. The SDK provides independent packages for each Slack API surface: Web API, Webhooks, Socket Mode, OAuth, Audit Logs, SCIM, RTM, Block Kit models, and request signature verification.\n\n- **Repository**: <https://github.com/slackapi/python-slack-sdk>\n- **Documentation**: <https://docs.slack.dev/tools/python-slack-sdk/>\n- **PyPI**: <https://pypi.org/project/slack-sdk/>\n- **Current version**: defined in `slack_sdk/version.py`\n\n## Critical Rules\n\nThese are the most important constraints in this project. Violating any of them will break CI or corrupt auto-generated code:\n\n1. **Never edit auto-generated files.** The following files are produced by `scripts/codegen.py` and must not be modified directly:\n   - `slack_sdk/web/async_client.py`\n   - `slack_sdk/web/legacy_client.py`\n   - `slack_sdk/web/async_chat_stream.py`\n\n   Edit the source files (`client.py` or `chat_stream.py`) instead, then run codegen (see [Code Generation](#code-generation-critical-pattern)).\n\n2. **Zero runtime dependencies.** The core sync Web API client must have no required runtime dependencies. Do not add entries to `install_requires` / `dependencies` in `pyproject.toml`.\n\n3. **Do not modify the legacy `slack/` package.** It is in maintenance mode and only re-exports from `slack_sdk` with deprecation warnings. All new development goes in `slack_sdk/`.\n\n4. **Always run codegen + format after editing `client.py` or `chat_stream.py`:**\n\n   ```sh\n   python scripts/codegen.py --path .\n   ./scripts/format.sh\n   ```\n\n5. **Use project scripts, not raw tool commands.** Run `./scripts/run_tests.sh`, not `pytest` directly. The scripts handle codegen and formatting.\n\n## Architecture\n\n### Package Structure\n\nThe SDK is organized into independent sub-packages:\n\n- **`slack_sdk/web/`** — Web API client (sync, async, legacy). Contains auto-generated files (see [Code Generation](#code-generation-critical-pattern))\n- **`slack_sdk/webhook/`** — Incoming Webhooks\n- **`slack_sdk/socket_mode/`** — Socket Mode with pluggable backends\n- **`slack_sdk/oauth/`** — OAuth flows and token storage\n- **`slack_sdk/models/`** — Block Kit UI builders\n- **`slack_sdk/audit_logs/`**, **`slack_sdk/scim/`** — Enterprise APIs\n- **`slack_sdk/signature/`** — Request verification\n- **`slack_sdk/http_retry/`** — Retry handlers\n- **`slack/`** — Legacy package (maintenance mode, do not modify)\n\nSee the repository structure for the complete package layout.\n\n### Code Generation (Critical Pattern)\n\n**NEVER edit these auto-generated files:**\n\n- `slack_sdk/web/async_client.py`\n- `slack_sdk/web/legacy_client.py`\n- `slack_sdk/web/async_chat_stream.py`\n\nEach contains a header warning:\n```text\n# DO NOT EDIT THIS FILE\n# 1) Modify slack_sdk/web/client.py\n# 2) Run `python scripts/codegen.py`\n# 3) Run `ruff format slack_sdk/`\n```\n\n**How it works:**\n\n1. Edit `slack_sdk/web/client.py` (canonical source for Web API methods)\n2. Edit `slack_sdk/web/chat_stream.py` (canonical source for streaming chat)\n3. Run `python scripts/codegen.py --path .` to generate async/legacy variants\n4. Run `./scripts/format.sh` to format the generated code\n\nThe codegen script (`scripts/codegen.py`) automatically transforms sync code into async variants by adding `async def`, `await`, and replacing classes with async equivalents.\n\n### Web API Method Pattern\n\nEvery Web API method in `client.py` follows this pattern:\n\n```python\ndef method_name(\n    self,\n    *,                              # keyword-only arguments\n    required_param: str,\n    optional_param: Optional[str] = None,\n    **kwargs,\n) -> SlackResponse:\n    \"\"\"Description of the API method\n    https://docs.slack.dev/reference/methods/method.name\n    \"\"\"\n    kwargs.update({\"required_param\": required_param})\n    if optional_param is not None:\n        kwargs.update({\"optional_param\": optional_param})\n    return self.api_call(\"method.name\", params=kwargs)\n```\n\nKey conventions:\n\n- All parameters are keyword-only (after `*`)\n- Required params have no default; optional params default to `None`\n- `**kwargs` captures additional/undocumented parameters\n- Parameters are collected into `kwargs` dict and passed to `self.api_call()`\n- The `api_call` method name uses Slack's dot-notation (e.g., `\"chat.postMessage\"`)\n- Docstrings include a link to the Slack API reference\n\n### Error Types\n\nAll SDK exceptions are defined in `slack_sdk/errors/__init__.py` and inherit from `SlackClientError`.\n\nKey exceptions to be aware of:\n\n- **`SlackApiError`** — Raised when the API returns an error response (carries the `response` object)\n- **`SlackRequestError`** — Raised when the HTTP request itself fails\n- **`BotUserAccessError`** — Raised when using a bot token (`xoxb-*`) for a user-only method\n\nSee `slack_sdk/errors/__init__.py` for the complete list of exception types and their usage.\n\n### HTTP Retry Handlers\n\nThe `slack_sdk/http_retry/` module provides built-in retry strategies for connection errors, rate limiting (HTTP 429), and server errors (HTTP 500/503).\n\nKey handlers:\n\n- **`ConnectionErrorRetryHandler`** / **`AsyncConnectionErrorRetryHandler`**\n- **`RateLimitErrorRetryHandler`** / **`AsyncRateLimitErrorRetryHandler`** (respects `Retry-After` header)\n- **`ServerErrorRetryHandler`** / **`AsyncServerErrorRetryHandler`**\n\nRetry intervals can be configured with `BackoffRetryIntervalCalculator` (exponential backoff) or `FixedValueRetryIntervalCalculator`. See `slack_sdk/http_retry/` for implementation details.\n\n### Test Patterns\n\nTests use `unittest.TestCase` with a mock web API server:\n\n```python\nimport unittest\nfrom slack_sdk import WebClient\nfrom tests.slack_sdk.web.mock_web_api_handler import MockHandler\nfrom tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server\n\nclass TestFeature(unittest.TestCase):\n    def setUp(self):\n        setup_mock_web_api_server(self, MockHandler)\n        self.client = WebClient(\n            token=\"xoxb-api_test\",\n            base_url=\"http://localhost:8888\",\n        )\n\n    def tearDown(self):\n        cleanup_mock_web_api_server(self)\n\n    def test_something(self):\n        resp = self.client.api_test()\n        self.assertTrue(resp[\"ok\"])\n```\n\nEach sub-package has its own `MockHandler` (e.g., `tests/slack_sdk/webhook/mock_web_api_handler.py`, `tests/slack_sdk/scim/mock_web_api_handler.py`). Use the handler from the matching sub-package.\n\nTest directories:\n\n- `tests/` — Unit tests (mirroring `slack_sdk/` structure)\n  - `tests/slack_sdk/` — Sync tests\n  - `tests/slack_sdk_async/` — Async variants\n  - `tests/slack_sdk_fixture/` — Pytest fixtures and test data\n  - `tests/data/` — JSON fixture files\n  - `tests/mock_web_api_server/` — Mock Slack API server\n- `integration_tests/` — Tests against real Slack APIs (require env tokens)\n\n## Development Commands\n\n### Setup\n\n**Prerequisites:** A Python virtual environment must be activated. See `.github/maintainers_guide.md` for detailed setup instructions using `pyenv` and `venv`.\n\n**Quick check:** Verify venv is active with `echo $VIRTUAL_ENV` (should output a path).\n\nAlways use the project scripts instead of calling tools like `pytest` directly.\n\n### Install Dependencies\n\n```sh\n./scripts/install.sh\n```\n\nInstalls all project dependencies (testing, optional, and tools) via pip. This script is called automatically by `run_validation.sh`, `run_integration_tests.sh`, and `run_mypy.sh`, so you typically don't need to run it separately.\n\n### Full Validation\n\n```sh\n./scripts/run_validation.sh\n```\n\nThis is the canonical check — CI runs this and the PR template asks contributors to run it. It installs requirements, runs codegen, formats, lints, runs tests with coverage, and runs mypy type checking on the latest supported Python version (check the `LATEST_SUPPORTED_PY` environment variable in `.github/workflows/ci-build.yml`).\n\n### Individual Commands\n\nAvailable scripts in the `scripts/` directory:\n\n| Task | Command |\n| --- | --- |\n| Install dependencies | `./scripts/install.sh` |\n| Uninstall all packages | `./scripts/uninstall_all.sh` |\n| Format code | `./scripts/format.sh` |\n| Lint (check formatting) | `./scripts/lint.sh` |\n| Run all unit tests | `./scripts/run_tests.sh` |\n| Run a specific test | `./scripts/run_tests.sh tests/slack_sdk/web/test_web_client.py` |\n| Run type checking | `./scripts/run_mypy.sh` |\n| Generate async/legacy code | `python scripts/codegen.py --path .` |\n| Build PyPI package | `./scripts/build_pypi_package.sh` |\n| Generate API docs | `./scripts/generate_api_docs.sh` |\n\n## Code Style & Tooling\n\nAll tooling configuration is defined in the following files:\n\n- **Formatter**: `ruff format` — see `[tool.ruff]` in `pyproject.toml`\n- **Linter**: `ruff check` — see `[tool.ruff.lint]` in `pyproject.toml`\n- **Type checker**: `mypy` — see `[tool.mypy]` in `pyproject.toml`\n- **Test runner**: `pytest` — see `[tool.pytest.ini_options]` in `pyproject.toml`\n- **Coverage**: `pytest-cov` reporting to Codecov\n- **Build system**: see `[build-system]` and `[project]` in `pyproject.toml`\n\n**Dependencies:**\n\n- Testing: `requirements/testing.txt`\n- Optional runtime: `requirements/optional.txt`\n- Dev tools (ruff, mypy): `requirements/tools.txt`\n\n## CI Pipeline (GitHub Actions)\n\nDefined in `.github/workflows/ci-build.yml`, runs on push to `main`, all PRs, and daily schedule. Check the workflow file for the current Python version matrix.\n\n## Key Files & Directories\n\n**Source Code:**\n\n- `slack_sdk/` — Main package (active development)\n- `slack_sdk/web/client.py` — **CANONICAL SOURCE** for all Web API methods\n- `slack_sdk/web/chat_stream.py` — Canonical streaming chat client\n- `slack_sdk/version.py` — Single source of truth for version\n- `slack/` — Legacy package (maintenance mode, DO NOT MODIFY)\n\n**Configuration:**\n\n- `pyproject.toml` — Project metadata, build config, tool settings (ruff, pytest, mypy)\n- `requirements/*.txt` — Dependency specifications\n\n**Tooling:**\n\n- `scripts/codegen.py` — Generates async/legacy client variants\n- `scripts/*.sh` — Development and CI helper scripts\n\n**GitHub & CI/CD:**\n\n- `.github/` — GitHub-specific configuration and documentation\n- `.github/workflows/` — Continuous integration pipeline definitions that run on GitHub Actions\n- `.github/maintainers_guide.md` — Maintainer workflows and release process\n\n**Documentation:**\n\n- `README.md` — Project overview, installation, and usage examples\n- `.github/maintainers_guide.md` — Maintainer workflows and release process\n\n## Common Contribution Workflows\n\n### Adding a New Web API Method\n\n1. Add the method to `slack_sdk/web/client.py` following the existing pattern\n2. Run code generation: `python scripts/codegen.py --path .`\n3. Run formatter: `./scripts/format.sh`\n4. Add tests in `tests/slack_sdk/web/`\n5. Validate: `./scripts/run_validation.sh`\n\n### Adding a New Feature to a Non-Web Module\n\n1. Implement the sync version in the appropriate `slack_sdk/` subpackage\n2. If the module has async variants, implement those as well (not auto-generated for non-web modules)\n3. Add tests mirroring the module structure\n4. Validate: `./scripts/run_validation.sh`\n\n### Adding a New Block Kit Type\n\nSee [`slack_sdk/models/AGENTS.md`](slack_sdk/models/AGENTS.md) for detailed steps on defining, registering, exporting, and testing new Block Kit blocks, elements, and composition objects.\n\n### Fixing a Bug\n\n1. Write a test that reproduces the bug\n2. Fix the code (if in `client.py`, run codegen afterward)\n3. Validate: `./scripts/run_validation.sh`\n\n## Versioning & Releases\n\n- Use the new version mentioned by the maintainer; if they do not provide it, prompt them for it.\n- Ensure the new version follows [Semantic Versioning](http://semver.org/) via [PEP 440](https://peps.python.org/pep-0440/)\n- Version lives in `slack_sdk/version.py` and is dynamically read by `pyproject.toml`\n- Releases are triggered by publishing a GitHub Release, which triggers the PyPI deployment workflow; the maintainer will take care of this\n- Commit message format for releases: `chore(release): version X.Y.Z`\n\n## Dependencies\n\nThe SDK has **zero required runtime dependencies** for the core sync Web API client and it is imperative it remains that way. Optional dependencies enable additional functionality:\n\n- `aiohttp` — async HTTP client\n- `websockets` / `websocket-client` — Socket Mode backends\n- `SQLAlchemy` — OAuth token storage\n- `boto3` — S3/DynamoDB token storage\n- `aiodns` — faster DNS resolution for async\n\nVersion constraints for optional and dev dependencies are pinned in the `requirements/` directory.\n\nBefore adding, bumping, pinning, or reviewing any dependency in `requirements/*.txt`, whether a Dependabot PR or a manual edit, follow the `managing-dependencies` skill in `.claude/skills/`. It defines the layout and `python_version` marker conventions that keep one set of pins working across the full Python matrix.\n",".claude/CLAUDE.md":"@../AGENTS.md\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/slackapi/python-slack-sdk/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md — Python Slack SDK\n\n## Project Overview\n\nThe Python Slack SDK (`slack_sdk`) is a modular Python library for interacting with the Slack platform APIs. It is published on PyPI as `slack-sdk`. The SDK provides independent packages for each Slack API surface: Web API, Webhooks, Socket Mode, OAuth, Audit Logs, SCIM, RTM, Block Kit models, and request signature verification.\n\n- **Repository**: <https://github.com/slackapi/python-slack-sdk>\n- **Documentation**: <https://docs.slack.dev/tools/python-slack-sdk/>\n- **PyPI**: <https://pypi.org/project/slack-sdk/>\n- **Current version**: defined in `slack_sdk/version.py`\n\n## Critical Rules\n\nThese are the most important constraints in this project. Violating any of them will break CI or corrupt auto-generated code:\n\n1. **Never edit auto-generated files.** The following files are produced by `scripts/codegen.py` and must not be modified directly:\n   - `slack_sdk/web/async_client.py`\n   - `slack_sdk/web/legacy_client.py`\n   - `slack_sdk/web/async_chat_stream.py`\n\n   Edit the source files (`client.py` or `chat_stream.py`) instead, then run codegen (see [Code Generation](#code-generation-critical-pattern)).\n\n2. **Zero runtime dependencies.** The core sync Web API client must have no required runtime dependencies. Do not add entries to `install_requires` / `dependencies` in `pyproject.toml`.\n\n3. **Do not modify the legacy `slack/` package.** It is in maintenance mode and only re-exports from `slack_sdk` with deprecation warnings. All new development goes in `slack_sdk/`.\n\n4. **Always run codegen + format after editing `client.py` or `chat_stream.py`:**\n\n   ```sh\n   python scripts/codegen.py --path .\n   ./scripts/format.sh\n   ```\n\n5. **Use project scripts, not raw tool commands.** Run `./scripts/run_tests.sh`, not `pytest` directly. The scripts handle codegen and formatting.\n\n## Architecture\n\n### Package Structure\n\nThe SDK is organized into independent sub-packages:\n\n- **`slack_sdk/web/`** — Web API client (sync, async, legacy). Contains auto-generated files (see [Code Generation](#code-generation-critical-pattern))\n- **`slack_sdk/webhook/`** — Incoming Webhooks\n- **`slack_sdk/socket_mode/`** — Socket Mode with pluggable backends\n- **`slack_sdk/oauth/`** — OAuth flows and token storage\n- **`slack_sdk/models/`** — Block Kit UI builders\n- **`slack_sdk/audit_logs/`**, **`slack_sdk/scim/`** — Enterprise APIs\n- **`slack_sdk/signature/`** — Request verification\n- **`slack_sdk/http_retry/`** — Retry handlers\n- **`slack/`** — Legacy package (maintenance mode, do not modify)\n\nSee the repository structure for the complete package layout.\n\n### Code Generation (Critical Pattern)\n\n**NEVER edit these auto-generated files:**\n\n- `slack_sdk/web/async_client.py`\n- `slack_sdk/web/legacy_client.py`\n- `slack_sdk/web/async_chat_stream.py`\n\nEach contains a header warning:\n```text\n# DO NOT EDIT THIS FILE\n# 1) Modify slack_sdk/web/client.py\n# 2) Run `python scripts/codegen.py`\n# 3) Run `ruff format slack_sdk/`\n```\n\n**How it works:**\n\n1. Edit `slack_sdk/web/client.py` (canonical source for Web API methods)\n2. Edit `slack_sdk/web/chat_stream.py` (canonical source for streaming chat)\n3. Run `python scripts/codegen.py --path .` to generate async/legacy variants\n4. Run `./scripts/format.sh` to format the generated code\n\nThe codegen script (`scripts/codegen.py`) automatically transforms sync code into async variants by adding `async def`, `await`, and replacing classes with async equivalents.\n\n### Web API Method Pattern\n\nEvery Web API method in `client.py` follows this pattern:\n\n```python\ndef method_name(\n    self,\n    *,                              # keyword-only arguments\n    required_param: str,\n    optional_param: Optional[str] = None,\n    **kwargs,\n) -> SlackResponse:\n    \"\"\"Description of the API method\n    https://docs.slack.dev/reference/methods/method.name\n    \"\"\"\n    kwargs.update({\"required_param\": required_param})\n    if optional_param is not None:\n        kwargs.update({\"optional_param\": optional_param})\n    return self.api_call(\"method.name\", params=kwargs)\n```\n\nKey conventions:\n\n- All parameters are keyword-only (after `*`)\n- Required params have no default; optional params default to `None`\n- `**kwargs` captures additional/undocumented parameters\n- Parameters are collected into `kwargs` dict and passed to `self.api_call()`\n- The `api_call` method name uses Slack's dot-notation (e.g., `\"chat.postMessage\"`)\n- Docstrings include a link to the Slack API reference\n\n### Error Types\n\nAll SDK exceptions are defined in `slack_sdk/errors/__init__.py` and inherit from `SlackClientError`.\n\nKey exceptions to be aware of:\n\n- **`SlackApiError`** — Raised when the API returns an error response (carries the `response` object)\n- **`SlackRequestError`** — Raised when the HTTP request itself fails\n- **`BotUserAccessError`** — Raised when using a bot token (`xoxb-*`) for a user-only method\n\nSee `slack_sdk/errors/__init__.py` for the complete list of exception types and their usage.\n\n### HTTP Retry Handlers\n\nThe `slack_sdk/http_retry/` module provides built-in retry strategies for connection errors, rate limiting (HTTP 429), and server errors (HTTP 500/503).\n\nKey handlers:\n\n- **`ConnectionErrorRetryHandler`** / **`AsyncConnectionErrorRetryHandler`**\n- **`RateLimitErrorRetryHandler`** / **`AsyncRateLimitErrorRetryHandler`** (respects `Retry-After` header)\n- **`ServerErrorRetryHandler`** / **`AsyncServerErrorRetryHandler`**\n\nRetry intervals can be configured with `BackoffRetryIntervalCalculator` (exponential backoff) or `FixedValueRetryIntervalCalculator`. See `slack_sdk/http_retry/` for implementation details.\n\n### Test Patterns\n\nTests use `unittest.TestCase` with a mock web API server:\n\n```python\nimport unittest\nfrom slack_sdk import WebClient\nfrom tests.slack_sdk.web.mock_web_api_handler import MockHandler\nfrom tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server\n\nclass TestFeature(unittest.TestCase):\n    def setUp(self):\n        setup_mock_web_api_server(self, MockHandler)\n        self.client = WebClient(\n            token=\"xoxb-api_test\",\n            base_url=\"http://localhost:8888\",\n        )\n\n    def tearDown(self):\n        cleanup_mock_web_api_server(self)\n\n    def test_something(self):\n        resp = self.client.api_test()\n        self.assertTrue(resp[\"ok\"])\n```\n\nEach sub-package has its own `MockHandler` (e.g., `tests/slack_sdk/webhook/mock_web_api_handler.py`, `tests/slack_sdk/scim/mock_web_api_handler.py`). Use the handler from the matching sub-package.\n\nTest directories:\n\n- `tests/` — Unit tests (mirroring `slack_sdk/` structure)\n  - `tests/slack_sdk/` — Sync tests\n  - `tests/slack_sdk_async/` — Async variants\n  - `tests/slack_sdk_fixture/` — Pytest fixtures and test data\n  - `tests/data/` — JSON fixture files\n  - `tests/mock_web_api_server/` — Mock Slack API server\n- `integration_tests/` — Tests against real Slack APIs (require env tokens)\n\n## Development Commands\n\n### Setup\n\n**Prerequisites:** A Python virtual environment must be activated. See `.github/maintainers_guide.md` for detailed setup instructions using `pyenv` and `venv`.\n\n**Quick check:** Verify venv is active with `echo $VIRTUAL_ENV` (should output a path).\n\nAlways use the project scripts instead of calling tools like `pytest` directly.\n\n### Install Dependencies\n\n```sh\n./scripts/install.sh\n```\n\nInstalls all project dependencies (testing, optional, and tools) via pip. This script is called automatically by `run_validation.sh`, `run_integration_tests.sh`, and `run_mypy.sh`, so you typically don't need to run it separately.\n\n### Full Validation\n\n```sh\n./scripts/run_validation.sh\n```\n\nThis is the canonical check — CI runs this and the PR template asks contributors to run it. It installs requirements, runs codegen, formats, lints, runs tests with coverage, and runs mypy type checking on the latest supported Python version (check the `LATEST_SUPPORTED_PY` environment variable in `.github/workflows/ci-build.yml`).\n\n### Individual Commands\n\nAvailable scripts in the `scripts/` directory:\n\n| Task | Command |\n| --- | --- |\n| Install dependencies | `./scripts/install.sh` |\n| Uninstall all packages | `./scripts/uninstall_all.sh` |\n| Format code | `./scripts/format.sh` |\n| Lint (check formatting) | `./scripts/lint.sh` |\n| Run all unit tests | `./scripts/run_tests.sh` |\n| Run a specific test | `./scripts/run_tests.sh tests/slack_sdk/web/test_web_client.py` |\n| Run type checking | `./scripts/run_mypy.sh` |\n| Generate async/legacy code | `python scripts/codegen.py --path .` |\n| Build PyPI package | `./scripts/build_pypi_package.sh` |\n| Generate API docs | `./scripts/generate_api_docs.sh` |\n\n## Code Style & Tooling\n\nAll tooling configuration is defined in the following files:\n\n- **Formatter**: `ruff format` — see `[tool.ruff]` in `pyproject.toml`\n- **Linter**: `ruff check` — see `[tool.ruff.lint]` in `pyproject.toml`\n- **Type checker**: `mypy` — see `[tool.mypy]` in `pyproject.toml`\n- **Test runner**: `pytest` — see `[tool.pytest.ini_options]` in `pyproject.toml`\n- **Coverage**: `pytest-cov` reporting to Codecov\n- **Build system**: see `[build-system]` and `[project]` in `pyproject.toml`\n\n**Dependencies:**\n\n- Testing: `requirements/testing.txt`\n- Optional runtime: `requirements/optional.txt`\n- Dev tools (ruff, mypy): `requirements/tools.txt`\n\n## CI Pipeline (GitHub Actions)\n\nDefined in `.github/workflows/ci-build.yml`, runs on push to `main`, all PRs, and daily schedule. Check the workflow file for the current Python version matrix.\n\n## Key Files & Directories\n\n**Source Code:**\n\n- `slack_sdk/` — Main package (active development)\n- `slack_sdk/web/client.py` — **CANONICAL SOURCE** for all Web API methods\n- `slack_sdk/web/chat_stream.py` — Canonical streaming chat client\n- `slack_sdk/version.py` — Single source of truth for version\n- `slack/` — Legacy package (maintenance mode, DO NOT MODIFY)\n\n**Configuration:**\n\n- `pyproject.toml` — Project metadata, build config, tool settings (ruff, pytest, mypy)\n- `requirements/*.txt` — Dependency specifications\n\n**Tooling:**\n\n- `scripts/codegen.py` — Generates async/legacy client variants\n- `scripts/*.sh` — Development and CI helper scripts\n\n**GitHub & CI/CD:**\n\n- `.github/` — GitHub-specific configuration and documentation\n- `.github/workflows/` — Continuous integration pipeline definitions that run on GitHub Actions\n- `.github/maintainers_guide.md` — Maintainer workflows and release process\n\n**Documentation:**\n\n- `README.md` — Project overview, installation, and usage examples\n- `.github/maintainers_guide.md` — Maintainer workflows and release process\n\n## Common Contribution Workflows\n\n### Adding a New Web API Method\n\n1. Add the method to `slack_sdk/web/client.py` following the existing pattern\n2. Run code generation: `python scripts/codegen.py --path .`\n3. Run formatter: `./scripts/format.sh`\n4. Add tests in `tests/slack_sdk/web/`\n5. Validate: `./scripts/run_validation.sh`\n\n### Adding a New Feature to a Non-Web Module\n\n1. Implement the sync version in the appropriate `slack_sdk/` subpackage\n2. If the module has async variants, implement those as well (not auto-generated for non-web modules)\n3. Add tests mirroring the module structure\n4. Validate: `./scripts/run_validation.sh`\n\n### Adding a New Block Kit Type\n\nSee [`slack_sdk/models/AGENTS.md`](slack_sdk/models/AGENTS.md) for detailed steps on defining, registering, exporting, and testing new Block Kit blocks, elements, and composition objects.\n\n### Fixing a Bug\n\n1. Write a test that reproduces the bug\n2. Fix the code (if in `client.py`, run codegen afterward)\n3. Validate: `./scripts/run_validation.sh`\n\n## Versioning & Releases\n\n- Use the new version mentioned by the maintainer; if they do not provide it, prompt them for it.\n- Ensure the new version follows [Semantic Versioning](http://semver.org/) via [PEP 440](https://peps.python.org/pep-0440/)\n- Version lives in `slack_sdk/version.py` and is dynamically read by `pyproject.toml`\n- Releases are triggered by publishing a GitHub Release, which triggers the PyPI deployment workflow; the maintainer will take care of this\n- Commit message format for releases: `chore(release): version X.Y.Z`\n\n## Dependencies\n\nThe SDK has **zero required runtime dependencies** for the core sync Web API client and it is imperative it remains that way. Optional dependencies enable additional functionality:\n\n- `aiohttp` — async HTTP client\n- `websockets` / `websocket-client` — Socket Mode backends\n- `SQLAlchemy` — OAuth token storage\n- `boto3` — S3/DynamoDB token storage\n- `aiodns` — faster DNS resolution for async\n\nVersion constraints for optional and dev dependencies are pinned in the `requirements/` directory.\n\nBefore adding, bumping, pinning, or reviewing any dependency in `requirements/*.txt`, whether a Dependabot PR or a manual edit, follow the `managing-dependencies` skill in `.claude/skills/`. It defines the layout and `python_version` marker conventions that keep one set of pins working across the full Python matrix.\n","isInternal":false,"tokens":3124,"sizeBytes":13073},{"name":"SKILL.md","path":".claude/skills/managing-dependencies/SKILL.md","rawUrl":"https://raw.githubusercontent.com/slackapi/python-slack-sdk/HEAD/.claude/skills/managing-dependencies/SKILL.md","title":"managing-dependencies","category":"anthropic-skill","format":"markdown","content":"---\nname: managing-dependencies\ndescription: >-\n  Use when adding, bumping, pinning, or reviewing a dependency in requirements.txt or requirements/*.txt, including taking or fixing a Dependabot PR. Also use when pip install fails on some Python versions in CI but not others (\"Could not find a version that satisfies the requirement\", \"No matching distribution found\", \"Requires-Python\", \"ResolutionImpossible\"), including PyPy-only failures, and when dropping support for an old Python version.\n---\n\n# Managing dependencies\n\nThis repo supports a range of Python versions, and one set of requirements files has to install on all of them. Dependabot proposes the newest release, so its bumps often raise a lower bound past what the oldest Pythons can install, and CI fails at the install step on those jobs only. The fix is to split the requirement with PEP 508 `python_version` markers so each Python gets the newest release it can install. Dropping an old Python to turn CI green is not the fix.\n\n## Where the facts live\n\nRead these instead of trusting memory; they change often.\n\n- **Supported Pythons:** `requires-python` and the `Programming Language :: Python :: 3.X` classifiers in `pyproject.toml`, and the CI matrix.\n- **Who installs a file, on which Python:** `grep -rn requirements .github/workflows scripts requirements pyproject.toml`. This also catches `-r` includes. For a hit in a script, find the CI job that runs it and read its `python-version`.\n- **Whether a file ships to users:** a file that `pyproject.toml` reads into package metadata (`dependencies` or `optional-dependencies` under `[tool.setuptools.dynamic]`) ends up in the published wheel.\n\n## Workflow\n\n- [ ] 1. Find which Pythons install the file. A file installed only on the newest Python takes the bump as-is.\n- [ ] 2. Find the newest release each affected Python can install (see \"Finding the ceiling\").\n- [ ] 3. If every Python the line covers can install the new lower bound, take the bump as one line. Done.\n- [ ] 4. Otherwise split the line (see \"Splitting a line\").\n- [ ] 5. Re-read the section: no Python matches two lines, and the newest line is open-ended.\n\nChange only the section you are fixing; don't reformat other sections in the same change.\n\n## File layout\n\nEach file starts with a `# pip install -r <path>` header, then one section per dependency: a `# <name>` line, optional `# Note:` lines, the requirement line(s), and a blank line.\n\n```\n# pip install -r requirements/testing.txt\n\n# pkg\npkg>=1.4,<2; python_version < \"3.9\"\npkg>=2.0,<2.3; python_version == \"3.9\"\npkg>=2.5.1,<3; python_version >= \"3.10\"\n\n# other-pkg\n# Note: other-pkg 4.x breaks on Python 3.8 but declares no requires_python, so pip can't filter it.\nother-pkg>=3,<4\n```\n\n**Notes:** write one only for what the lines can't say: a cap that isn't obvious, a coupling with another package, or a release with wrong metadata. Don't restate versions or which Python gets which release; a routine split needs no note. Existing notes that only restate their lines are legacy: drop one when you edit its section, and don't copy it.\n\n## Finding the ceiling\n\nAn old Python's ceiling is the first release that dropped it, not the release Dependabot proposes. If `pkg` dropped 3.9 at 2.3 and the target is 2.5.1, the 3.9 line is `<2.3`, since 2.3 through 2.5.0 are 3.10-only too.\n\n- **From a red CI job:** the failing install log is ground truth. The lowest version on its `Ignored the following versions that require a different python version` line is that Python's ceiling.\n- **For any Python:** `pip index versions <pkg> --python-version 3.9` lists what 3.9 can install, newest first. Run it again for the next Python up; the first release newer than 3.9's newest is 3.9's ceiling.\n- **The pre-bump lower bound** is in the Dependabot PR title (`update pkg requirement from <3,>=2.0 to >=2.5.1,<3`) or `git diff`.\n\n## Splitting a line\n\nSplit only the Pythons that the bumped line covers and the new lower bound excludes. Leave existing bands for older Pythons alone, even when they are coarser than they could be.\n\n- Lines run oldest Python to newest. The newest line takes the bump and stays open-ended (`>= \"X.Y\"`).\n- Each band that splits off keeps the line's pre-bump lower bound and gets its ceiling from \"Finding the ceiling\". Pythons that share a ceiling share a band.\n- Marker style: `pkg>=1,<2; python_version == \"3.9\"`. Spaces around operators, double-quoted `\"X.Y\"`, one space after `;`. A one-Python band is `== \"X.Y\"`; a band spanning several is `>= \"X.Y\" and < \"X.Z\"`, or `< \"X.Z\"` when it is the oldest. Boundaries use `>=` and `<` so each Python lands on exactly one side. `python_version` is major.minor only (3.9.5 reports `\"3.9\"`), so `== \"3.9\"` covers every 3.9 patch; don't use `python_full_version` here, where `== \"3.9\"` matches only 3.9.0.\n\nA section with a single gated line (installed only on the newest Python, or only on PyPy) is intentional; don't fill in the other Pythons.\n\n## Gotchas\n\n- **Dependabot rewrites only the last line** of a split section, the open-ended one. When it bumps a split package, check which Pythons that line covers; the new lower bound may exclude the oldest of them, which then splits off.\n- **Releases with no `requires_python`** can't be filtered by pip, so they install on Pythons they don't support and fail at import or test time rather than at install. `pip index` lists them too. Pin an explicit ceiling with a note saying why. When the culprit comes in loosely through another package, pin it in that package's section so the coupling stays visible.\n- **Only PyPy jobs fail:** the release usually dropped PyPy wheels. Gate on the implementation instead of downgrading CPython: `pkg<46; implementation_name == \"pypy\" and python_version == \"3.10\"`. Lowercase `pypy`, and `implementation_name`, not `platform_python_implementation`. No unmarked companion line is needed.\n- **Files that ship:** full-line comments only; an inline comment breaks the package build. Their lower bounds are the minimum users must install, so raise one only for a reason.\n- **Not this pattern:** a real test failure, or an install failure on every Python. Investigate the bump normally.\n\n## Dropping a Python version\n\nWhen a Python leaves the classifiers and the CI matrix, merge any band that only served it into its neighbor. If a section is left with one line, remove its marker and the `;`.\n\n## Leave alone\n\n- `requires-python`, the classifiers, and the CI matrix. Changing which Pythons are supported is its own decision.\n- Packages under `ignore` in `.github/dependabot.yml`: maintainers bump those by hand on purpose. Prefer a marker split over adding a new `ignore`, which freezes the package on every Python.\n","frontmatter":{"name":"managing-dependencies","description":"Use when adding, bumping, pinning, or reviewing a dependency in requirements.txt or requirements/*.txt, including taking or fixing a Dependabot PR. Also use when pip install fails on some Python versions in CI but not others (\"Could not find a version that satisfies the requirement\", \"No matching distribution found\", \"Requires-Python\", \"ResolutionImpossible\"), including PyPy-only failures, and when dropping support for an old Python version."},"isInternal":false,"tokens":1676,"sizeBytes":6714},{"name":"CLAUDE.md","path":".claude/CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/slackapi/python-slack-sdk/HEAD/.claude/CLAUDE.md","title":".claude - Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"@../AGENTS.md\n","isInternal":false,"tokens":6,"sizeBytes":14}],"systemPromptSnippet":"<yakaai_skills repo=\"slackapi/python-slack-sdk\">\n<!-- File: AGENTS.md (Tokens: ~3124 | Category: root-instruction) -->\n# AGENTS.md — Python Slack SDK\n\n## Project Overview\n\nThe Python Slack SDK (`slack_sdk`) is a modular Python library for interacting with the Slack platform APIs. It is published on PyPI as `slack-sdk`. The SDK provides independent packages for each Slack API surface: Web API, Webhooks, Socket Mode, OAuth, Audit Logs, SCIM, RTM, Block Kit models, and request signature verification.\n\n- **Repository**: <https://github.com/slackapi/python-slack-sdk>\n- **Documentation**: <https://docs.slack.dev/tools/python-slack-sdk/>\n- **PyPI**: <https://pypi.org/project/slack-sdk/>\n- **Current version**: defined in `slack_sdk/version.py`\n\n## Critical Rules\n\nThese are the most important constraints in this project. Violating any of them will break CI or corrupt auto-generated code:\n\n1. **Never edit auto-generated files.** The following files are produced by `scripts/codegen.py` and must not be modified directly:\n   - `slack_sdk/web/async_client.py`\n   - `slack_sdk/web/legacy_client.py`\n   - `slack_sdk/web/async_chat_stream.py`\n\n   Edit the source files (`client.py` or `chat_stream.py`) instead, then run codegen (see [Code Generation](#code-generation-critical-pattern)).\n\n2. **Zero runtime dependencies.** The core sync Web API client must have no required runtime dependencies. Do not add entries to `install_requires` / `dependencies` in `pyproject.toml`.\n\n3. **Do not modify the legacy `slack/` package.** It is in maintenance mode and only re-exports from `slack_sdk` with deprecation warnings. All new development goes in `slack_sdk/`.\n\n4. **Always run codegen + format after editing `client.py` or `chat_stream.py`:**\n\n   ```sh\n   python scripts/codegen.py --path .\n   ./scripts/format.sh\n   ```\n\n5. **Use project scripts, not raw tool commands.** Run `./scripts/run_tests.sh`, not `pytest` directly. The scripts handle codegen and formatting.\n\n## Architecture\n\n### Package Structure\n\nThe SDK is organized into independent sub-packages:\n\n- **`slack_sdk/web/`** — Web API client (sync, async, legacy). Contains auto-generated files (see [Code Generation](#code-generation-critical-pattern))\n- **`slack_sdk/webhook/`** — Incoming Webhooks\n- **`slack_sdk/socket_mode/`** — Socket Mode with pluggable backends\n- **`slack_sdk/oauth/`** — OAuth flows and token storage\n- **`slack_sdk/models/`** — Block Kit UI builders\n- **`slack_sdk/audit_logs/`**, **`slack_sdk/scim/`** — Enterprise APIs\n- **`slack_sdk/signature/`** — Request verification\n- **`slack_sdk/http_retry/`** — Retry handlers\n- **`slack/`** — Legacy package (maintenance mode, do not modify)\n\nSee the repository structure for the complete package layout.\n\n### Code Generation (Critical Pattern)\n\n**NEVER edit these auto-generated files:**\n\n- `slack_sdk/web/async_client.py`\n- `slack_sdk/web/legacy_client.py`\n- `slack_sdk/web/async_chat_stream.py`\n\nEach contains a header warning:\n```text\n# DO NOT EDIT THIS FILE\n# 1) Modify slack_sdk/web/client.py\n# 2) Run `python scripts/codegen.py`\n# 3) Run `ruff format slack_sdk/`\n```\n\n**How it works:**\n\n1. Edit `slack_sdk/web/client.py` (canonical source for Web API methods)\n2. Edit `slack_sdk/web/chat_stream.py` (canonical source for streaming chat)\n3. Run `python scripts/codegen.py --path .` to generate async/legacy variants\n4. Run `./scripts/format.sh` to format the generated code\n\nThe codegen script (`scripts/codegen.py`) automatically transforms sync code into async variants by adding `async def`, `await`, and replacing classes with async equivalents.\n\n### Web API Method Pattern\n\nEvery Web API method in `client.py` follows this pattern:\n\n```python\ndef method_name(\n    self,\n    *,                              # keyword-only arguments\n    required_param: str,\n    optional_param: Optional[str] = None,\n    **kwargs,\n) -> SlackResponse:\n    \"\"\"Description of the API method\n    https://docs.slack.dev/reference/methods/method.name\n    \"\"\"\n    kwargs.update({\"required_param\": required_param})\n    if optional_param is not None:\n        kwargs.update({\"optional_param\": optional_param})\n    return self.api_call(\"method.name\", params=kwargs)\n```\n\nKey conventions:\n\n- All parameters are keyword-only (after `*`)\n- Required params have no default; optional params default to `None`\n- `**kwargs` captures additional/undocumented parameters\n- Parameters are collected into `kwargs` dict and passed to `self.api_call()`\n- The `api_call` method name uses Slack's dot-notation (e.g., `\"chat.postMessage\"`)\n- Docstrings include a link to the Slack API reference\n\n### Error Types\n\nAll SDK exceptions are defined in `slack_sdk/errors/__init__.py` and inherit from `SlackClientError`.\n\nKey exceptions to be aware of:\n\n- **`SlackApiError`** — Raised when the API returns an error response (carries the `response` object)\n- **`SlackRequestError`** — Raised when the HTTP request itself fails\n- **`BotUserAccessError`** — Raised when using a bot token (`xoxb-*`) for a user-only method\n\nSee `slack_sdk/errors/__init__.py` for the complete list of exception types and their usage.\n\n### HTTP Retry Handlers\n\nThe `slack_sdk/http_retry/` module provides built-in retry strategies for connection errors, rate limiting (HTTP 429), and server errors (HTTP 500/503).\n\nKey handlers:\n\n- **`ConnectionErrorRetryHandler`** / **`AsyncConnectionErrorRetryHandler`**\n- **`RateLimitErrorRetryHandler`** / **`AsyncRateLimitErrorRetryHandler`** (respects `Retry-After` header)\n- **`ServerErrorRetryHandler`** / **`AsyncServerErrorRetryHandler`**\n\nRetry intervals can be configured with `BackoffRetryIntervalCalculator` (exponential backoff) or `FixedValueRetryIntervalCalculator`. See `slack_sdk/http_retry/` for implementation details.\n\n### Test Patterns\n\nTests use `unittest.TestCase` with a mock web API server:\n\n```python\nimport unittest\nfrom slack_sdk import WebClient\nfrom tests.slack_sdk.web.mock_web_api_handler import MockHandler\nfrom tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server\n\nclass TestFeature(unittest.TestCase):\n    def setUp(self):\n        setup_mock_web_api_server(self, MockHandler)\n        self.client = WebClient(\n            token=\"xoxb-api_test\",\n            base_url=\"http://localhost:8888\",\n        )\n\n    def tearDown(self):\n        cleanup_mock_web_api_server(self)\n\n    def test_something(self):\n        resp = self.client.api_test()\n        self.assertTrue(resp[\"ok\"])\n```\n\nEach sub-package has its own `MockHandler` (e.g., `tests/slack_sdk/webhook/mock_web_api_handler.py`, `tests/slack_sdk/scim/mock_web_api_handler.py`). Use the handler from the matching sub-package.\n\nTest directories:\n\n- `tests/` — Unit tests (mirroring `slack_sdk/` structure)\n  - `tests/slack_sdk/` — Sync tests\n  - `tests/slack_sdk_async/` — Async variants\n  - `tests/slack_sdk_fixture/` — Pytest fixtures and test data\n  - `tests/data/` — JSON fixture files\n  - `tests/mock_web_api_server/` — Mock Slack API server\n- `integration_tests/` — Tests against real Slack APIs (require env tokens)\n\n## Development Commands\n\n### Setup\n\n**Prerequisites:** A Python virtual environment must be activated. See `.github/maintainers_guide.md` for detailed setup instructions using `pyenv` and `venv`.\n\n**Quick check:** Verify venv is active with `echo $VIRTUAL_ENV` (should output a path).\n\nAlways use the project scripts instead of calling tools like `pytest` directly.\n\n### Install Dependencies\n\n```sh\n./scripts/install.sh\n```\n\nInstalls all project dependencies (testing, optional, and tools) via pip. This script is called automatically by `run_validation.sh`, `run_integration_tests.sh`, and `run_mypy.sh`, so you typically don't need to run it separately.\n\n### Full Validation\n\n```sh\n./scripts/run_validation.sh\n```\n\nThis is the canonical check — CI runs this and the PR template asks contributors to run it. It installs requirements, runs codegen, formats, lints, runs tests with coverage, and runs mypy type checking on the latest supported Python version (check the `LATEST_SUPPORTED_PY` environment variable in `.github/workflows/ci-build.yml`).\n\n### Individual Commands\n\nAvailable scripts in the `scripts/` directory:\n\n| Task | Command |\n| --- | --- |\n| Install dependencies | `./scripts/install.sh` |\n| Uninstall all packages | `./scripts/uninstall_all.sh` |\n| Format code | `./scripts/format.sh` |\n| Lint (check formatting) | `./scripts/lint.sh` |\n| Run all unit tests | `./scripts/run_tests.sh` |\n| Run a specific test | `./scripts/run_tests.sh tests/slack_sdk/web/test_web_client.py` |\n| Run type checking | `./scripts/run_mypy.sh` |\n| Generate async/legacy code | `python scripts/codegen.py --path .` |\n| Build PyPI package | `./scripts/build_pypi_package.sh` |\n| Generate API docs | `./scripts/generate_api_docs.sh` |\n\n## Code Style & Tooling\n\nAll tooling configuration is defined in the following files:\n\n- **Formatter**: `ruff format` — see `[tool.ruff]` in `pyproject.toml`\n- **Linter**: `ruff check` — see `[tool.ruff.lint]` in `pyproject.toml`\n- **Type checker**: `mypy` — see `[tool.mypy]` in `pyproject.toml`\n- **Test runner**: `pytest` — see `[tool.pytest.ini_options]` in `pyproject.toml`\n- **Coverage**: `pytest-cov` reporting to Codecov\n- **Build system**: see `[build-system]` and `[project]` in `pyproject.toml`\n\n**Dependencies:**\n\n- Testing: `requirements/testing.txt`\n- Optional runtime: `requirements/optional.txt`\n- Dev tools (ruff, mypy): `requirements/tools.txt`\n\n## CI Pipeline (GitHub Actions)\n\nDefined in `.github/workflows/ci-build.yml`, runs on push to `main`, all PRs, and daily schedule. Check the workflow file for the current Python version matrix.\n\n## Key Files & Directories\n\n**Source Code:**\n\n- `slack_sdk/` — Main package (active development)\n- `slack_sdk/web/client.py` — **CANONICAL SOURCE** for all Web API methods\n- `slack_sdk/web/chat_stream.py` — Canonical streaming chat client\n- `slack_sdk/version.py` — Single source of truth for version\n- `slack/` — Legacy package (maintenance mode, DO NOT MODIFY)\n\n**Configuration:**\n\n- `pyproject.toml` — Project metadata, build config, tool settings (ruff, pytest, mypy)\n- `requirements/*.txt` — Dependency specifications\n\n**Tooling:**\n\n- `scripts/codegen.py` — Generates async/legacy client variants\n- `scripts/*.sh` — Development and CI helper scripts\n\n**GitHub & CI/CD:**\n\n- `.github/` — GitHub-specific configuration and documentation\n- `.github/workflows/` — Continuous integration pipeline definitions that run on GitHub Actions\n- `.github/maintainers_guide.md` — Maintainer workflows and release process\n\n**Documentation:**\n\n- `README.md` — Project overview, installation, and usage examples\n- `.github/maintainers_guide.md` — Maintainer workflows and release process\n\n## Common Contribution Workflows\n\n### Adding a New Web API Method\n\n1. Add the method to `slack_sdk/web/client.py` following the existing pattern\n2. Run code generation: `python scripts/codegen.py --path .`\n3. Run formatter: `./scripts/format.sh`\n4. Add tests in `tests/slack_sdk/web/`\n5. Validate: `./scripts/run_validation.sh`\n\n### Adding a New Feature to a Non-Web Module\n\n1. Implement the sync version in the appropriate `slack_sdk/` subpackage\n2. If the module has async variants, implement those as well (not auto-generated for non-web modules)\n3. Add tests mirroring the module structure\n4. Validate: `./scripts/run_validation.sh`\n\n### Adding a New Block Kit Type\n\nSee [`slack_sdk/models/AGENTS.md`](slack_sdk/models/AGENTS.md) for detailed steps on defining, registering, exporting, and testing new Block Kit blocks, elements, and composition objects.\n\n### Fixing a Bug\n\n1. Write a test that reproduces the bug\n2. Fix the code (if in `client.py`, run codegen afterward)\n3. Validate: `./scripts/run_validation.sh`\n\n## Versioning & Releases\n\n- Use the new version mentioned by the maintainer; if they do not provide it, prompt them for it.\n- Ensure the new version follows [Semantic Versioning](http://semver.org/) via [PEP 440](https://peps.python.org/pep-0440/)\n- Version lives in `slack_sdk/version.py` and is dynamically read by `pyproject.toml`\n- Releases are triggered by publishing a GitHub Release, which triggers the PyPI deployment workflow; the maintainer will take care of this\n- Commit message format for releases: `chore(release): version X.Y.Z`\n\n## Dependencies\n\nThe SDK has **zero required runtime dependencies** for the core sync Web API client and it is imperative it remains that way. Optional dependencies enable additional functionality:\n\n- `aiohttp` — async HTTP client\n- `websockets` / `websocket-client` — Socket Mode backends\n- `SQLAlchemy` — OAuth token storage\n- `boto3` — S3/DynamoDB token storage\n- `aiodns` — faster DNS resolution for async\n\nVersion constraints for optional and dev dependencies are pinned in the `requirements/` directory.\n\nBefore adding, bumping, pinning, or reviewing any dependency in `requirements/*.txt`, whether a Dependabot PR or a manual edit, follow the `managing-dependencies` skill in `.claude/skills/`. It defines the layout and `python_version` marker conventions that keep one set of pins working across the full Python matrix.\n\n<!-- File: .claude/skills/managing-dependencies/SKILL.md (Tokens: ~1676 | Category: anthropic-skill) -->\n---\nname: managing-dependencies\ndescription: >-\n  Use when adding, bumping, pinning, or reviewing a dependency in requirements.txt or requirements/*.txt, including taking or fixing a Dependabot PR. Also use when pip install fails on some Python versions in CI but not others (\"Could not find a version that satisfies the requirement\", \"No matching distribution found\", \"Requires-Python\", \"ResolutionImpossible\"), including PyPy-only failures, and when dropping support for an old Python version.\n---\n\n# Managing dependencies\n\nThis repo supports a range of Python versions, and one set of requirements files has to install on all of them. Dependabot proposes the newest release, so its bumps often raise a lower bound past what the oldest Pythons can install, and CI fails at the install step on those jobs only. The fix is to split the requirement with PEP 508 `python_version` markers so each Python gets the newest release it can install. Dropping an old Python to turn CI green is not the fix.\n\n## Where the facts live\n\nRead these instead of trusting memory; they change often.\n\n- **Supported Pythons:** `requires-python` and the `Programming Language :: Python :: 3.X` classifiers in `pyproject.toml`, and the CI matrix.\n- **Who installs a file, on which Python:** `grep -rn requirements .github/workflows scripts requirements pyproject.toml`. This also catches `-r` includes. For a hit in a script, find the CI job that runs it and read its `python-version`.\n- **Whether a file ships to users:** a file that `pyproject.toml` reads into package metadata (`dependencies` or `optional-dependencies` under `[tool.setuptools.dynamic]`) ends up in the published wheel.\n\n## Workflow\n\n- [ ] 1. Find which Pythons install the file. A file installed only on the newest Python takes the bump as-is.\n- [ ] 2. Find the newest release each affected Python can install (see \"Finding the ceiling\").\n- [ ] 3. If every Python the line covers can install the new lower bound, take the bump as one line. Done.\n- [ ] 4. Otherwise split the line (see \"Splitting a line\").\n- [ ] 5. Re-read the section: no Python matches two lines, and the newest line is open-ended.\n\nChange only the section you are fixing; don't reformat other sections in the same change.\n\n## File layout\n\nEach file starts with a `# pip install -r <path>` header, then one section per dependency: a `# <name>` line, optional `# Note:` lines, the requirement line(s), and a blank line.\n\n```\n# pip install -r requirements/testing.txt\n\n# pkg\npkg>=1.4,<2; python_version < \"3.9\"\npkg>=2.0,<2.3; python_version == \"3.9\"\npkg>=2.5.1,<3; python_version >= \"3.10\"\n\n# other-pkg\n# Note: other-pkg 4.x breaks on Python 3.8 but declares no requires_python, so pip can't filter it.\nother-pkg>=3,<4\n```\n\n**Notes:** write one only for what the lines can't say: a cap that isn't obvious, a coupling with another package, or a release with wrong metadata. Don't restate versions or which Python gets which release; a routine split needs no note. Existing notes that only restate their lines are legacy: drop one when you edit its section, and don't copy it.\n\n## Finding the ceiling\n\nAn old Python's ceiling is the first release that dropped it, not the release Dependabot proposes. If `pkg` dropped 3.9 at 2.3 and the target is 2.5.1, the 3.9 line is `<2.3`, since 2.3 through 2.5.0 are 3.10-only too.\n\n- **From a red CI job:** the failing install log is ground truth. The lowest version on its `Ignored the following versions that require a different python version` line is that Python's ceiling.\n- **For any Python:** `pip index versions <pkg> --python-version 3.9` lists what 3.9 can install, newest first. Run it again for the next Python up; the first release newer than 3.9's newest is 3.9's ceiling.\n- **The pre-bump lower bound** is in the Dependabot PR title (`update pkg requirement from <3,>=2.0 to >=2.5.1,<3`) or `git diff`.\n\n## Splitting a line\n\nSplit only the Pythons that the bumped line covers and the new lower bound excludes. Leave existing bands for older Pythons alone, even when they are coarser than they could be.\n\n- Lines run oldest Python to newest. The newest line takes the bump and stays open-ended (`>= \"X.Y\"`).\n- Each band that splits off keeps the line's pre-bump lower bound and gets its ceiling from \"Finding the ceiling\". Pythons that share a ceiling share a band.\n- Marker style: `pkg>=1,<2; python_version == \"3.9\"`. Spaces around operators, double-quoted `\"X.Y\"`, one space after `;`. A one-Python band is `== \"X.Y\"`; a band spanning several is `>= \"X.Y\" and < \"X.Z\"`, or `< \"X.Z\"` when it is the oldest. Boundaries use `>=` and `<` so each Python lands on exactly one side. `python_version` is major.minor only (3.9.5 reports `\"3.9\"`), so `== \"3.9\"` covers every 3.9 patch; don't use `python_full_version` here, where `== \"3.9\"` matches only 3.9.0.\n\nA section with a single gated line (installed only on the newest Python, or only on PyPy) is intentional; don't fill in the other Pythons.\n\n## Gotchas\n\n- **Dependabot rewrites only the last line** of a split section, the open-ended one. When it bumps a split package, check which Pythons that line covers; the new lower bound may exclude the oldest of them, which then splits off.\n- **Releases with no `requires_python`** can't be filtered by pip, so they install on Pythons they don't support and fail at import or test time rather than at install. `pip index` lists them too. Pin an explicit ceiling with a note saying why. When the culprit comes in loosely through another package, pin it in that package's section so the coupling stays visible.\n- **Only PyPy jobs fail:** the release usually dropped PyPy wheels. Gate on the implementation instead of downgrading CPython: `pkg<46; implementation_name == \"pypy\" and python_version == \"3.10\"`. Lowercase `pypy`, and `implementation_name`, not `platform_python_implementation`. No unmarked companion line is needed.\n- **Files that ship:** full-line comments only; an inline comment breaks the package build. Their lower bounds are the minimum users must install, so raise one only for a reason.\n- **Not this pattern:** a real test failure, or an install failure on every Python. Investigate the bump normally.\n\n## Dropping a Python version\n\nWhen a Python leaves the classifiers and the CI matrix, merge any band that only served it into its neighbor. If a section is left with one line, remove its marker and the `;`.\n\n## Leave alone\n\n- `requires-python`, the classifiers, and the CI matrix. Changing which Pythons are supported is its own decision.\n- Packages under `ignore` in `.github/dependabot.yml`: maintainers bump those by hand on purpose. Prefer a marker split over adding a new `ignore`, which freezes the package on every Python.\n\n<!-- File: .claude/CLAUDE.md (Tokens: ~6 | Category: claude-rule) -->\n@../AGENTS.md\n</yakaai_skills>"}