{"owner":"redis","repo":"redis-py","hasSkills":true,"totalSkillsCount":18,"totalTokensCount":26449,"categories":["command-prompt","anthropic-skill","root-instruction","claude-rule"],"hasMcp":false,"mcpConfig":null,"found":[".agents/commands/add-new-command/add-new-command.md",".agents/commands/add-new-command/command-specification-template.md",".agents/commands/sync-claude-md/sync-claude-md.md",".agents/skills/generate-release-notes/SKILL.md",".agents/skills/generate-release-notes/references/pr-labels-guide.md",".agents/skills/generate-release-notes/references/release-notes-template.md",".agents/skills/maintainer-review/SKILL.md",".agents/skills/maintainer-review/references/evaluation-framework.md",".agents/skills/pr-draft-summary/SKILL.md",".agents/skills/prepare-for-takeover/SKILL.md",".claude/commands/add-new-command.md",".claude/commands/sync-claude-md.md",".claude/skills/generate-release-notes/SKILL.md",".claude/skills/maintainer-review/SKILL.md",".claude/skills/pr-draft-summary/SKILL.md",".claude/skills/prepare-for-takeover/SKILL.md","AGENTS.md","CLAUDE.md"],"skills":{".agents/commands/add-new-command/add-new-command.md":"---\ndescription: Adds support for a new Redis command from a given specification. Check command-specification-template.md.\nargument-hint: [path-to-specification]\n---\n\n# Execute: Add new Redis command support\n\n## Plan to Execute\n\nRead specification file: `$ARGUMENTS`\n\n## Execution Instructions\n\n### 1. Preparations\n\n- Follow instructions from `.agent/instructions.md`\n- Go through the guide `specs/redis_commands_guide.md`\n\n### 2. Read and Understand\n\n- Read the ENTIRE specification carefully\n- Go through Command Description and identify command type (string, list, set, etc.)\n- Go through the Command API:\n  - Identify required and optional arguments\n  - Identify how to match Redis command arguments type to Python types\n  - Identify return value and possible response types\n- Check relevant Redis-Cli examples, if provided\n- Review the Test Plan\n\n### 3. Execute Tasks in Order\n\n#### a. Navigate to the task\n- Identify the files and action required\n- Read existing related files if modifying\n\n#### b. Implement the command\n- Add new command method within matching trait object (for example: `redis/commands/core.py` for core Redis commands)\n- Ensure overloading is implemented for sync and async API\n- Follow Arguments definition section from `specs/redis_commands_guide.md` before defining command arguments\n- Ensure arguments and response types consider bytes representation\n- Ensure response RESP2 and RESP3 compatibility via `response_callbacks`\n\n#### c. Record the command's metadata\n- Obtain the server's metadata for the command: use the `COMMAND INFO` output from the\n  specification's \"Command metadata\" section, or run `COMMAND INFO <name>` against a server\n  that ships the command. For a container command (`BLESS`, `MEMORY`, ...) the subcommands\n  are nested in the reply as `<container>|<sub>`; each one gets its own record.\n- Add a record for the command to `_STATIC_COMMAND_METADATA` in `redis/commands/metadata.py`,\n  keyed lowercase under its module (`\"core\"` for non-module commands, the lowercased prefix\n  such as `\"json\"` or `\"ft\"` for module commands). Key a container subcommand by its\n  space-joined name (`\"bless scan\"`), the form `execute_command` receives.\n- Map the reply onto the record fields and reuse an existing shape (`_CACHEABLE_KEYED`,\n  `_WRITE_KEYED`, `_NONDETERMINISTIC_KEYED`, ...) when one matches; otherwise spell out every\n  field of a `CommandMetadata` inline, as the table does for `touch` and `vrandmember`:\n  - `is_readonly` is the `readonly` command flag - not the `RO` key spec flag. A command the\n    server does not flag `readonly` is neither client-side cacheable nor replica-eligible.\n  - `is_blocking` is the `blocking` flag; `is_script_runner` the `script_runner` flag.\n  - `has_key_argument` is true when `first_key_pos > 0 and step_count > 0`, or when a\n    `movablekeys` command reports a key spec not flagged `not_key`.\n  - `has_nondeterministic_output` and `is_dont_cache` are the `nondeterministic_output` and\n    `dont_cache` tips, matched exactly (`nondeterministic_output_order` is a different tip).\n  - `has_complete_metadata` is true: the record was taken from a reply that carries flags and\n    tips.\n  - `request_policy` / `response_policy` are the keyed or keyless defaults unless a\n    `request_policy:` / `response_policy:` tip overrides them. Withhold both (set them to\n    `None`) when the cluster client must keep resolving the target itself: the command is in\n    `RedisCluster.COMMAND_FLAGS` (as `SCAN` and `BLESS SCAN` are), is `movablekeys`, or tips a\n    policy the client does not implement (`multi_shard`). A keyed command must never be\n    recorded `DEFAULT_KEYLESS`.\n- Document in a comment next to the record anything that diverges from the live reply and\n  why, and extend the provenance note above the table if the command needs a newer server\n  than the table was validated against.\n- Update the guards in `tests/test_command_metadata.py` (shared with its async mirror):\n  add a withheld record to `ALL_WITHHELD_ROUTING_COMMANDS`, a deliberate cacheability\n  divergence to `LIVE_CACHEABILITY_DIVERGENCE`, and raise `STATIC_TABLE_SERVER_VERSION` if\n  the command first ships in a newer release. Then run `tests/test_command_metadata.py`,\n  `tests/test_asyncio/test_command_metadata.py` and the static-table routing tests in\n  `tests/test_cluster.py` / `tests/test_asyncio/test_cluster.py`.\n\n#### d. Verify as you go\n- After each file change, check syntax\n- Ensure imports are correct\n- Verify types are properly defined\n- Verify that response schema is similar for RESP2 and RESP3\n\n### 4. Implement Testing Plan\n\nAfter completing implementation tasks:\n\n- Identify matching test file or create new one if needed\n- Implement all test cases as separate test methods\n- Ensure adding version constraint if specified in the specification\n- Ensure tests cover edge cases\n\n### 5. Run tests\n\n- Run newly added test cases using `pytest` with RESP2 and RESP3 protocol specified via `--protocol` option\n- Ensure that the same test cases passed with both protocols\n- Get back to the Implementation stage if any test failed\n\n### 6. Final Verification\n\nBefore completing:\n\n- ✅ All tasks from plan completed\n- ✅ All tests created and passing\n- ✅ Code follows project conventions\n- ✅ Documentation added/updated as needed\n\n## Output Report\n\nProvide summary:\n\n### Completed Tasks\n- List of all tasks completed\n- Files created (with paths)\n- Files modified (with paths)\n\n### Tests Added\n- Test files created\n- Test cases implemented\n- Test results\n\n\n",".agents/commands/add-new-command/command-specification-template.md":"# $COMMAND_NAME command specification\n\n## Supported version\n\nAdd supported Redis version here. For example: Redis >= 6.2.0\n\n## Command description\n\nAdd a description of the command here.\n\n## Command API\n\nSpecify an API for the command in the format that official docs uses. For example:\n\n```\n$COMMAND_NAME $key $member [NX|XX] [CH] [INCR]\n```\n\n## Redis-CLI examples\n\nAdd relevant Redis-CLI examples here.\n\n## Command metadata\n\nPaste the output of `COMMAND INFO $COMMAND_NAME` from a server that ships the command. It is\nthe source for the command's record in `_STATIC_COMMAND_METADATA`\n(`redis/commands/metadata.py`): the command flags, the `request_policy:` / `response_policy:`\n/ `nondeterministic_output` / `dont_cache` tips and the key specifications. For a container\ncommand, include the nested subcommands.\n\n## Test plan\n\nSpecify how you want to test the command in terms of integration testing. For example:\n\n- Test only with required arguments, assert that single value returned\n- Test with required arguments and optional XX modifier, ensure that 1 returned\n- ...\n",".agents/commands/sync-claude-md/sync-claude-md.md":"---\ndescription: Audit AGENTS.md (the canonical agent guide; CLAUDE.md is only an `@AGENTS.md` include) against the current state of the repo and update it directly. Run when wrapping up a feature or after significant structural changes.\n---\n\n# Execute: Sync AGENTS.md with current repo state\n\nAudit `AGENTS.md` at the repo root and update it to reflect the current state of the codebase. This is the canonical guidance file; `CLAUDE.md` is only an `@AGENTS.md` include and must NOT be edited (editing it would drop the include and duplicate content). Edit `AGENTS.md` directly — do not ask for confirmation first.\n\n## Inputs\n\n- Current `AGENTS.md` (read it before doing anything else — it's the baseline you're updating against).\n- Recent git history. Read `git log --oneline -40` and `git log --diff-filter=A --name-only --since=\"3 months ago\"` to find recently added subsystems.\n- Current file/directory layout of the repo, especially `redis/`, `redis/asyncio/`, `redis/commands/`, `redis/_parsers/`, `tests/`, `.github/workflows/`, `.agents/`, `.claude/`.\n\n## What to verify\n\nWalk through each section of `AGENTS.md` and check:\n\n### 1. Commands section\n- Cross-reference every `invoke` task mentioned against `tasks.py`. Catch wrong defaults, renamed flags, removed/added tasks, changed conftest options.\n- Cross-reference pytest options against the `pytest_addoption` calls in `tests/conftest.py`.\n- Cross-reference the pytest marker list against `[tool.pytest.ini_options].markers` in `pyproject.toml`.\n\n### 2. Architecture section\n- Verify each subpackage list matches what's actually in the directory (e.g., the modules under `redis/commands/`, `redis/_parsers/`, `redis/multidb/`, `redis/asyncio/`, `redis/auth/`, `redis/observability/`, `redis/http/`).\n- Check whether any new top-level subsystem under `redis/` was added since AGENTS.md was last touched and is not mentioned. Use `git log --diff-filter=A --name-only` scoped to `redis/` to find newly added files.\n- Verify any specific symbols I reference (e.g., `READ_COMMANDS`, `RequestPolicy`, `ResponsePolicy`, `CommandsParser`, `ExponentialWithJitterBackoff`) still exist — grep for them.\n- Confirm sync/async mirror claim: spot-check that the async equivalents listed exist under `redis/asyncio/`.\n\n### 3. Python/dependency notes\n- Cross-reference `requires-python`, `[project.optional-dependencies]`, and ruff config in `pyproject.toml`.\n\n### 4. Pointers to other docs\n- Verify referenced files still exist.\n\n## What to add (only if genuinely missing)\n\nIf a new subsystem, workflow, or developer-facing convention was introduced and is non-obvious from reading the directory tree, add a short note. Examples of things worth adding:\n\n- A new top-level package under `redis/` with its own architectural role.\n- A new `invoke` task or a meaningful change to an existing one.\n- A new pytest marker or a new convention about how tests are organized.\n- A newly added `.agents/skills/` or `.agents/commands/` skill that future agents should know about.\n- A new spec file under `specs/` that documents a load-bearing design decision.\n\n## What NOT to add\n\n- Per-file descriptions or anything discoverable by `ls`. AGENTS.md is an orienting document, not a directory listing.\n- Generic Python or git advice.\n- Recent bugfixes or commit-level changes. AGENTS.md describes the repo, not its history.\n- Anything obvious from reading `README.md` or `CONTRIBUTING.md`.\n\n## Output\n\n1. Apply edits directly to `AGENTS.md` using the `Edit` tool. Prefer small, targeted edits over rewrites.\n2. After editing, print a short summary of what changed and why (under 200 words). Group by section: Commands / Architecture / Other.\n3. If nothing needs to change, say so explicitly — do not edit the file just to look productive.\n",".agents/skills/generate-release-notes/SKILL.md":"---\nname: generate-release-notes\ndescription: Generate a redis-py GitHub release-notes draft and save it as a Markdown file. Collects the merged PRs/commits since the previous release, maps each commit to its PR labels, categorizes them into the project's release sections (Breaking Changes, Deprecations, Experimental Features, New Features, Bug Fixes, Maintenance) using the skill's pr-labels-guide, infers a category for unlabeled PRs, and assembles the notes with an optional Highlights section and a contributors footer. Trigger this skill whenever the user says \"release notes\", \"create release notes\", \"generate release notes\", \"draft release notes\", \"prepare release notes\", \"changelog\", or asks for the release notes/changelog of a specific version or branch (e.g. \"release notes for 8.0\", \"generate release notes for the 8.0 branch\", \"what changed since the last release\"). A phrase like \"release notes for 8.0\" means: use branch `8.0` as the release-branch input. Also use when asked to summarize what changed since the last tag.\n---\n\n# Generate Release Notes\n\n## Objective\n\nProduce a redis-py release-notes draft that matches the structure of the project's published GitHub\nreleases, derived from the merged PRs on a given **release branch** since the previous release and\ncategorized by PR label. The release branch is the skill's primary input (step 1). Save the result as a Markdown file. Never publish it — drafting only; the user publishes the release themselves.\n\n**Do not rely on any automated release-drafter output.** We do not use it to decide which PRs are\nincluded or how they are categorized — it misses PRs and miscategorizes them. Collect the PRs\nyourself from the git history (step 2) and categorize every one of them manually using this skill's\nreferences, which are the complete and authoritative rules:\n\n- `references/release-notes-template.md` — the section order, titles, line format, and full skeleton.\n- `references/pr-labels-guide.md` — the authoritative label→category mapping, what each label means,\n  and how to categorize a PR/commit that has no category label.\n\n## Constraints\n\n- **Read-only GitHub access only.** Per the repository owner's standing rule, never run `gh` (or any\n  GitHub API call) that writes — no creating/editing releases, comments, labels, or tags — unless the\n  user explicitly authorizes that specific action in the same turn. Use `gh ... view/list`,\n  `git log`, or GET-only `gh api`/`curl` to gather data. `gh` may fail with a TLS error inside the\n  command sandbox; run it with the sandbox disabled when needed.\n- This skill **only writes the draft Markdown file** in the working tree. It does not commit, tag,\n  push, or create a GitHub release.\n\n## Workflow\n\n### 1. Determine the release range and version\n\n1. **Read the release branch as input.** The branch the release is cut from is the primary input to\n   this skill — it defines which changes are included. Take it from the skill invocation/args if\n   provided; otherwise ask the user for it (e.g. `8.0`, `release/8.0.2`, `master`). Do not guess.\n   Verify it exists (`git rev-parse --verify <branch>`; fetch first if it is a remote branch,\n   `origin/<branch>`). Every change to include is a commit reachable from this branch and not from\n   the previous release tag.\n2. Identify the previous release tag: the most recent `v*` tag reachable from the release branch,\n   `git describe --tags --abbrev=0 <branch>` (or `gh release list -L 5`, read-only). Confirm with the\n   user if a non-default base is intended.\n3. Decide the new version. redis-py ships major, minor, and patch releases (e.g. `8.0.1`, `7.4.1`).\n   Infer minor vs patch from the change set (any Breaking/New Feature → at least a minor bump) and\n   confirm the exact version with the user if unclear. The tag form is `v<version>`.\n\n### 2. Collect the merged PRs / commits in range\n\n1. List the commits on the release branch since the previous tag:\n   `git log --oneline --no-merges <prev-tag>..<branch>`. Each squashed/merged commit title typically\n   ends with `(#<pr-number>)`; extract that number. For merge-commit workflows, use\n   `git log --merges` or the GitHub compare API instead.\n2. For each PR number, fetch its **title, labels, and author** (read-only):\n   `gh pr view <n> --json number,title,labels,author` or\n   `gh api repos/redis/redis-py/pulls/<n>`. Use the PR title for the change line (it is what the\n   published notes use), not the raw commit subject, when they differ.\n3. Drop any PR labeled `skip-changelog`.\n4. If a commit has no associated PR (direct push), use the commit subject and treat it as unlabeled.\n\n### 3. Categorize each commit\n\nFor each remaining commit, categorize it using `references/release-notes-template.md` and\n`references/pr-labels-guide.md`:\n\n- If the PR has one or more **category labels**, list it under **every** matching section — a PR with\n  two category labels appears in both (e.g. `deprecation` + `breakingchange` → both ⚠️ Deprecations\n  and 🔥 Breaking Changes).\n- If the PR has **no category label**, or only topic/process labels, or a non-category repo label\n  (e.g. `bug-fix`, `security`, `techdebt`), infer the section(s) using\n  `references/pr-labels-guide.md` — apply **every** inference rule that matches, so a change may land\n  in more than one section. Note the inferred labels you would suggest applying to the PR.\n- When a category is genuinely ambiguous, place your best guess and leave a\n  `<!-- TODO: confirm category for #<n> -->` marker, then surface it to the user.\n\n### 4. Assemble the draft\n\nFollow `references/release-notes-template.md`:\n\n1. Start with `# Changes`.\n2. Optionally add `## ✨ Highlights` (or a lead prose paragraph) — hand-authored `###` subsections\n   describing each highlight with API names and doc/spec links. Include it **only when the release has\n   something significant to focus the reader on**: a notable new feature, or a breaking change that\n   may affect a large share of users. Omit for routine patch releases. When the release contains\n   Breaking Changes or notable New Features, ask the user whether highlights are wanted.\n3. Emit the category sections **in the order from `release-notes-template.md`** (🚀 New Features,\n   🧪 Experimental, 🔥 Breaking Changes, ⚠️ Deprecations, 🐛 Bug Fixes, 🧰 Maintenance), omitting any\n   empty section. Each line is `- <PR title> (#<n>)`. **Combine PRs that deliver the same\n   feature/change onto one line** with all their numbers in a single bracket group in ascending order,\n   e.g. `- <title> (#3434 #3456 #3467)` (common for paired sync/async PRs or a feature split across\n   follow-ups) — see the grouping rule in `references/release-notes-template.md`.\n4. End with the contributors footer: the thank-you line and the de-duplicated, space-separated list\n   of `@<author>` handles for every included PR.\n\n### 5. Save the draft as a local Markdown file\n\nWrite the assembled notes to a **local Markdown file** so the maintainer can review it and copy the\ncontents into the GitHub release UI by hand. This file is a local review artifact only:\n\n- Default path: `release_notes/release_notes_<version>.md` at the repo root (e.g.\n  `release_notes/release_notes_8.0.2.md`). Create the `release_notes/` folder if it does not exist.\n  Confirm or accept an override path from the user.\n- **Do not commit, tag, push, or create/edit a GitHub release.** The skill only writes the file in\n  the working tree; the maintainer reviews it and publishes manually. (If the `release_notes/` folder\n  is not already git-ignored, mention that so it is not accidentally committed.)\n- The file content is exactly what should be pasted into the release body — no extra wrapper or\n  commentary inside the file.\n\nAfter writing, report the file path and a short summary: the version, the previous tag, the\nper-section commit counts, the count of PRs whose category was inferred (and which ones), and any\n`TODO` markers left in the draft.\n\n## Verification\n\n- Cross-check the commit count: the number of **distinct PR numbers** referenced across all change\n  lines (a grouped line contributes all of its numbers), plus any `skip-changelog`/excluded PRs,\n  should equal the commits in `git log <prev-tag>..<branch>` (minus merge commits). Grouping related\n  PRs reduces the number of lines but not the number of PR references, so reconcile on PR numbers, not\n  lines.\n- Confirm section order and titles match `references/release-notes-template.md` and no empty section\n  was emitted.\n- Confirm every included PR number resolves to a real PR and every contributor handle is included\n  once.\n- Spot-check against the previous published release's formatting (run `gh release view <prev-tag>`).\n",".agents/skills/generate-release-notes/references/pr-labels-guide.md":"# PR Labels Guide for Release Notes\n\nThis guide is the **authoritative, self-contained mapping** from a PR's labels (or, when there is no\nuseful label, its actual change) to a release-notes category. Use it to categorize every commit/PR in\nthe release range. We do **not** rely on any automated release-drafter output to decide what goes\nwhere or which PRs are included — categorize manually using the rules below.\n\n## Category labels (drive a release-notes section)\n\nThese are the only labels that place a commit into a section. When a PR carries labels from more than\none category, **list it under every matching section** — one line per section (e.g. a PR labeled both\n`deprecation` and `breakingchange` appears in both ⚠️ Deprecations and 🔥 Breaking Changes). The\npriority order in this table is used only to pick a single section when a PR has **no** category\nlabel and you must infer one (see \"Inferring a category when there is no label\" below).\n\n| Section | Mapped labels | Classify a PR here when it... |\n| --- | --- | --- |\n| 🚀 New Features | `feature`, `enhancement` | Adds a new Redis command, client capability, public option, or a user-visible enhancement to existing behavior. |\n| 🧪 Experimental Features | `experimental` | Adds a preview/experimental capability not yet covered by compatibility guarantees (often also tagged `experimental` in code/tests). |\n| 🔥 Breaking Changes | `breakingchange` | Changes or removes any public contract: a function/method signature, argument name, default value, return type, raised exception type, wire-protocol/response shape, or persisted/connection config — anything that can break a user upgrading from the latest released version. |\n| ⚠️ Deprecations | `deprecation` | Marks a public API, option, or behavior as deprecated while keeping it working this release (typically emitting a `DeprecationWarning`). If the same PR *also* removes/changes another public contract now, it additionally appears in 🔥 Breaking Changes (list it in both). |\n| 🐛 Bug Fixes | `fix`, `bugfix`, `bug`, `BUG` | Corrects incorrect behavior, a crash, a leak, or a regression in supported functionality. |\n| 🧰 Maintenance | `maintenance`, `dependencies`, `documentation`, `docs`, `testing` | CI/release tooling, dependency bumps, docs, tests, refactors, type-hint/lint cleanups — no user-facing behavior change. |\n\n## Repo labels that need an explicit decision\n\nThese labels exist in the redis-py repo but are **not** one of the category labels above, so a PR\nlabeled only with one of them does not name its own section. Route them as follows, and add the\nproper category label to the PR when possible so the labeling is correct for future reference:\n\n- `bug-fix` — treat as **🐛 Bug Fixes** (synonym of `bug`/`fix`; prefer also adding `bug`).\n- `security` — place under **🐛 Bug Fixes** by default; if the fix changes a public contract, it is\n  **🔥 Breaking Changes** instead. Strongly consider calling it out in `## ✨ Highlights` with the\n  advisory/CVE reference regardless of section.\n- `techdebt` — **🧰 Maintenance**.\n- `deprecation` — route to the **⚠️ Deprecations** section (see the category table above). If the PR\n  removes or changes a public contract *in this release* (not just warns), it belongs in **🔥 Breaking\n  Changes** instead.\n- Topic/area labels — `async`, `cluster`, `windows`, `redis-7`, `redis-py-5`, `RediSearch`,\n  `RedisJSON`, `RedisGraph` — describe *where* a change applies, not *what kind* it is. They never\n  select a section; a PR carrying only these still needs a category decision from the rules below.\n\n## Excluded and non-release labels\n\n- `skip-changelog` — **exclude the PR from the release notes entirely.**\n- Process/triage labels never appear in release notes and are ignored for categorization:\n  `triage`, `question`, `discussion`, `duplicate`, `stale`, `help-wanted`, `good first issue`,\n  `need more info`, `needs-information`, `ready-for-merge`, `changes-requested`,\n  `waiting-for-response`, `docs-review`, `update-docs`, `run-benchmark`, `hacktoberfest-accepted`,\n  `tracking-issue`.\n\n## Inferring a category when there is no label\n\nWhen a PR/commit has no category label, infer the section from the change itself. Apply **every** rule\nthat matches — a single change can land in more than one section (e.g. it both deprecates one API and\nbreaks another, so it appears in Deprecations and Breaking Changes):\n\n1. **New Features** — adds a new Redis command, client/connection capability, or public option, or\n   a clearly user-visible enhancement. Title often starts with `feat`/`feature`/`add`. Label:\n   `feature`.\n2. **Experimental Features** — the change is explicitly a preview/experimental feature (marked\n   experimental in code, tests under the `experimental` marker, or stated as preview). Label:\n   `experimental`.\n3. **Breaking Changes** — the diff changes/removes a public signature, argument name, default,\n   return type, raised exception type, or wire-protocol/response shape against the latest release, or\n   the PR title/body says \"breaking\", \"remove\", \"drop support\". Suggested label: `breakingchange`.\n4. **Deprecations** — the change marks a public API/option/behavior as deprecated but keeps it\n   working (e.g. adds a `DeprecationWarning`, a \"deprecated\" note in a docstring, or a\n   `@deprecated` marker) without removing it this release. Suggested label: `deprecation`.\n5. **Bug Fixes** — fixes incorrect behavior, a crash, a connection/resource leak, or a regression.\n   Title often starts with `fix`. Branch named `bug-*`. Label: `bug`.\n6. **Maintenance** — everything else with no user-facing behavior change: touches only `*.md`,\n   `.github/*`, `docs/`, `tests/`, `pyproject.toml`/`dev_requirements.txt`/`docker-compose.yml`/\n   `dockers/`/`tasks.py`, dependency bumps, refactors, type-hint/lint cleanups, or CI. Branch named\n   `maintenance-*`. Label: `maintenance`.\n\nBranch-naming hints that help inference: branch `bug-*` → Bug Fixes; branch `maintenance-*` →\nMaintenance; branch `feature-*` → New Features; changes touching only `*.md` or `.github/*` →\nMaintenance.\n\nWhen the right category is genuinely ambiguous, record the uncertainty in the draft (a `<!-- TODO:\nconfirm category for #N -->` comment) rather than silently guessing, and surface it to the user so\nthe PR can be labeled correctly.\n\n## sync/async note\n\nA single behavior change is frequently delivered as paired sync/async work, usually in a single PR.\nIf two separate PRs (one sync, one async) implement the same change, treat them as one change: group\nthem onto a single line under the same category, with both PR numbers in one bracket group\n(`- <title> (#3434 #3456)`), per the grouping rule in `release-notes-template.md`.\n",".agents/skills/generate-release-notes/references/release-notes-template.md":"# Release Notes Template\n\nThis is the canonical structure for a redis-py GitHub release: the label-driven category sections\nplus the hand-authored sections maintainers add before publishing. Use it as the skeleton when\nassembling a draft. The label→category rules live in `pr-labels-guide.md`; this file owns the section\norder, titles, line format, and full skeleton.\n\n## Section order\n\nThe canonical sections, in the order they must appear, and the PR labels that route a commit into\neach (see `pr-labels-guide.md` for the full classification rules):\n\n| Order | Section title | PR labels that route a commit here |\n| --- | --- | --- |\n| 1 | `## 🚀 New Features` | `feature`, `enhancement` |\n| 2 | `## 🧪 Experimental Features` | `experimental` |\n| 3 | `## 🔥 Breaking Changes` | `breakingchange` |\n| 4 | `## ⚠️ Deprecations` | `deprecation` |\n| 5 | `## 🐛 Bug Fixes` | `fix`, `bugfix`, `bug`, `BUG` |\n| 6 | `## 🧰 Maintenance` | `maintenance`, `dependencies`, `documentation`, `docs`, `testing` |\n\nRules:\n\n- **Omit empty sections.** A category with no commits is not rendered (see the patch releases below).\n- **Each line** uses the change template `- $TITLE (#$NUMBER)` — the PR title verbatim, then a space, then `(#<pr-number>)`.\n- **Group PRs that deliver the same feature/change onto a single line**, with every related PR number\n  in one space-separated bracket group: `- <title> (#3434 #3456 #3467)`. Use the clearest title for\n  the combined change (usually the primary PR's). This is common for paired sync/async PRs, a\n  feature split across follow-ups, or a fix plus its test/typing follow-up. List the PR numbers in\n  ascending order. Only group PRs that are genuinely the same change — do not merge unrelated work to\n  shorten the list. If the grouped PRs would land in different categories, place the combined line in\n  each matching section (same one-line-per-section rule below).\n- **Exclude** any PR labeled `skip-changelog`.\n- A commit whose PR carries labels from more than one category is listed under **every matching section** — one line per section (e.g. a PR labeled both `deprecation` and `breakingchange` appears in both ⚠️ Deprecations and 🔥 Breaking Changes). The priority order is used only to infer a single section for a PR that has no category label.\n- For any commit/PR with **no category label**, infer the section using `pr-labels-guide.md` before placing it.\n\n## Optional hand-authored sections\n\nThese are written by maintainers, not derived from labels. Include them only when the release warrants it.\n\n- `## ✨ Highlights` — include this **only when the release has something significant worth focusing\n  the reader on**: a notable new feature, or a breaking change that may affect a large share of users.\n  Use `###` subsections with prose describing each highlight, with code/API names and links to\n  docs/specs. Routine patch releases have no Highlights.\n- A lead prose paragraph immediately under `# Changes` (no heading) summarizing a headline change and\n  linking the migration guide. (See the 8.0.0b2 pre-release.)\n- Highlights/lead prose go **above** the generated category sections.\n\n## Contributors footer\n\nEnd with the thank-you line followed by the de-duplicated list of contributor GitHub handles\n(`@handle`), space-separated, for everyone whose PR is included:\n\n```\nWe'd like to thank all the contributors who worked on this release!\n@handle1 @handle2 @handle3\n```\n\n## Full skeleton\n\n```markdown\n# Changes\n\n## ✨ Highlights\n\n### <Notable feature name>\n\n<Prose: what it is, the public API/class/option names, and links to docs or specs. Omit this whole\nsection for routine patch releases.>\n\n## 🚀 New Features\n\n- <PR title> (#<n>)\n\n## 🧪 Experimental Features\n\n- <PR title> (#<n>)\n\n## 🔥 Breaking Changes\n\n- <PR title> (#<n>)\n\n## ⚠️ Deprecations\n\n- <PR title> (#<n>)\n\n## 🐛 Bug Fixes\n\n- <PR title> (#<n>)\n\n## 🧰 Maintenance\n\n- <PR title> (#<n>)\n\nWe'd like to thank all the contributors who worked on this release!\n@handle1 @handle2\n```\n\n## Worked examples from published releases\n\n### Minor/major release (8.0.0-style)\n\n`# Changes` → `## ✨ Highlights` (several `###` prose subsections covering async cluster pubsub,\nkeyspace notifications, RESP3-by-default, type-hint overloads, connection/retry defaults) →\ncategory sections → contributors footer.\n\n### Pre-release (8.0.0b2-style)\n\n`# Changes` → lead prose paragraph on the headline breaking change with a migration-guide link →\nNew Features → Breaking Changes → Maintenance → contributors footer.\n\n### Patch release (8.0.1-style)\n\n`# Changes` → `## 🐛 Bug Fixes` → `## 🧰 Maintenance` → contributors footer. No Highlights, and\nempty categories (Breaking, Experimental, New Features) are omitted entirely.\n",".agents/skills/maintainer-review/SKILL.md":"---\nname: maintainer-review\ndescription: Review a GitHub issue or pull request URL as a redis-py maintainer, with a staged assessment of whether the claim is real, practically important, already solvable with supported functionality, correctly scoped, better served by another design, and worth maintainer and contributor effort. Use when assessing issue validity or severity, deciding whether an issue should be prioritized or closed, determining whether a requested feature represents an unmet need rather than a discoverability or usage gap, judging whether a PR is worth bringing to mergeable quality, comparing open PRs or alternative designs, separating code quality from repository readiness, or drafting a concise maintainer assessment. When closure, additional evidence, or code changes should be requested, also produce a polite, concise, complete, copy-paste-ready maintainer comment.\n---\n\n# Maintainer Review\n\n## Objective\n\nMake a maintainer decision, not a generic diff summary. Separate these questions:\n\n1. Is the claimed behavior real?\n2. What user outcome or constraint exists independently of the reporter's proposed API or fix?\n3. Can supported functionality already achieve that outcome with reasonable composition or configuration?\n4. If a gap remains, is the proposed solution the best design and implementation layer?\n5. Can supported users plausibly reach the gap, and what happens when they do?\n6. Is it important enough to act on now?\n7. If this PR did not already exist, would maintainers choose to open and implement the same work?\n8. For a PR, is this solution worth merging and maintaining?\n9. Can overlapping or stale operations corrupt shared state or clean up resources owned by surviving work?\n10. If competing PRs exist, which single implementation path should maintainers pursue?\n11. What concise maintainer message should communicate closure, an evidence request, or required changes?\n\nTreat an issue's requested keyword argument, callback, flag, class, or implementation strategy as a proposed mechanism, not as the accepted requirement. Do not begin by asking how to implement it. First establish either a concrete unmet user outcome or a violated supported contract — the public API surface, documented Redis command semantics, error/exception types, or wire-protocol behavior — then prove that the proposed mechanism is better than the available alternatives.\n\nLead with the current review state. Use `Preliminary assessment` while runtime approval or evidence is pending, and `Maintainer decision` only when the review can be concluded. Use the diff, issue narrative, and contributor effort as evidence, not as proxies for impact.\n\n## redis-py context to hold throughout\n\nThis library ships two parallel stacks (sync under `redis/`, async under `redis/asyncio/`) that must stay aligned, and exercises behavior across several orthogonal axes. Keep these as the runtime-variant matrix whenever you reason about reach, consistency, or \"the exact same scenario\":\n\n- **Sync vs async**: every behavior question has a sync (`redis/...`) and async (`redis/asyncio/...`) variant; tests mirror under `tests/` and `tests/test_asyncio/`. A defect or fix on one side usually has a counterpart on the other (`specs/sync_async_deduplication_analysis.md`).\n- **Wire protocol vs response shape**: `protocol=2|3` chooses RESP2/RESP3 on the wire; `legacy_responses=True|False` chooses the Python response shape independently. Both axes are exercised in CI (`invoke run-test-matrix`; see `specs/unified_responses_migration_guide.md`). A claim or fix may behave differently across these four combinations.\n- **Topology**: standalone, cluster (`redis/cluster.py`), and sentinel (`redis/sentinel.py`) have distinct routing, connection, and failure behavior. Cluster adds slot mapping, MOVED/ASK redirection, metadata-driven replica routing (`MetadataResolver.is_replica_safe`), and `RequestPolicy`/`ResponsePolicy` resolution.\n- **Parser backend**: pure-Python (`redis/_parsers/resp2.py`, `resp3.py`) vs optional C-accelerated `hiredis`. Behavior must match across both.\n- **Public API compatibility**: signatures, argument names, defaults, return types, error/exception types, and `response_callbacks` shaping are contracts. The compatibility boundary is the latest released version on PyPI, not unreleased branch churn.\n\n## Workflow\n\n### 1. Establish the exact remote target\n\n- Accept a GitHub issue or PR URL as the primary input. Resolve owner, repository, item type, and number before reviewing it.\n- For an issue, read the full report, comments, reproduction, environment (redis-py version, Python version, Redis server version, topology, protocol, hiredis present or not), linked material, and maintainer responses.\n- For a PR, inspect the current remote base and head, full patch, commit history when relevant, tests, linked issue, and review discussion. Do not substitute the current local checkout for the remote change.\n- State the claim in one falsifiable sentence. Separate the observed symptom from the proposed cause or fix.\n- Identify the latest released boundary (the current redis-py release on PyPI) when compatibility or regression claims matter.\n- Verify whether linked evidence matches the PR's exact runtime variant — sync or async, RESP2 or RESP3, `legacy_responses` setting, standalone/cluster/sentinel topology, parser backend — triggering condition, and user outcome. A generic issue title, conceptual similarity, or wording such as `Related to` does not transfer evidence of need to an adjacent variant. If the reported scenario has already been fixed, treat additional variants (the other stack, another protocol, another topology) as new needs requiring their own evidence.\n\nUse read-only GitHub access. Per the repository owner's standing rule, never run `gh` (or any GitHub API call) that writes — no comments, labels, reviews, approvals, branch changes, pushes, or merges — unless the user explicitly authorizes that specific action in the same turn. Read-only inspection (`gh ... list/view/diff/status`, GET-only `gh api`) is fine. A review never authorizes remote writes. Note: `gh` may fail with a TLS error inside the command sandbox; run it with the sandbox disabled when needed.\n\n### 2. Establish the unmet need and challenge the proposed solution\n\nComplete this pass before deeply evaluating a proposed implementation and before any positive issue or PR assessment.\n\nFirst assign one `Need evidence` status:\n\n- **Demonstrated**: The exact scope has a concrete supported scenario, a real-path reproduction, a released compatibility requirement, a violated supported contract (public API, Redis command semantics, error/exception type, or wire-protocol behavior), repeated demand, or a broad invariant with a meaningful consequence.\n- **Plausible but unproven**: The path can exist, but realistic Redis-server behavior, user reach, frequency, consequence, or demand is not established.\n- **Already covered**: A reasonable supported workflow already satisfies the outcome.\n- **Unsupported**: The outcome belongs outside redis-py's contract or at the Redis server, a caller-owned layer, or an application concern.\n\nOnly `Demonstrated` need may receive `Merge-worthy as-is` or `Merge-worthy after focused changes`. For `Plausible but unproven`, prefer `Needs evidence` or `Not worth completing`; for `Already covered` or `Unsupported`, prefer closure or the relevant simpler alternative.\n\n1. Restate the desired user outcome without naming the requested API, class, file, option, or implementation. Separate the actual constraint from the reporter's preferred mechanism.\n2. Trace the closest supported ways to achieve that outcome in the current release across the relevant stack. Inspect the owning code path, public API, tests, and relevant docs rather than assuming that an unfamiliar capability is missing. Consider configuration, composition, client/connection-pool options, callbacks, `response_callbacks`, retry/backoff strategies, existing command mixins, and caller-owned code.\n3. Determine whether the report shows a capability gap, an ergonomics or discoverability gap, an unsupported use case, or no demonstrated gap. A more convenient spelling is not automatically a missing capability.\n4. Compare the proposed solution against the strongest existing approach and at least one better-design candidate: no code change, clearer documentation or validation, a narrower fix, reuse of an existing abstraction or helper (`redis/commands/helpers.py`, existing parser/callback hooks), or enforcement at a more coherent shared boundary.\n5. For each viable approach, compare whether it satisfies the concrete scenario, what new public or internal contract it creates, cross-path consistency (sync/async, protocol, topology), compatibility, and permanent maintenance cost.\n\nDo not treat a test proving that new code can work as evidence that the feature is needed. A mock connection, a `FakeRedis`/monkeypatched parser response, a manually constructed RESP payload, a mock, or a new regression test can establish code-path reachability and implementation correctness; it does not by itself establish realistic Redis-server behavior, user reach, frequency, practical consequence, or demand.\n\nAPI symmetry, naming consistency, and parity with an adjacent command, topology, protocol, or the other stack are design arguments, not evidence of need. Parity may justify work when it removes existing complexity or enforces a broad demonstrated invariant (and sync/async parity is itself a repository invariant), but adding branches, tests, documentation, or public behavior requires independent practical justification.\n\nIf the need is not `Demonstrated`, inspect the patch only far enough to understand its contract, risk, and maintenance cost. Do not turn implementation defects, missing tests, or documentation gaps into a request-changes recommendation, because those questions become merge-blocking only after the need gate passes. If the report provides no concrete scenario, the existing functionality appears sufficient, or the requested mechanism solves only a hypothetical convenience problem, prefer `Needs evidence`, `Close`, `Supersede with a simpler alternative`, or `Not worth completing` over designing the requested feature on the reporter's behalf.\n\nAn existing workaround may change priority or solution shape, but it does not by itself erase a demonstrated correctness, security, compatibility, or lifecycle defect in supported behavior. Evaluate both the unmet outcome and the violated contract.\n\n### 3. Discover competing open PRs proportionally\n\nDo this before deeply evaluating a specified PR. A PR URL selects the starting point, not necessarily the entire comparison set.\n\n- Determine the primary issue from explicit closing keywords, linked issues, timeline/development links, PR body/comments, and the reproduced symptom. State when association is inferred.\n- When an issue is explicitly linked, enumerate all open PRs that address it through cross-references, closing keywords, and development links. Include drafts but label them.\n- When no issue is linked, run a bounded duplicate search using the strongest signals from the title, reproduction, violated invariant, and runtime path (command, topology, protocol, sync/async).\n- Exclude closed/merged PRs from the active comparison set while using them as history when relevant.\n- Require a shared issue, symptom, violated invariant, or materially overlapping path. A shared area label is not enough.\n- If repository access cannot establish completeness, say so instead of claiming every candidate was found.\n\nThe repository also has an `analyze-open-prs` skill for triaging the whole PR queue; use this skill for the focused single-target comparison and that one when the user wants a queue-wide sweep. Compare candidates on need coverage, runtime correctness, placement, tests, compatibility, complexity, readiness, remaining maintainer work, and reusable pieces. Prefer the best maintainable solution, not the first or smallest diff by default.\n\n### 4. Use a two-stage evidence flow\n\nAlways begin with a desk review. Inspect the real runtime path before judging a change as trivial or meaningful. Check callers, public exports, both stacks (sync and async), equivalent protocol/topology/parser paths, persistence and connection lifecycle, cleanup, and focused tests. Inspecting test code is part of the desk review; executing tests, imports, examples, reproductions, benchmarks, or live Redis calls is a runtime probe.\n\nTreat `references/evaluation-framework.md` and the repository's own design notes (`.agent/`, `specs/`, `CLAUDE.md`) as read-only background during review. Verify every current claim against the remote change, current source, tests, docs, release boundary, and runtime evidence. Do not infer issue status or PR correctness from a reference. Recommend a separate documentation/spec-maintenance change when review reveals a durable invariant unless the user explicitly includes that update.\n\nUse this evidence order across the two stages:\n\n1. Trace the closest existing supported capabilities and determine whether they already satisfy the underlying user outcome.\n2. Inspect existing tests and complete the code-path trace, including the mandatory interleaving and ownership pass when triggered, without executing code.\n3. With explicit user approval, run a focused local reproduction of the exact claim when the desk-review rules below require it.\n4. A comparison with the latest release, base branch, or another known-good control.\n5. A broader runtime matrix only when the maintainer decision remains uncertain and the user approves it.\n\n#### Stage 1: desk review\n\nProduce an initial result from static evidence before running code:\n\n##### Mandatory unmet-need and design pass\n\nBefore a positive assessment, complete the pass in step 2 and be able to state all of the following from concrete evidence:\n\n1. The user outcome that current supported behavior cannot achieve, or the supported contract that the reported defect violates.\n2. The closest existing API or composition path and the exact reason it is insufficient.\n3. Why the proposed behavior belongs at the chosen abstraction layer instead of a caller, application, the Redis server, a response callback, validation, documentation, or an existing extension point.\n4. Why the proposed permanent contract is better than no code change and the strongest narrower alternative.\n5. What real scenario, compatibility requirement, violated invariant, or repeated demand justifies the maintenance surface.\n6. Whether maintainers would choose to pursue the same work if no contributor had already supplied a patch.\n\nIf any answer is missing and could change whether code should exist at all, do not call the issue actionable or the PR merge-worthy. Request only the evidence needed to distinguish a genuine capability gap or contract violation from a usage, discoverability, or solution-design problem. This is a product and architecture evidence gap, not a runtime-probe trigger by itself.\n\n##### Mandatory sync/async parity pass\n\nredis-py requires the sync and async stacks to stay aligned. Before any positive assessment of a PR that changes runtime behavior, confirm:\n\n1. Whether the change touches `redis/` (sync) without a mirrored change under `redis/asyncio/` (or vice versa), and whether that asymmetry is intended.\n2. Whether tests were added or updated on both sides (`tests/` and `tests/test_asyncio/`) when both stacks are affected.\n3. Whether type hints, overloads, signatures, defaults, and return types remain identical across stacks (`.agent/sync_async_type_hints_overload_guide.md`).\n\nA behavior fix applied to only one stack is normally `Merge-worthy after focused changes` at best, with the missing mirror called out as required work — unless the divergence is deliberate and documented.\n\n##### Mandatory interleaving and ownership pass\n\nRun this pass before any positive PR assessment when a patch adds, removes, or reorders cleanup, retry, reconnect, cancellation, listeners, shared coroutines/tasks, sockets or connections, pool state, state flags, or mutable state across an `await`, callback, event, or deferred completion. In redis-py this most often means connection-pool acquire/release, async locks held across awaits, pub/sub reconnect, retry/backoff, cluster MOVED/ASK re-slotting, maintenance-notification handlers, and multidb health-check/failover.\n\n1. Name each shared resource or state value and the operation that owns it. Include pool connections, async locks, pub/sub state, in-flight commands, cluster slot maps, coroutines/tasks, sockets, caches, state flags, persistence, and telemetry/observability spans.\n2. Trace at least two overlapping operations, `A` and `B`, across every suspension or re-entry point. Check `A pending -> B starts -> A fails -> B succeeds`, `A pending -> B starts -> B fails -> A succeeds`, disconnect/cancellation between setup and completion, and a stale completion arriving after newer work.\n3. For every cleanup or rollback, identify the exact attempt and resource generation it is allowed to dispose. Treat unconditional cleanup after a suspension point as a regression candidate until the code proves it cannot tear down newer or surviving work. In particular, an async lock released across an `await` inside a pool mutation can let another task observe in-between state — hold the lock across the awaits.\n4. Compare base and head for the survivor invariant. Replacing duplicated work with a closed connection, a returned-then-reused pool slot, reverted slot-map state, or a rejected surviving coroutine is a regression, not a successful cleanup.\n5. Inspect tests for controlled interleavings using events, deferred futures, or callbacks. Require assertions about the surviving operation's observable behavior and final resource state (pool size, connection validity, lock state), not only counts or individual exception results.\n\nDo not mark a concurrency-sensitive patch `Merge-worthy as-is` merely because sequential reconnect, retry, failure, and disconnect tests pass. If the code trace proves an unsafe interleaving, conclude from static evidence and request a focused fix and regression test. If ownership remains ambiguous, keep the result preliminary and request approval for the smallest decisive runtime probe.\n\n- If the claim or PR is decisively negative from a complete reachable code-path trace, conclude the review without a runtime probe. Examples include an impossible or unsupported path, duplicated existing handling, a demonstrated no-op, a direct compatibility break, or a clearly wrong abstraction. Do not call an ambiguous result negative merely to avoid a probe.\n- If the initial result is positive and there is no unresolved runtime concern, and any triggered parity and interleaving passes are complete, the desk review may be sufficient for a final maintainer decision. Do not run a probe only to restate evidence that cannot plausibly change the decision.\n- If the initial result is positive but there is any unresolved runtime concern that could plausibly change claim validity, severity, merge-worthiness, required changes, or the preferred competing PR, stop before executing code. Report a `Preliminary assessment`, name the concern, propose the smallest decisive probe and control, and ask the user for approval to run it.\n- A purely stylistic, documentation, CI-status, or repository-readiness concern does not trigger a runtime probe unless it masks a runtime question.\n\nDo not issue a definitive positive maintainer decision while a decision-relevant runtime concern remains unresolved. If the user declines the probe, keep the result preliminary and state the exact confidence limitation.\n\n#### Stage 2: approved runtime probe\n\nAfter explicit approval, run only the smallest probe needed to resolve the stated concern. Exercise the real public or internal path and include a base, release, or known-good control when relevant. Do not stop at a happy-path smoke check when failure behavior determines the decision. Return to the user for separate approval before expanding materially beyond the approved probe.\n\nFor latency, timeout, buffering, backpressure, or cleanup, measure an observable elapsed-time or state transition where feasible. Do not assume that a mocked unit test exercises real scheduling or Redis-server behavior. Prefer a local probe first; use an approval-gated probe against a live or external Redis deployment only when local evidence cannot settle the decision.\n\nThe repository's local runtime tools are the test harness, not a separate probe skill: `invoke devenv` brings up the docker test environment; `invoke tests` / `invoke standalone-tests` / `invoke cluster-tests` run suites; targeted runs use `pytest ... --protocol 2|3` and topology markers (`onlycluster`, `onlynoncluster`). The repository also has `verify` and `run` skills for driving the library end-to-end. Honor any live-service, Docker, cost, cleanup, and reporting implications when proposing a probe, and call out when a probe needs `invoke devenv` or external infrastructure (scenario tests). Ordinary maintainer review must not depend on spinning up external infrastructure.\n\nFor validation, cleanup, retries, interruption, background work, or concurrency:\n\n- Identify the earliest correct decision point after dynamic inputs are available.\n- List resources acquired before and after it: pool connections, sockets, coroutines/tasks, locks, caches, state mutations, and telemetry.\n- Exercise failure during construction, connection, validation, command execution, and cleanup where applicable.\n- Verify explicit cleanup when failure occurs before normal teardown (connection returned to or removed from the pool, lock released, task cancelled).\n- Require a negative-path test when a connection, coroutine, lock, socket, or state can remain after failure.\n\nStop when additional evidence is unlikely to change validity, severity, or maintainer action.\n\n### 5. Calibrate validity and impact\n\nRead [the evaluation framework](references/evaluation-framework.md) when validity, severity, or merge value is not immediately clear.\n\nAssess claim validity, realistic reach (which stacks, protocols, topologies, and versions), consequence, breadth, frequency, recoverability, compatibility, and severity. Keep observed facts separate from inference and name missing evidence that could change the result.\n\nReport the `Need evidence` status before classifying the need as a capability gap, ergonomics or discoverability gap, unsupported use case, no demonstrated gap, or a defect in supported behavior. Do not assign practical impact to the absence of the requested mechanism when an existing supported workflow already produces the requested outcome. Do not infer practical importance merely from reachability, API asymmetry, or a technically successful patch.\n\nFor a PR, make `Severity` describe the underlying issue or user need. Report patch-induced regression, compatibility, lifecycle, or maintenance risk separately as `Patch risk`.\n\nDo not speculate about AI authorship or contributor intent. Identify weak reports through objective evidence: no reproduction, unsupported input, impossible path, duplicated handling, a test that does not exercise the claim, or a fix that is a runtime no-op.\n\n### 6. Apply the maintainer-effort test\n\nUse one code recommendation:\n\n- **Merge-worthy as-is**: real need, sound placement, proportionate scope, adequate tests, and sync/async parity maintained.\n- **Merge-worthy after focused changes**: real need and viable direction with bounded corrections (which may include adding the missing sync or async mirror, RESP2/RESP3 coverage, or a regression test).\n- **Supersede with a simpler alternative**: real need, but a smaller or more coherent fix is preferable.\n- **Not worth completing**: negligible/unsupported impact, no-op behavior, wrong abstraction, or excessive completion cost.\n\n`Merge-worthy as-is` and `Merge-worthy after focused changes` are invalid unless `Need evidence` is `Demonstrated`. A bounded set of implementation fixes cannot promote a `Plausible but unproven` need into a merge-worthy recommendation.\n\nFor merge-worthy recommendations, use one repository-readiness status when useful:\n\n- **Ready**\n- **CI or review pending**\n- **Rebase or conflict resolution required**\n- **Blocked**\n\nOmit readiness for supersede/not-worth-completing recommendations; CI does not change those code decisions. Do not downgrade sound code only because CI is pending, and do not call a PR ready when semantic changes remain (including a missing sync/async mirror or untested protocol/topology).\n\nFor competing PRs, make one portfolio recommendation: choose one, choose one after focused changes, combine exact pieces into one destination, replace all with a simpler approach, or merge none. State what should happen to every active candidate.\n\nAlways compare the proposed patch with the strongest existing supported approach and at least one alternative: no code change, validation or documentation, a narrower fix, reuse of an existing helper, or a different layer that enforces the invariant consistently. A review is incomplete if it establishes only that the patch works without establishing why the current product cannot meet the underlying need or violates a supported contract, and why this design is preferable.\n\n### 7. Report the decision and action\n\nChoose the assessment language from the current user request and governing repository instructions. Per the repository owner's standing rules, deliver review findings in chat, lead with findings ordered by severity with file/line references, and do not post comments, approvals, or change requests to GitHub or any external service. Maintainer comment drafts are produced in chat as copy-paste-ready text, in English, for the user to send themselves.\n\nUse the matching compact report in the evaluation framework. While runtime approval is pending, use its preliminary-assessment variant and end with the approval request instead of presenting a final recommendation. Keep the report decision-oriented, put unexpected/negative evidence first, and use no more than five evidence bullets by default.\n\nFor PRs, put `Need evidence` before the code recommendation. When the need is not `Demonstrated`, lead with that result, omit repository readiness, and avoid presenting patch fixes as the primary maintainer action.\n\nWhen existing functionality or a better alternative materially affects the decision, state it explicitly in the evidence and recommendation. Name the exact supported path, what it does and does not cover, and why it is preferable. Do not bury a `Not worth completing` or `Supersede with a simpler alternative` conclusion beneath praise for implementation quality.\n\nWhen recommending closure, more evidence, focused changes, or superseding a PR, append a polite, complete, copy-paste-ready English maintainer comment. Include only merge-blocking work in its required-action paragraph. Do not produce a line-by-line review unless requested, equate passing tests with merge-worthiness, or equate a logically correct patch with practical value.\n\n## Resource\n\n- `references/evaluation-framework.md` contains the severity rubric, evidence checks, lifecycle review, issue dispositions, PR value checks, documentation threshold, competing-PR framework, maintainer-comment guidance, and compact report variants.\n",".agents/skills/maintainer-review/references/evaluation-framework.md":"# Maintainer Evaluation Framework\n\nUse this reference when a claim is ambiguous, severity is disputed, or a technically correct PR may not justify permanent maintenance.\n\n## Contents\n\n- [Decision model](#decision-model)\n- [Severity](#severity)\n- [Evidence strength](#evidence-strength)\n- [Unmet need and alternative design gate](#unmet-need-and-alternative-design-gate)\n- [Issue disposition](#issue-disposition)\n- [PR quality and value](#pr-quality-and-value)\n- [Documentation threshold](#documentation-threshold)\n- [Lifecycle and failure paths](#lifecycle-and-failure-paths)\n- [Concurrency and cleanup ownership](#concurrency-and-cleanup-ownership)\n- [Better-alternative prompts](#better-alternative-prompts)\n- [Competing pull requests](#competing-pull-requests)\n- [Maintainer comments](#maintainer-comments)\n- [Compact report variants](#compact-report-variants)\n\n## Decision Model\n\nTreat validity, severity, and merge-worthiness as separate outputs. Also distinguish a `Preliminary assessment`, which may still require approved runtime evidence, from a final `Maintainer decision`. Do not label a provisional positive result as a verdict or final decision.\n\n| Dimension | Question | Strong evidence |\n| --- | --- | --- |\n| Claim validity | Does the exact behavior occur, and is the proposed cause correct? | Reproduction, failing focused test, or complete reachable path |\n| Reachability | Can supported realistic inputs reach it? | Public API trace, real configuration, user report, or release comparison |\n| Consequence | What fails, and is it silent or recoverable? | Observed output/error/state and downstream effect |\n| Breadth | Which stacks (sync/async), protocols (RESP2/RESP3, `legacy_responses`), topologies (standalone/cluster/sentinel), parsers (pure-Python/hiredis), and versions are affected? | Explicit path and compatibility matrix |\n| Frequency | Is it normal, intermittent, or pathological? | Repeats, deterministic preconditions, reports, or telemetry |\n| Need evidence | Is the exact scope demonstrated, merely plausible, already covered, or unsupported? | Same-scope user scenario, real-path reproduction, released compatibility requirement, violated supported contract, repeated demand, or broad consequential invariant |\n| Unmet need | What user outcome cannot be achieved through supported behavior today, or what supported contract is violated? | Concrete scenario plus a trace showing why the closest existing path is insufficient or defective |\n| Existing capability | Can configuration, composition, client/pool options, callbacks, `response_callbacks`, retry strategies, an existing command mixin, or a caller-owned layer already satisfy the outcome? | Current release code, tests, docs, and an exact supported workflow |\n| Compatibility | Is released public API, signature, default, return type, error/exception type, wire-protocol handling, or connection/config behavior changed? | Latest released (PyPI) comparison and contract inspection |\n| Solution fit | Is the requested mechanism the best design and implementation layer? | Proposed solution compared with the strongest existing path and at least one narrower or more coherent alternative |\n| Resource ownership | Can stale, failed, cancelled, or overlapping work mutate or clean up resources owned by surviving work (pool connections, locks, slot maps, pub/sub state)? | Interleaving trace, attempt or generation ownership, and survivor assertions |\n| Maintenance cost | What permanent complexity and review burden is added? | New branches/configuration, changed surface, tests across stacks/protocols, and remaining work |\n\n## Severity\n\n- **Negligible**: no runtime difference, unreachable/unsupported input, cosmetic inconsistency, or harmless edge case. Usually close, document, or decline code complexity.\n- **Low**: real but narrow and recoverable behavior with a simple workaround and no data, security, or compatibility risk. Merge only when the fix is small and strengthens an invariant.\n- **Moderate**: supported use fails or produces incorrect behavior for a meaningful subset (one stack, one protocol, one topology). Prioritize a bounded fix and regression test.\n- **High**: common or important use is broken, released compatibility is seriously affected, sensitive data can leak, connections/pools leak or corrupt, or persistent corruption is possible. Require urgent strong validation.\n- **Critical**: broadly exploitable security impact, severe data loss, or systemic failure requiring coordinated action. Use only with concrete evidence.\n\nSeverity is consequence multiplied by realistic reach and frequency, reduced by recoverability. Do not raise it because prose is alarming or lower it because a diff is small.\n\n## Evidence Strength\n\nBefore calling a claim confirmed, answer:\n\n- Does the reproduction exercise the same public/internal path, stack (sync/async), protocol, topology, and parser?\n- Does failure occur on the relevant base, latest release, or target?\n- Does the regression test fail without the patch and pass with it?\n- Are stale build/`dist`, wrong virtualenv/imports, dependency drift (including hiredis presence/version), proxies, caches, runtime conditions, unavailable Docker/sandbox, authentication, quota, and Redis-server-version differences excluded?\n- Does an equivalent sync/async, RESP2/RESP3, `legacy_responses`, standalone/cluster/sentinel, or hiredis/pure-Python path differ?\n- Is behavior prohibited by a real contract (public API, documented Redis command semantics, error/exception type, wire protocol) or merely surprising?\n- For latency, timeout, buffering, backpressure, or cleanup, was observable time or state measured rather than inferred only from mocks?\n- For shared asynchronous state, do tests control completion order and prove that a stale failure or cleanup cannot affect the surviving operation?\n\nUse `partially confirmed` when the symptom is real but cause/reach/scope is wrong. Use `unproven` when decisive evidence is missing. Use `contradicted` only when evidence directly disproves the claim.\n\n## Unmet Need and Alternative Design Gate\n\nIssue reports often combine a desired outcome with a proposed API or implementation. Treat the proposed mechanism as a hypothesis. Confirm the unmet outcome or violated supported contract before evaluating how well the patch implements that mechanism.\n\n### Linked-evidence scope\n\nEvidence from a linked issue applies only when the issue and PR share the same runtime variant — stack (sync/async), protocol, `legacy_responses` setting, topology, command, parser — trigger, supported configuration, and user outcome. A broad title, ordinary reference, `Related to` statement, or conceptual similarity is not enough. If an earlier change already resolved the concrete reported scenario, an adjacent variant (the other stack, another protocol, another topology) starts with no inherited evidence of need.\n\n### Need evidence status\n\nAssign one status before deep implementation review:\n\n- **Demonstrated**: The exact scope has a concrete supported scenario, real-path reproduction, released compatibility requirement, violated supported contract, repeated demand, or broad invariant with meaningful consequence.\n- **Plausible but unproven**: The code path is possible, but realistic reach, frequency, consequence, Redis-server behavior, or demand is missing.\n- **Already covered**: A reasonable supported workflow already satisfies the outcome.\n- **Unsupported**: The outcome is outside redis-py's contract or belongs at the Redis server, a caller-owned, or an application layer.\n\nOnly `Demonstrated` need can support a merge-worthy code recommendation. `Plausible but unproven` maps to `Needs evidence` or `Not worth completing`, even when the patch is technically correct and its remaining fixes are bounded. `Already covered` and `Unsupported` normally map to closure or a simpler non-core alternative.\n\nBefore accepting an issue or recommending a PR, record:\n\n| Question | Required evidence |\n| --- | --- |\n| What outcome is needed? | A concrete supported scenario stated without the proposed API or fix |\n| What exists today? | The closest current-release API, configuration, composition, extension point, or caller-owned solution |\n| Why is it insufficient? | An exact behavioral, compatibility, lifecycle, or operational constraint, not preference alone |\n| What are the alternatives? | The proposed patch, the strongest existing path, and at least one no-code, narrower, or better-layer design |\n| Why add a contract? | Practical benefit sufficient to justify public surface, runtime branches, cross-stack/protocol tests, documentation, and long-term maintenance |\n\nClassify the result:\n\n- **Capability gap**: a supported, realistic outcome cannot be achieved with current functionality. Code may be warranted.\n- **Ergonomics or discoverability gap**: the outcome is already possible, but the supported route is confusing or unnecessarily difficult. Prefer documentation, validation, or a narrowly justified convenience improvement.\n- **Unsupported use case**: the desired outcome lies outside redis-py's contract or belongs at the Redis server, application, or other caller-owned layer. Do not expand the core API merely to make it possible.\n- **No demonstrated gap**: no concrete scenario proves that existing functionality is insufficient. Request evidence or close rather than designing from the proposed mechanism.\n- **Defect in supported behavior**: the existing path violates a documented or established correctness, security, compatibility, or lifecycle contract (including Redis command semantics, error/exception types, and sync/async parity). A workaround may affect priority or solution shape, but does not erase the defect.\n\nPassing tests for a new implementation establish feasibility and correctness, not need. A mock connection, `FakeRedis`/monkeypatched response, manually constructed RESP payload, mock, or synthetic fixture does not establish realistic Redis-server behavior, user reach, frequency, consequence, or demand. API symmetry and parity with an adjacent command, protocol, topology, or the other stack are design arguments, not need evidence. A technically coherent patch can still be `Not worth completing` when the motivating scenario is hypothetical, already supported, or better solved elsewhere.\n\nUse the counterfactual maintainer test: if the PR did not already exist, would maintainers choose to file and implement the same work from the available evidence? Contributor effort lowers implementation cost but does not create product need or remove permanent maintenance cost.\n\nWhen the need is not `Demonstrated`, inspect implementation only far enough to estimate contract, risk, and maintenance cost. Do not convert patch defects, missing tests, or documentation gaps into a request-changes disposition; those become merge blockers only after the need gate passes.\n\n## Issue Disposition\n\nChoose one:\n\n- **Prioritize**: confirmed moderate-or-higher impact or important invariant with no safe workaround.\n- **Accept, low priority**: confirmed low impact, existing supported functionality is insufficient or defective for the demonstrated scenario, and a proportionate fix is plausible.\n- **Narrow scope**: valid core, overstated paths or expected behavior.\n- **Needs evidence**: plausible but missing a supported reproduction, contract basis, or concrete scenario showing why existing functionality is insufficient.\n- **Close**: duplicate, unsupported, unreachable, contradicted, no-op, already addressed by a reasonable supported path, or not worth permanent complexity.\n\nAsk only for evidence that could change the disposition.\n\n## PR Quality and Value\n\nAssess independently:\n\n1. **Need**: same-scope evidence demonstrates a concrete unmet user outcome or defect in supported behavior, and the closest supported capability cannot reasonably satisfy the scenario as-is. Do not inherit evidence from an adjacent variant (other stack, protocol, topology) or already-fixed scenario.\n2. **Correctness**: the fix covers the claim and meaningful boundaries, and preserves Redis command semantics and error/exception types.\n3. **Placement**: the invariant is enforced once at the owning layer (connection, pool, parser, callback, command mixin) instead of duplicating existing functionality, patching locally, or moving server- or application-owned policy into the client.\n4. **Consistency**: equivalent sync/async, RESP2/RESP3, `legacy_responses`, standalone/cluster/sentinel, parser, and serialization paths stay aligned. Sync/async parity is a repository requirement, not optional polish.\n5. **Tests**: a regression test fails on base, passes on head, and asserts the non-happy-path value/state; protocol-sensitive changes run under both `--protocol 2` and `--protocol 3`. When shared state crosses an asynchronous boundary, tests control relevant completion orders and assert the surviving operation's behavior and final resource state.\n6. **Compatibility**: released public API (signatures, argument names, defaults, return types), error/exception types, wire-protocol handling, packaging extras, and connection/config behavior are preserved or intentionally migrated against the latest release.\n7. **Proportionality**: public surface and complexity match impact.\n8. **Completion cost**: remaining code, tests, docs, design, the missing sync or async mirror, and conflict work is bounded enough to justify attention.\n\nA PR can be correct but not merge-worthy because the need is negligible, the outcome is already supported through a reasonable existing mechanism, the real path is unchanged, equivalent paths remain inconsistent, the abstraction costs more than the benefit, or a simpler design exists at another layer.\n\nDo not use implementation correctness, bounded remaining work, CI status, or contributor effort to upgrade a need that is only `Plausible but unproven`. Merge-worthiness is gated by demonstrated need, not by how close the patch is to completion.\n\nKeep issue severity separate from `Patch risk`. A patch-induced regression, compatibility break, connection/resource leak, lock-release-across-await hazard, or maintenance hazard does not make the underlying issue more severe.\n\n## Documentation Threshold\n\nMake docs merge-blocking only when:\n\n- Existing docs or docstrings become materially false, unsafe, or misleading.\n- Safe/correct use depends on a non-obvious constraint, migration, compatibility boundary, or operational warning.\n- Repository policy, accepted scope, or an explicit maintainer decision requires docs in the same PR.\n- The feature is practically unusable or undiscoverable without a user-facing entrypoint and generated/API discovery is insufficient.\n\nKeep optional discoverability/completeness non-blocking. Do not downgrade a code recommendation solely for optional docs or include optional docs in a required-action paragraph.\n\n## Lifecycle and Failure Paths\n\nApply this section when a change adds validation, fail-fast behavior, cleanup, retry, interruption, background work, connection handling, or concurrency.\n\n- Identify the earliest point where all dynamic inputs required for a correct decision exist.\n- List side effects before and after that point: pool connections, sockets, coroutines/tasks, locks, caches, state, persistence, and telemetry/observability spans.\n- Exercise failure during construction, connection, validation, command execution, persistence, and teardown where those phases exist.\n- Confirm normal teardown is actually entered. If construction/connect fails, verify explicit cleanup (connection returned to or evicted from the pool, lock released, task cancelled).\n- Prefer validation after dynamic configuration is resolved but before avoidable side effects begin.\n- Require a regression test for any connection, coroutine, lock, socket, or state that can remain after failure.\n\n## Concurrency and Cleanup Ownership\n\nApply this section before a positive assessment whenever lifecycle work crosses an `await`, callback, event, deferred completion, retry, reconnect, cancellation, or shared resource boundary. Sequential correctness is insufficient because the patch can improve isolated cleanup while introducing cross-attempt teardown. In redis-py this most often involves connection-pool acquire/release, async locks held across awaits, pub/sub reconnect, cluster slot-map updates, and multidb failover.\n\nUse a two-operation interleaving matrix during desk review:\n\n| Ordering | Required question |\n| --- | --- |\n| `A pending -> B starts -> A fails -> B succeeds` | Can A's cleanup remove or revert anything B needs (a pool connection, a slot map, a lock)? |\n| `A pending -> B starts -> B fails -> A succeeds` | Can B's cleanup leave A successful but non-functional? |\n| `A succeeds -> B starts -> stale A completion` | Can stale A overwrite B's newer state or generation (slot map, reconnect, config)? |\n| setup -> close/cancel -> late completion | Can late work resurrect connections, listeners, state, or tasks after teardown? |\n\nFor each ordering:\n\n- Identify the resource owner before and after every suspension point.\n- Distinguish per-attempt resources from shared pool, connection, slot-map, cache, or pub/sub state.\n- Require cleanup to carry an ownership token, generation, identity check, serialization guarantee, or another invariant that prevents cross-attempt disposal. Keep async locks held across the awaits inside a pool mutation so other tasks cannot observe in-between state.\n- Compare base and head on the survivor invariant. Fewer duplicates do not justify losing the only active connection, task, or state update.\n- Require a controlled interleaving test when the ordering is reachable. The test must assert both the failing operation and the surviving operation's observable behavior after all completions settle.\n\nAn unscoped `finally`, `except`, close handler, cancellation callback, or rollback that mutates shared state after a suspension point is merge-blocking when another operation can still own or use that state.\n\n## Better-Alternative Prompts\n\nStart with the strongest existing supported path, then test at least one additional alternative against the proposed patch. Do not complete a positive review without this comparison.\n\n- Can the requested outcome already be achieved through configuration, composition, client/pool options, callbacks, `response_callbacks`, a retry/backoff strategy, an existing command mixin, or caller-owned code?\n- If the existing route is awkward, is the problem discoverability or ergonomics rather than missing capability?\n- What happens with no code change?\n- Can input validation or an existing helper (`redis/commands/helpers.py`) enforce the invariant earlier?\n- Can the fix be limited to the supported failing path?\n- Would a clearer error/exception or documentation prevent misuse without runtime complexity?\n- Can a failing test reveal a smaller correct change?\n- Is a new public option compensating for an internal ownership problem (pool, lock, connection lifecycle)?\n- Can the same result be achieved in the parser, response callback, or state owner instead of every caller?\n- Is the proposed core behavior actually Redis-server- or application-specific policy that belongs at another layer?\n\n## Competing Pull Requests\n\nRequire an explicit issue link, same reproduction, same violated invariant, or materially overlapping runtime path (command, stack, protocol, topology) before grouping candidates.\n\n| Criterion | Question |\n| --- | --- |\n| Need | Does a concrete user outcome remain unmet, or supported behavior remain defective, after tracing existing functionality? |\n| Existing capability | Could every candidate be avoided by configuration, composition, an extension point, or a better caller-owned solution? |\n| Coverage | Whole confirmed issue, useful subset, or adjacent problem? Both stacks or only one? |\n| Correctness | Real path and meaningful boundaries? |\n| Placement | Owning shared layer? |\n| Tests | Base failure reproduced, protocols/topologies covered, and approaches distinguished? |\n| Compatibility | Released APIs, signatures, error types, protocols, packaging, connection/config? |\n| Complexity | Permanent branches, abstractions, configuration, coupling? |\n| Readiness | Mergeable now or bounded focused work (including missing sync/async mirror)? |\n| Reuse | Exact tests or ideas worth transferring? |\n\nChoose one portfolio action:\n\n- **Prefer one PR**\n- **Prefer one after focused changes**\n- **Combine selectively** into a named destination PR\n- **Replace all** with a simpler/coherent implementation\n- **Merge none**\n\nDo not issue independent approvals for overlapping candidates. State the action for every active PR.\n\n## Maintainer Comments\n\nWrite drafts in English, in chat, as copy-paste-ready text for the user to send themselves. Per the repository owner's standing rules, do not post them to GitHub or any external service, and do not impersonate the user. Produce one when recommending closure, more evidence, focused changes, superseding, or choosing among competing PRs.\n\nKeep it polite, direct, complete, and usually 60-160 words in one to three short paragraphs:\n\n1. Acknowledge the contribution/report.\n2. State the decision with decisive technical evidence.\n3. Give the exact next action or reconsideration condition.\n\nDo not include internal severity labels, speculate about authorship/intent, repeat the full review, or soften the requested action until it is unclear.\n\n### Close\n\n```text\nThanks for taking the time to investigate this. I traced the reported case through <path or behavior>, and <decisive finding>. In the supported path, <practical result>, so the added complexity is not justified by the demonstrated impact.\n\nI am going to close this <issue/PR>. If you can provide <specific reproduction or evidence that would change the decision>, we can revisit the underlying problem with that narrower scope.\n```\n\n### Request Changes\n\n```text\nThanks for the contribution. The underlying issue is valid, and this approach is directionally reasonable. Before we can merge it, please address the following points: <bounded required changes, e.g., mirror the change in the async stack under redis/asyncio/, add RESP2/RESP3 coverage, add a regression test>.\n\nThese changes are needed because <contract, lifecycle, compatibility, sync/async parity, or test reason>. Once they are covered with a regression test that fails on the base and passes on the updated branch, the PR should be ready for another review.\n```\n\nAdapt these templates to evidence. Do not use them as filler.\n\n### Existing Capability or Better Alternative\n\n```text\nThanks for the contribution. I traced the underlying use case through <existing API or workflow>, which already supports <desired outcome and relevant limits>. The proposed change adds <new contract or complexity>, but the issue does not demonstrate a concrete supported case that the existing approach cannot handle.\n\nI am going to close this <issue/PR> for now. If you can provide <specific scenario showing the existing approach is insufficient>, we can revisit the unmet need and choose the narrowest appropriate design from that evidence.\n```\n\n## Compact Report Variants\n\nUse `Maintainer decision` for a concluded review. Use `Preliminary assessment` when a desk review is tentatively positive but a decision-relevant runtime concern remains. `Verdict` is intentionally avoided in the report headings because it does not communicate whether the result is provisional or final.\n\n### Runtime Approval Gate\n\n```markdown\n## Preliminary assessment\n\n<Tentative issue or PR assessment based on desk review only.>\n\n## Static evidence\n\n- <decisive code-path or test-inspection evidence>\n- <what remains uncertain at runtime>\n\n## Proposed runtime probe\n\n- Concern: <the uncertainty that could change the decision>\n- Probe: <smallest exact execution path; note protocol/topology and whether `invoke devenv` or external infra is required>\n- Control: <base, release, or known-good comparison when relevant>\n- Scope: <local-only or any live-service, Docker, cost, mutation, or cleanup implications>\n\n## Approval request\n\n<Ask whether to run this exact probe. Do not present a final positive recommendation yet.>\n```\n\n### Issue\n\n```markdown\n## Maintainer decision\n\n<Real/partial/unproven/contradicted, severity, and disposition.>\n\n## Evidence\n\n- <decisive evidence>\n- <scope or uncertainty (which stacks/protocols/topologies)>\n\n## Existing capability and alternatives\n\n<Closest supported path, why it is or is not sufficient, and the preferred design alternative.>\n\n## Recommendation\n\n<Prioritize, accept low priority, narrow, request evidence, or close.>\n\n## Maintainer comment draft\n\n<Include for closure or an evidence request.>\n```\n\n### Pull Request\n\n```markdown\n## Maintainer decision\n\n<Need, practical impact, and merge-worthiness.>\n\n- Need evidence: <Demonstrated / Plausible but unproven / Already covered / Unsupported>\n- Code recommendation: <code disposition>\n- Repository readiness: <one allowed status; only when useful for a merge-worthy recommendation>\n\n## Evidence\n\n- <runtime/code-path result>\n- <test/compatibility result>\n\n## Existing capability and alternatives\n\n<Closest supported path, why the demonstrated scenario cannot use it or remains defective, and why this patch is preferable to no code change or a narrower design.>\n\n## Issue impact\n\n- Validity: <claim validity>\n- Severity: <underlying issue severity>\n- Reach: <realistic reach: stacks, protocols, topologies, versions>\n\n## Patch risk\n\n<Only meaningful patch-induced risk, including sync/async parity and resource-ownership hazards.>\n\n## PR quality\n\n- Solution fit: <assessment>\n- Tests: <assessment, including protocol/topology coverage>\n- Remaining effort: <bounded/unbounded and why; note any missing sync or async mirror>\n\n## Recommendation\n\n<Merge, focused changes, simpler replacement, or close.>\n\n## Maintainer comment draft\n\n<Only when closure, evidence, or changes should be requested.>\n```\n\n### Competing Pull Requests\n\n```markdown\n## Maintainer decision\n\n<Issue validity, severity, and preferred implementation path.>\n\n## Open PR comparison\n\n| PR   | Approach | Correctness | Tests | Compatibility/complexity | Readiness |\n| ---- | -------- | ----------- | ----- | ------------------------ | --------- |\n| #... | ...      | ...         | ...   | ...                      | ...       |\n\n## Recommendation\n\n<Select one, request focused changes, combine exact pieces, replace all, or merge none.> <State the action for every other active candidate.>\n\n## Maintainer comment drafts\n\n<One draft for each PR that should be closed, changed, or superseded.>\n```\n",".agents/skills/pr-draft-summary/SKILL.md":"---\nname: pr-draft-summary\ndescription: Produce PR-ready output for redis-py — a one-line commit message and a copy-pasteable Markdown PR description (Change summary + Test coverage, 5–6 paragraphs) covering all staged changes plus the commits already on the current branch. Trigger whenever the user's message mentions any of these keywords or phrases: \"PR\", \"pr\", \"PR description\", \"PR draft\", \"pull request\", \"the PR block\", \"commit msg\", \"commit message\", or otherwise asks for a commit message or PR write-up for their local changes.\n---\n\n# PR Draft Summary\n\n## Purpose\n\nAfter eligible work is complete, produce exactly two things for redis-py:\n\n1. A **one-line commit message** (imperative, conventional prefix preferred).\n2. A **PR description** as a single Markdown block the user can copy-paste into\n   the GitHub PR UI with all formatting preserved. Keep it to **5–6 short\n   paragraphs** that explain what the changes do.\n\nBy default, emit both as text in the chat. Write them to a `.md` file **only**\nif the user explicitly asks for a file.\n\n## Scope of Changes to Evaluate\n\nBy default, cover **staged changes** and **commits already on the current\nbranch** (relative to the PR base) — that is what the commit/PR will actually\ncontain. Include **unstaged changes** and **untracked files** only when the user\nexplicitly asks for them; note that untracked files do not appear in\n`git diff --stat`, so pick them up from `git status -sb` (the `??` entries). Use\ncommit messages as supporting context, not as a substitute for reading the\ndiffs. If the in-scope set is empty, reply briefly that no changes were detected\nand skip emitting the blocks.\n\n## Inputs to Collect Automatically (do not ask the user)\n\n- Working tree overview — `git status -sb` (one shot: branch name, ahead/behind,\n  and staged/unstaged/untracked entries, with `??` marking untracked files).\n- What changed, with line counts — `git diff --stat --cached` for staged\n  changes (stats include file names, so a separate `--name-only` is not needed);\n  read the staged diff to write the summary. Run `git diff --stat` (unstaged)\n  only when the user asks to include unstaged/untracked work.\n- PR base — the branch this change will merge into. Accept it as an input: use\n  the base branch the user names (which may be a feature/integration branch, not\n  `master`). When none is given, default to `origin/master` (fall back to\n  `master`). Then set `BASE_REF` to that branch and\n  `BASE_COMMIT=$(git merge-base \"$BASE_REF\" HEAD)`.\n- Branch commits vs the base — `git diff --stat \"${BASE_COMMIT}..HEAD\"` and\n  `git log --oneline --no-merges \"${BASE_COMMIT}..HEAD\"`.\n\nClassify the change from the touched paths: runtime (`redis/`,\n`redis/asyncio/`), tests (`tests/`, `tests/test_asyncio/`), docs/examples\n(`docs/`, `doctests/`, `benchmarks/`), build/test config (`pyproject.toml`,\n`tasks.py`, `dockers/`, …), or repo-meta (`AGENTS.md`, `CLAUDE.md`, `.agents/`,\n`.github/`).\n\n## Workflow\n\n1. Determine the PR base first: use the base branch the user provided; otherwise\n   default to `origin/master` (fall back to `master`). Compute\n   `BASE_REF`/`BASE_COMMIT`, then run the commands above. The base may be a\n   feature or integration branch when the change targets one rather than\n   `master`.\n2. Build the in-scope diff: committed branch changes + staged changes by\n   default; add unstaged and untracked changes only when the user explicitly\n   asks. Use commit messages as supporting context, not as a substitute for\n   inspecting the diff itself.\n3. Infer change type from touched paths (feature, fix, refactor, perf, or\n   docs-with-impact). Pick the commit-message prefix accordingly: `feat:`,\n   `fix:`, `refactor:`, `perf:`, `docs:`, `test:`, `build:`, `chore:`.\n4. Flag **backward-compatibility risk** in the description only when the diff\n   changes the public API (signatures, argument names, defaults, return types),\n   command semantics, error/exception types, wire-protocol (RESP2/RESP3)\n   handling, or persisted/connection config. Judge that risk against the latest\n   released version, not unreleased branch-only churn.\n5. Flag **sync/async parity** explicitly: if the change touches sync code under\n   `redis/` without a mirrored change under `redis/asyncio/` (or vice versa, and\n   likewise `tests/` vs `tests/test_asyncio/`), call it out, since this repo\n   requires both stacks to stay aligned.\n6. If the branch name matches `issue-<number>` (digits only) or an issue number\n   is otherwise known, reference\n   `https://github.com/redis/redis-py/issues/<number>` and add an auto-closing\n   line such as `Resolves #<number>.` Do not block if the issue cannot be fetched.\n7. Draft the two blocks using the format below. The PR description always has a\n   \"Change summary\" and a \"Test coverage\" section. Keep it to 5–6 short\n   paragraphs total; do not pad. In \"Test coverage\", describe the tests added or\n   updated; if none were added, say so briefly and explain why.\n8. Output both blocks as chat text by default. If — and only if — the user\n   explicitly requested a file, also write them to a `.md` file and report its\n   path.\n\n## Output Format\n\nEmit the commit message first, then the PR description. The description must be\na single fenced Markdown block so it can be copied into the GitHub UI with\nformatting intact.\n\n### Commit message\n\n```\n<single-line imperative commit message; conventional prefix preferred, e.g. fix: release async pool lock only after pool mutation>\n```\n\n### PR description\n\n````\n```markdown\n## Change summary\n\n<3–4 paragraphs of prose, starting with \"This pull request resolves/updates/adds ...\"\nusing a verb that matches the change. Explain what the change does and the\nbackground — for bugs, the symptom/repro; for features, what is needed and why —\nthen the concrete changes: the main files/areas touched, the approach, and notable\nedge cases. Call out public-API or backward-compatibility impact and any sync/async\nparity considerations here. Bullets are allowed.>\n\n## Test coverage\n\n<1–2 paragraphs (bullets allowed) describing the tests added or updated and what\nbehavior they exercise (sync and async, edge/failure cases). If the change is not\ntestable or no tests were added, say so briefly and explain why.>\n\nResolves #<number>.   <!-- include only when an issue number is known -->\n```\n````\n\nKeep it tight — 5–6 paragraphs total across both sections, no redundant prose\naround the blocks, and no repeating the same detail in both sections.\n",".agents/skills/prepare-for-takeover/SKILL.md":"---\nname: prepare-for-takeover\ndescription: Prepare copy-paste-ready maintainer comments that mark stalled but valuable redis-py PRs as `open-for-takeover`, inviting the original author or any other contributor to finish the work. Each comment summarizes what is left based on the maintainer's earlier review. Use when the user says \"prepare for takeover\", \"open for takeover\", \"mark as abandoned\", \"invite contributors to finish\", or gives one or more PR numbers/URLs that stalled and should be handed over.\n---\n\n# Prepare for Takeover\n\n## Purpose\n\nSome PRs are worth finishing, but the author has gone quiet and the maintainer has no time to complete them. This skill drafts one comment per PR that:\n\n1. Says the PR seems to have stalled but is still valuable for redis-py.\n2. Announces the `open-for-takeover` label.\n3. Lists what is left, taken from the maintainer's earlier review.\n4. Invites the original author to continue, or anyone else to take over while keeping the author's credit.\n\nFor now the skill only generates the comment texts. It does not apply labels, post comments, or edit workflows.\n\n## Hard rule: no GitHub writes\n\nUse read-only GitHub access only (`gh pr view`, `gh api` GET). Never comment, label, review, or edit anything on GitHub. The user posts the comments and applies the labels manually. `gh` may fail with a TLS error inside the command sandbox; run it with the sandbox disabled when needed.\n\n## Inputs\n\n- One or more PR numbers or URLs. Default repo: `redis/redis-py`. Invoking the skill on a PR makes it eligible; do not require any label (such as `waiting-for-response`), a minimum inactivity period, or other staleness signals.\n- The maintainer's GitHub login: resolve it with `gh api user --jq .login`. Do not guess.\n\n## Data to collect per PR (read-only)\n\n```\ngh pr view <n> -R <owner/repo> --json title,author,state,isDraft,labels,updatedAt,body,reviews,comments\ngh api graphql --paginate -F owner=<owner> -F repo=<repo> -F n=<n> -f query='\n  query($owner: String!, $repo: String!, $n: Int!, $endCursor: String) {\n    repository(owner: $owner, name: $repo) {\n      pullRequest(number: $n) {\n        reviewThreads(first: 100, after: $endCursor) {\n          pageInfo { hasNextPage endCursor }\n          nodes {\n            isResolved isOutdated path line subjectType\n            comments(first: 100) {\n              nodes { author { login } body createdAt pullRequestReview { state } }\n            }\n          }\n        }\n      }\n    }\n  }'\n```\n\nThe second command returns the inline review threads, which `gh pr view` does not include. A `CHANGES_REQUESTED` review often has an empty or short body with the details inline. Per thread:\n\n- `isResolved: true` means the thread was settled (often the author resolved it after the fix, with no reply); treat it as done unless a later maintainer comment reopens it.\n- `isOutdated: true` means the code moved under the comment; check whether it still applies.\n- `subjectType: FILE` is a file-level comment. Its `line` is null, but it is still current.\n\nIgnore pending (unsubmitted) feedback: reviews with `state: PENDING` and thread comments whose `pullRequestReview.state` is `PENDING`. They are drafts visible only to the maintainer, so they must not appear in a public comment or count as an earlier review.\n\nFrom the output, get:\n\n- `author.login`: the PR author.\n- Whether the maintainer has already commented or reviewed (a comment, submitted review or inline comment by the maintainer's login).\n- All maintainer feedback in chronological order: review bodies, inline comments and conversation comments. Read it in full; it is the source for \"What's left\". Drop items in resolved threads, items that a later maintainer comment withdrew or marked as done, and items that a later commit or reply clearly addressed. An empty review body does not mean there is no feedback.\n- Current labels (to point out `waiting-for-response` for removal) and `isDraft`.\n\nIf the PR is closed or merged, skip it and say so. If the maintainer left no feedback in any of these sources, write \"What's left\" from the open TODOs in the PR `body` or ask the user for a line, and do not invent requirements.\n\n## Writing \"What's left\"\n\n- 1 to 5 short bullets, each one concrete action, in the order the maintainer raised them.\n- Paraphrase the review. Do not add new requests that the maintainer did not make.\n- Keep scope notes from the review (for example \"change Fixes #N to Refs #N\", \"keep #N open, retitle the PR\").\n- Add \"Take the PR out of draft.\" when `isDraft` is true and the review asked for it.\n- Put file and symbol names in single backticks.\n\n## Comment template\n\nInclude the greeting line **only** if the maintainer has not commented on or reviewed this PR before. Otherwise start with `@<author>, this PR seems to have stalled...`.\n\n```markdown\nHey @<author>, thank you for your contribution!\n\nThis PR seems to have stalled, but the change is still valuable for redis-py and I would like to see it land. I don't have the bandwidth to finish it myself right now, so I'm marking it as `open-for-takeover`.\n\nWhat's left (details in my review above):\n- <action>\n- <action>\n\n@<author>, if you want to continue, you're very welcome to, just let me know here. Otherwise, anyone interested can take it over: leave a comment first so two people don't work on it at the same time, then open a new PR that builds on this branch (keep the original commits so @<author> keeps the credit) and links back here.\n\nAny help is very welcome!\n```\n\nWhen there is no earlier maintainer review or inline review comment, use `What's left:` without \"(details in my review above)\".\n\n## Writing rules\n\n- Use \"I\", never \"we/us/our\". The maintainer is one person.\n- Never use em dashes, en dashes, or `--` as punctuation; use a single hyphen.\n- No code fences inside the comment (it breaks the outer fence). Use single backticks only.\n- Keep it short; do not restate the full review.\n\n## Output format\n\nStart with one line naming the audience, for example `Written for: the PR authors and other contributors on GitHub.`\n\nThen for each PR, a bold heading with the number and author, followed by the comment in its own fenced ```markdown block:\n\n**#<n>** (@<author>)\n\n```markdown\n<comment>\n```\n\nAfter the blocks, add at most two short notes:\n\n- PRs that still carry `waiting-for-response` (remove it when adding `open-for-takeover`).\n- PRs that were skipped (closed or merged).\n\n## Label reference\n\nIf the user asks for the label details:\n\n- Name: `open-for-takeover`\n- Description: `Stalled but valuable work - anyone is welcome to pick it up and finish it`\n- Color: `0e8a16`\n- Stale bot: add `open-for-takeover` to `exempt-pr-labels` in `.github/workflows/stale-issues.yml`.\n",".claude/commands/add-new-command.md":"---\ndescription: Adds support for a new Redis command from a given specification. Check command-specification-template.md.\nargument-hint: [path-to-specification]\n---\n\nThe full instructions for this command live in\n`.agents/commands/add-new-command/add-new-command.md` (relative to the redis-py repo\nroot), with the spec template at\n`.agents/commands/add-new-command/command-specification-template.md`.\n\n**Read `.agents/commands/add-new-command/add-new-command.md` in full now and follow it\nexactly.**\n\nThe specification file to use is: `$ARGUMENTS`\n",".claude/commands/sync-claude-md.md":"---\ndescription: Audit AGENTS.md (the canonical agent guide; CLAUDE.md is only an `@AGENTS.md` include) against the current state of the repo and update it directly. Run when wrapping up a feature or after significant structural changes.\n---\n\nThe full instructions for this command live in\n`.agents/commands/sync-claude-md/sync-claude-md.md` (relative to the redis-py repo root).\n\n**Read `.agents/commands/sync-claude-md/sync-claude-md.md` in full now and follow it\nexactly.**\n",".claude/skills/generate-release-notes/SKILL.md":"---\nname: generate-release-notes\ndescription: Generate a redis-py GitHub release-notes draft and save it as a Markdown file. Collects the merged PRs/commits since the previous release, maps each commit to its PR labels, categorizes them into the project's release sections (Breaking Changes, Deprecations, Experimental Features, New Features, Bug Fixes, Maintenance) using the skill's pr-labels-guide, infers a category for unlabeled PRs, and assembles the notes with an optional Highlights section and a contributors footer. Trigger this skill whenever the user says \"release notes\", \"create release notes\", \"generate release notes\", \"draft release notes\", \"prepare release notes\", \"changelog\", or asks for the release notes/changelog of a specific version or branch (e.g. \"release notes for 8.0\", \"generate release notes for the 8.0 branch\", \"what changed since the last release\"). A phrase like \"release notes for 8.0\" means: use branch `8.0` as the release-branch input. Also use when asked to summarize what changed since the last tag.\n---\n\nThe full instructions for this skill live in\n`.agents/skills/generate-release-notes/SKILL.md` (relative to the redis-py repo root),\nwith its helper files under `.agents/skills/generate-release-notes/references/`.\n\n**Read `.agents/skills/generate-release-notes/SKILL.md` in full now and follow it\nexactly.** Treat its contents as the body of this skill.\n",".claude/skills/maintainer-review/SKILL.md":"---\nname: maintainer-review\ndescription: Review a GitHub issue or pull request URL as a redis-py maintainer, with a staged assessment of whether the claim is real, practically important, already solvable with supported functionality, correctly scoped, better served by another design, and worth maintainer and contributor effort. Use when assessing issue validity or severity, deciding whether an issue should be prioritized or closed, determining whether a requested feature represents an unmet need rather than a discoverability or usage gap, judging whether a PR is worth bringing to mergeable quality, comparing open PRs or alternative designs, separating code quality from repository readiness, or drafting a concise maintainer assessment. When closure, additional evidence, or code changes should be requested, also produce a polite, concise, complete, copy-paste-ready maintainer comment.\n---\n\nThe full instructions for this skill live in\n`.agents/skills/maintainer-review/SKILL.md` (relative to the redis-py repo root), with\nits helper files under `.agents/skills/maintainer-review/references/`.\n\n**Read `.agents/skills/maintainer-review/SKILL.md` in full now and follow it exactly.**\nTreat its contents as the body of this skill.\n",".claude/skills/pr-draft-summary/SKILL.md":"---\nname: pr-draft-summary\ndescription: Produce PR-ready output for redis-py — a one-line commit message and a copy-pasteable Markdown PR description (Change summary + Test coverage, 5–6 paragraphs) covering all staged changes plus the commits already on the current branch. Trigger whenever the user's message mentions any of these keywords or phrases: \"PR\", \"pr\", \"PR description\", \"PR draft\", \"pull request\", \"the PR block\", \"commit msg\", \"commit message\", or otherwise asks for a commit message or PR write-up for their local changes.\n---\n\nThe full instructions for this skill live in\n`.agents/skills/pr-draft-summary/SKILL.md` (relative to the redis-py repo root).\n\n**Read `.agents/skills/pr-draft-summary/SKILL.md` in full now and follow it exactly.**\nTreat its contents as the body of this skill.\n",".claude/skills/prepare-for-takeover/SKILL.md":"---\nname: prepare-for-takeover\ndescription: Prepare copy-paste-ready maintainer comments that mark stalled but valuable redis-py PRs as `open-for-takeover`, inviting the original author or any other contributor to finish the work. Each comment summarizes what is left based on the maintainer's earlier review. Use when the user says \"prepare for takeover\", \"open for takeover\", \"mark as abandoned\", \"invite contributors to finish\", or gives one or more PR numbers/URLs that stalled and should be handed over.\n---\n\nThe full instructions for this skill live in\n`.agents/skills/prepare-for-takeover/SKILL.md` (relative to the redis-py repo root).\n\n**Read `.agents/skills/prepare-for-takeover/SKILL.md` in full now and follow it exactly.**\nTreat its contents as the body of this skill.\n","AGENTS.md":"# ✅ Instructions for AI agents contributing to redis-py\n\nThis file is the single source of guidance for AI agents working in this repository. It combines project reference material (commands, architecture, dependency notes) with the contributor process rules you must follow.\n\n## Common commands\n\nDevelopment tasks are driven by `invoke` (see `tasks.py`). Activate `.venv` first.\n\n- `invoke devenv` — start the docker-compose test environment (standalone, replica, sentinel, cluster, redis-stack, stunnel). Profiles in `docker-compose.yml` control which containers come up; `--profile all` is the default.\n- `invoke clean` — stop all dockers and remove `build/` and `dist/`.\n- `invoke tests` — run `fixed_client`, `standalone`, then `cluster` test suites in sequence. Accepts `--uvloop`, `--protocol=2|3|\"\"`, `--legacy-responses=true|false`, `--profile`.\n- `invoke standalone-tests` / `invoke cluster-tests` — run just one suite. Cluster suite targets `redis://localhost:16379/0` (`rediss://localhost:27379/0` for TLS).\n- `invoke fixed-client-tests` — runs only tests marked `fixed_client` (tests that pin client config and must not be reconfigured by the global `--protocol` / `--legacy-responses` knobs).\n- `invoke run-test-matrix` — full `protocol` × `legacy_responses` matrix; what CI does.\n- `invoke linters` / `invoke linters-fix` — `ruff check`, `ruff format`, and `vulture redis whitelist.py --min-confidence 80` (use `whitelist.py` to suppress vulture false positives).\n- `invoke all-tests` — linters + tests.\n- `invoke build-docs` — sphinx HTML under `docs/`.\n\nRunning a single test directly with pytest:\n\n```\npytest tests/test_commands.py::TestRedisCommands::test_set -x\npytest tests/test_asyncio/test_commands.py -k \"test_set and not cluster\"\npytest --redis-url=redis://localhost:16379/0 -m onlycluster tests/test_cluster.py\n```\n\nThe pytest plugin in `tests/conftest.py` adds these options (also exposed via invoke wrappers): `--redis-url`, `--redis-ssl-url`, `--redis-mod-url`, `--protocol` (`\"\"` = library default, currently RESP3), `--legacy-responses` (`true|false|default`), `--redis-cluster-nodes`, `--uvloop`. It also adds `--tracemalloc` (off by default, not exposed via invoke), which traces allocations so `ResourceWarning`s show where the object was allocated; it makes cluster client setup several times slower.\n\n### pytest markers\n\nDefined in `pyproject.toml` and used to scope test runs:\n\n- `onlycluster` / `onlynoncluster` — limit a test to one topology.\n- `redismod` — requires Redis modules (Stack image); skipped against cluster.\n- `fixed_client` — test fixes its own client/config; excluded from the protocol/legacy matrix runs.\n- `experimental`, `replica`, `ssl`, `pipeline`, `cp_integration`, `no_mock_connections`.\n\n`tests/test_scenario/` and `tests/test_asyncio/test_scenario/` are scenario tests that require external infrastructure (Redis Enterprise, EntraID, etc.); they are excluded from the default `standalone-tests` / `cluster-tests` runs via `--ignore`.\n\n## Architecture\n\n### Sync and async mirrors\n\nThe library ships two parallel client stacks that must stay in lockstep:\n\n- Sync top-level: `redis/client.py`, `redis/cluster.py`, `redis/connection.py`, `redis/sentinel.py`, `redis/lock.py`, `redis/retry.py`.\n- Async mirrors under `redis/asyncio/`: `client.py`, `cluster.py`, `connection.py`, `sentinel.py`, etc.\n\nWhen changing behavior in one stack, the equivalent change almost always belongs in the other. `specs/sync_async_deduplication_analysis.md` documents the deliberate duplication. The process rules under \"Sync / Async Consistency\" below are mandatory whenever you touch either stack.\n\n### Commands layer (mixins)\n\nCommand surface is composed via mixin classes in `redis/commands/`:\n\n- `core.py` — `CoreCommands` / `AsyncCoreCommands` (every standard Redis command).\n- `cluster.py` — `RedisClusterCommands` / `AsyncRedisClusterCommands`; also exports `READ_COMMANDS`, which is **deprecated and unread**: replica eligibility is decided by the metadata resolver's `is_replica_safe`, so editing this set changes nothing. It is kept only so external importers keep working.\n- `sentinel.py` — sentinel-specific commands.\n- `redismodules.py` — aggregates module mixins (`bf/` Bloom, `json/`, `search/`, `timeseries/`, `vectorset/`). Each module subpackage owns its own command surface and response parsing.\n- `policies.py` — `PolicyResolver` / `StaticPolicyResolver`, the routing view the cluster client resolves per command. Also holds `STATIC_POLICIES`, which is **deprecated and unread**: a frozen verbatim copy of the 7.1.0 table, kept only for external importers and deliberately not derived from or synced with `_STATIC_COMMAND_METADATA`. Change routing in `metadata.py`, never there.\n- `metadata.py` — the command metadata record types: `RequestPolicy` / `ResponsePolicy`, `CommandPolicies` / `PolicyRecords`, and `CommandMetadata` / `CommandMetadataRecordsCache`, plus the metadata resolvers. `CommandMetadata` is the superset record; `_to_command_policies` projects one down to the routing view a `PolicyResolver` serves - projecting to `None` when the record withholds its routing policies, which is how a resolver tells the cluster client to resolve the target itself (the `movablekeys` reads need this: their keys are only in the key specs, so derived policies come out keyless). The two types are kept separate deliberately: `CommandPolicies` shipped in 7.1.0 as a mutable class, and `CommandMetadata` is frozen so one record can be shared across many commands. `redis/_parsers/commands.py` re-exports the policy types from here for backwards compatibility; import them from `redis.commands.metadata`. A resolver is a client-level constructor argument (`metadata_resolver=` on `Redis`, `ConnectionPool`, `RedisCluster`, `NodesManager` and `ClusterPipeline`; on the async side only on `redis.asyncio.RedisCluster`, because the async stack has no client-side cache to be the second consumer), built once and shared with every node client; it serves cluster routing through `resolve_policies`, replica safety through `is_replica_safe`, and client-side-cache eligibility through `is_cacheable`. When only `metadata_resolver` is given the routing resolver is derived from it; an explicit `policy_resolver` still wins for the routing view - which nodes a command targets - but not for anything that view cannot express: replica safety and cache eligibility keep resolving through `metadata_resolver` either way, because a `CommandPolicies` record carries no `is_readonly` flag to answer them with.\n- `helpers.py` — shared utilities (`list_or_args`, pubsub subscription partitioning, etc.).\n\n`Redis` (sync) and `redis.asyncio.Redis` are assembled by inheriting these mixins plus the connection/pool plumbing. Adding a new command means editing both the sync and async mixin classes and, for cluster routing, possibly the command metadata in `metadata.py`, which drives both the routing policies and replica eligibility. Not `READ_COMMANDS` - it is deprecated and unread. There is a `/add-new-command` skill (`.claude/commands/add-new-command.md`) that follows the project's expected workflow; use the spec template at `.agents/commands/add-new-command/command-specification-template.md`. Before adding commands, also read this file (AGENTS.md) and `.agents/sync_async_type_hints_overload_guide.md` — they document the sync/async type-overload convention that command additions must follow. The `/sync-claude-md` skill audits and refreshes this file when the project structure changes.\n\n### Wire protocol and response parsing\n\n`redis/_parsers/` handles RESP framing and response shaping:\n\n- `resp2.py`, `resp3.py` — pure-Python parsers.\n- `hiredis.py` — optional C-accelerated parser; auto-used if `hiredis>=3.2.0` is installed.\n- `response_callbacks.py` — per-command response post-processing (the place where the library reshapes raw protocol output into Python types).\n- `commands.py` — `CommandsParser`, used by the cluster client to learn command metadata via `COMMAND INFO`; the policy record types it returns live in `redis/commands/metadata.py`.\n- `encoders.py` — request encoding.\n\nRESP3 is now the default on the wire. Two knobs interact:\n\n- `protocol=2|3` chooses the wire protocol.\n- `legacy_responses=True|False` chooses the *Python* response shape. `True` (current default) preserves RESP2-era shapes regardless of wire protocol; `False` returns the new unified shapes. The test matrix exercises both axes; see `specs/unified_responses_migration_guide.md`.\n\n### Cluster client\n\n`redis/cluster.py` (`redis/asyncio/cluster.py`) implements topology discovery, slot mapping (`redis/crc.py`, `key_slot`), per-node connection pools, MOVED/ASK redirection, and routing using `RequestPolicy`/`ResponsePolicy` enums from `redis/commands/metadata.py` resolved via `redis/commands/policies.py`. Replica eligibility for read routing is decided by the metadata resolver's `is_replica_safe`, which reads the `is_readonly` flag on the command metadata record; `READ_COMMANDS` is deprecated and no longer consulted.\n\n### Multi-database (Active-Active) client\n\n`redis/multidb/` (and `redis/asyncio/multidb/`) implements a client that fronts multiple Redis deployments with health checks, failure detection, and failover. Key modules: `client.py`, `command_executor.py`, `database.py`, `failover.py`, `failure_detector.py`, `config.py`, plus `circuit.py` (pybreaker) and `exception.py` on the sync side and `healthcheck.py` on the async side. Mirror sync/async changes here too.\n\n### Cross-cutting subsystems\n\n- `redis/auth/` — credential providers and token-based auth (EntraID via `redis-entraid`; JWT support).\n- `redis/cache.py` — client-side caching configuration. `CacheConfig.is_allowed_to_cache` is the single eligibility decision point, and delegates to the `MetadataResolver` the config holds (`redis/commands/metadata.py`), defaulted to the static table and injected by `ConnectionPool.__init__` from the client-level `metadata_resolver=`. `DEFAULT_ALLOW_LIST` is deprecated and unread - change eligibility in `metadata.py`, never there. `CacheProxyConnection.send_command` asks that decision point and then requires the invocation to carry `keys`; a cacheable command without them bypasses the cache rather than raising, so plumbing `keys=` into a command method is a purely additive way to start caching it.\n- `redis/maint_notifications.py` — server-pushed maintenance notifications and the handler that reacts to them. The handlers report the relaxation they apply as `MaintenanceStartedEvent` / `MaintenanceCompletedEvent` (`redis/event.py`) to the optional `event_dispatcher` on `MaintNotificationsConfig`; the MultiDB health check policy (`redis/asyncio/multidb/healthcheck.py`) is the in-tree consumer, relaxing a database's `health_check_timeout` to `probes * (relaxed_timeout + delay)` while its probe client is under maintenance. With notifications enabled, a pooled connection that has readable data while idle is not reconnected outright by `ensure_connection`: `handle_pending_push_notifications` (both connection mixins) drains the buffered pushes with the handlers deferred, applies them only if the socket is still alive (the maintenance is still in progress), and discards them and raises for the pool to reconnect if the server has closed the socket or a stale reply was left on it.\n- `redis/keyspace_notifications.py` — keyspace/keyevent subscription helpers (sync and async variants live side by side).\n- `redis/observability/` (and `redis/asyncio/observability/`) — OpenTelemetry instrumentation. Modules: `attributes.py` (span/metric attribute keys), `config.py`, `metrics.py`, `providers.py`, `recorder.py` (the API the rest of the code calls — `record_operation_duration`, `record_error_count`, `record_pubsub_message`, `record_streaming_lag_from_response`), `registry.py`. Optional, gated by the `otel` extra. The async stack has only `recorder.py` and delegates the rest to the sync package.\n- `redis/event.py` — `EventDispatcher` used to notify subscribers on connection lifecycle events.\n- `redis/http/` — HTTP client used by some auth flows and scenario tests.\n- `redis/retry.py`, `redis/backoff.py` — retry/backoff strategy (`ExponentialWithJitterBackoff` is the default).\n\n### Docker test images\n\n`dockers/` contains config for standalone, cluster, sentinel, and redis-stack containers. The compose file pulls `redislabs/client-libs-test:<tag>` images parameterized by `CLIENT_LIBS_TEST_IMAGE_TAG` / `CLIENT_LIBS_TEST_STACK_IMAGE_TAG`. CI pins these via `CURRENT_REDIS_VERSION` in `.github/workflows/integration.yaml`.\n\n## Python and dependency notes\n\n- `requires-python = \">=3.10\"`; `pyproject.toml` lists the supported versions.\n- Optional extras: `hiredis`, `xxhash`, `ocsp`, `jwt`, `circuit_breaker`, `otel`.\n- Ruff is the only formatter/linter (`target-version = \"py310\"`, `line-length = 88`). `tests/*` has relaxed naming rules; module command packages (`bf`, `timeseries`, `json`, `search`) opt out of pep8 naming.\n- `whitelist.py` is the vulture allowlist — extend it instead of silencing vulture inline.\n\n---\n\n## MUST\n- Preserve public API compatibility (signatures, argument names, defaults, return types)\n- Keep sync and async implementations fully aligned (behavior, signatures, type hints) when possible\n- Run the full test suite and fix all failures\n- Run linting and fix all issues\n- Follow existing code patterns and conventions exactly\n- Maintain Redis command semantics and behavior precisely\n- Optimize for performance and memory efficiency\n\n## MUST NOT\n- Introduce breaking API changes without explicit need\n- Modify unrelated code\n- Change Redis behavior, edge cases, or error semantics\n- Introduce new dependencies without strong justification\n- Add unnecessary abstractions or new patterns\n- Weaken or remove existing tests to make them pass\n\n## General Principles\n- Make minimal, surgical changes\n- Prefer consistency over innovation\n- If unsure, follow existing implementations in the codebase\n- Prioritize correctness first, then performance\n\n## Type Hints & Overloads\n- Use PEP 604 (`X | Y`) syntax\n- Follow `.agents/sync_async_type_hints_overload_guide.md`\n- Keep type hints identical between sync and async APIs\n- Do not change public type signatures unnecessarily\n\n## Imports\n- Import all types at the top of the file\n- Avoid function-level imports unless absolutely necessary\n- Keep imports minimal and consistent\n\n## Sync / Async Consistency\n- Any change in sync must be mirrored in async (and vice versa)\n- Ensure identical signatures, return types, and behavior\n- Ensure overloads remain aligned\n\n## Performance & Memory\n- Prioritize correctness first, then optimize for performance and memory efficiency\n- Never trade correctness for performance\n- Avoid unnecessary allocations in hot paths\n- Avoid creating temporary objects when not needed\n- Reuse existing helpers/utilities instead of duplicating logic\n- Be careful with large data structures\n\n## Redis Semantics\n- Match Redis server behavior exactly\n- Preserve error types and messages\n- Do not introduce silent behavior changes\n- Handle edge cases explicitly and correctly\n\n## Testing\n- Add or update tests for every behavior change\n- Cover edge cases and failure scenarios\n- Keep tests deterministic and stable\n- Do not relax assertions to pass tests\n\n## Linting & Code Quality\n- Run all linters and fix issues\n- Ensure code is clean, readable, and consistent\n- Perform a full code review after changes\n- Refactor if needed to meet project standards\n\n## Documentation\n- Update docstrings when behavior changes\n- Keep docstrings consistent with type hints\n- Avoid redundant or outdated documentation\n\n## Dependencies\n- Do not introduce new dependencies unless absolutely necessary\n- Prefer existing utilities within the project\n\n---\n\n# 🔁 Self-Check Loop (MANDATORY)\n\nBefore finalizing changes, perform this loop:\n\n1. **Correctness Check**\n   - Does the change preserve Redis semantics exactly?\n   - Are edge cases handled correctly?\n\n2. **API Check**\n   - Did any public function signature change?\n   - If yes, revert or justify explicitly\n\n3. **Sync/Async Check**\n   - Are both implementations updated and identical in behavior?\n   - Are type hints and overloads aligned?\n\n4. **Performance Check**\n   - Did this introduce extra allocations or unnecessary work?\n   - Can this be simplified or reused?\n\n5. **Consistency Check**\n   - Does this match existing patterns in the codebase?\n   - Are naming and structure consistent?\n\n6. **Test Check**\n   - Are tests added/updated for the change?\n   - Do all tests pass without weakening assertions?\n\nRepeat this loop until all answers are satisfactory.\n\n---\n\n# 🔍 Diff Validation (MANDATORY)\n\nBefore submitting changes, review the full diff:\n\n- Ensure only relevant files are modified\n- Ensure no unrelated lines were changed\n- Ensure no accidental deletions of logic\n- Ensure no debug code or comments remain\n- Ensure imports were not unnecessarily altered\n- Ensure public APIs were not unintentionally modified\n\nIf any issue is found:\n- Fix it\n- Re-run validation\n\n---\n\n## 🔎 Code Review (MANDATORY)\n\nAfter implementing changes, perform a thorough code review of the final code.\n\n### Verify:\n\n- **Correctness**\n  - Logic is correct and complete\n  - Edge cases are handled properly\n  - Redis semantics are preserved exactly\n\n- **API Stability**\n  - No unintended changes to public APIs\n  - Function signatures, defaults, and return types are unchanged\n\n- **Sync/Async Parity**\n  - Sync and async implementations are fully aligned\n  - Type hints and overloads match\n\n- **Performance**\n  - No unnecessary allocations or inefficiencies introduced\n  - No redundant work or duplicate logic\n\n- **Consistency**\n  - Code follows existing patterns and conventions\n  - Naming and structure match the rest of the codebase\n\n- **Simplicity**\n  - No unnecessary abstractions added\n  - Implementation is as simple as possible\n\n- **Tests**\n  - Tests cover the change adequately\n  - No weakened assertions\n\n### If any issue is found:\n- Fix it\n- Re-run tests and linting\n- Repeat the review\n\n---\n\n# ✅ Final Checklist (must all be true)\n\n- Tests pass\n- Lint passes\n- Sync/async parity verified\n- No API breakage\n- No unnecessary allocations introduced\n- Behavior matches Redis exactly\n- Code follows existing patterns\n- Diff is minimal and clean\n","CLAUDE.md":"@AGENTS.md\n"},"files":{".agents/commands/add-new-command/add-new-command.md":"---\ndescription: Adds support for a new Redis command from a given specification. Check command-specification-template.md.\nargument-hint: [path-to-specification]\n---\n\n# Execute: Add new Redis command support\n\n## Plan to Execute\n\nRead specification file: `$ARGUMENTS`\n\n## Execution Instructions\n\n### 1. Preparations\n\n- Follow instructions from `.agent/instructions.md`\n- Go through the guide `specs/redis_commands_guide.md`\n\n### 2. Read and Understand\n\n- Read the ENTIRE specification carefully\n- Go through Command Description and identify command type (string, list, set, etc.)\n- Go through the Command API:\n  - Identify required and optional arguments\n  - Identify how to match Redis command arguments type to Python types\n  - Identify return value and possible response types\n- Check relevant Redis-Cli examples, if provided\n- Review the Test Plan\n\n### 3. Execute Tasks in Order\n\n#### a. Navigate to the task\n- Identify the files and action required\n- Read existing related files if modifying\n\n#### b. Implement the command\n- Add new command method within matching trait object (for example: `redis/commands/core.py` for core Redis commands)\n- Ensure overloading is implemented for sync and async API\n- Follow Arguments definition section from `specs/redis_commands_guide.md` before defining command arguments\n- Ensure arguments and response types consider bytes representation\n- Ensure response RESP2 and RESP3 compatibility via `response_callbacks`\n\n#### c. Record the command's metadata\n- Obtain the server's metadata for the command: use the `COMMAND INFO` output from the\n  specification's \"Command metadata\" section, or run `COMMAND INFO <name>` against a server\n  that ships the command. For a container command (`BLESS`, `MEMORY`, ...) the subcommands\n  are nested in the reply as `<container>|<sub>`; each one gets its own record.\n- Add a record for the command to `_STATIC_COMMAND_METADATA` in `redis/commands/metadata.py`,\n  keyed lowercase under its module (`\"core\"` for non-module commands, the lowercased prefix\n  such as `\"json\"` or `\"ft\"` for module commands). Key a container subcommand by its\n  space-joined name (`\"bless scan\"`), the form `execute_command` receives.\n- Map the reply onto the record fields and reuse an existing shape (`_CACHEABLE_KEYED`,\n  `_WRITE_KEYED`, `_NONDETERMINISTIC_KEYED`, ...) when one matches; otherwise spell out every\n  field of a `CommandMetadata` inline, as the table does for `touch` and `vrandmember`:\n  - `is_readonly` is the `readonly` command flag - not the `RO` key spec flag. A command the\n    server does not flag `readonly` is neither client-side cacheable nor replica-eligible.\n  - `is_blocking` is the `blocking` flag; `is_script_runner` the `script_runner` flag.\n  - `has_key_argument` is true when `first_key_pos > 0 and step_count > 0`, or when a\n    `movablekeys` command reports a key spec not flagged `not_key`.\n  - `has_nondeterministic_output` and `is_dont_cache` are the `nondeterministic_output` and\n    `dont_cache` tips, matched exactly (`nondeterministic_output_order` is a different tip).\n  - `has_complete_metadata` is true: the record was taken from a reply that carries flags and\n    tips.\n  - `request_policy` / `response_policy` are the keyed or keyless defaults unless a\n    `request_policy:` / `response_policy:` tip overrides them. Withhold both (set them to\n    `None`) when the cluster client must keep resolving the target itself: the command is in\n    `RedisCluster.COMMAND_FLAGS` (as `SCAN` and `BLESS SCAN` are), is `movablekeys`, or tips a\n    policy the client does not implement (`multi_shard`). A keyed command must never be\n    recorded `DEFAULT_KEYLESS`.\n- Document in a comment next to the record anything that diverges from the live reply and\n  why, and extend the provenance note above the table if the command needs a newer server\n  than the table was validated against.\n- Update the guards in `tests/test_command_metadata.py` (shared with its async mirror):\n  add a withheld record to `ALL_WITHHELD_ROUTING_COMMANDS`, a deliberate cacheability\n  divergence to `LIVE_CACHEABILITY_DIVERGENCE`, and raise `STATIC_TABLE_SERVER_VERSION` if\n  the command first ships in a newer release. Then run `tests/test_command_metadata.py`,\n  `tests/test_asyncio/test_command_metadata.py` and the static-table routing tests in\n  `tests/test_cluster.py` / `tests/test_asyncio/test_cluster.py`.\n\n#### d. Verify as you go\n- After each file change, check syntax\n- Ensure imports are correct\n- Verify types are properly defined\n- Verify that response schema is similar for RESP2 and RESP3\n\n### 4. Implement Testing Plan\n\nAfter completing implementation tasks:\n\n- Identify matching test file or create new one if needed\n- Implement all test cases as separate test methods\n- Ensure adding version constraint if specified in the specification\n- Ensure tests cover edge cases\n\n### 5. Run tests\n\n- Run newly added test cases using `pytest` with RESP2 and RESP3 protocol specified via `--protocol` option\n- Ensure that the same test cases passed with both protocols\n- Get back to the Implementation stage if any test failed\n\n### 6. Final Verification\n\nBefore completing:\n\n- ✅ All tasks from plan completed\n- ✅ All tests created and passing\n- ✅ Code follows project conventions\n- ✅ Documentation added/updated as needed\n\n## Output Report\n\nProvide summary:\n\n### Completed Tasks\n- List of all tasks completed\n- Files created (with paths)\n- Files modified (with paths)\n\n### Tests Added\n- Test files created\n- Test cases implemented\n- Test results\n\n\n",".agents/commands/add-new-command/command-specification-template.md":"# $COMMAND_NAME command specification\n\n## Supported version\n\nAdd supported Redis version here. For example: Redis >= 6.2.0\n\n## Command description\n\nAdd a description of the command here.\n\n## Command API\n\nSpecify an API for the command in the format that official docs uses. For example:\n\n```\n$COMMAND_NAME $key $member [NX|XX] [CH] [INCR]\n```\n\n## Redis-CLI examples\n\nAdd relevant Redis-CLI examples here.\n\n## Command metadata\n\nPaste the output of `COMMAND INFO $COMMAND_NAME` from a server that ships the command. It is\nthe source for the command's record in `_STATIC_COMMAND_METADATA`\n(`redis/commands/metadata.py`): the command flags, the `request_policy:` / `response_policy:`\n/ `nondeterministic_output` / `dont_cache` tips and the key specifications. For a container\ncommand, include the nested subcommands.\n\n## Test plan\n\nSpecify how you want to test the command in terms of integration testing. For example:\n\n- Test only with required arguments, assert that single value returned\n- Test with required arguments and optional XX modifier, ensure that 1 returned\n- ...\n",".agents/commands/sync-claude-md/sync-claude-md.md":"---\ndescription: Audit AGENTS.md (the canonical agent guide; CLAUDE.md is only an `@AGENTS.md` include) against the current state of the repo and update it directly. Run when wrapping up a feature or after significant structural changes.\n---\n\n# Execute: Sync AGENTS.md with current repo state\n\nAudit `AGENTS.md` at the repo root and update it to reflect the current state of the codebase. This is the canonical guidance file; `CLAUDE.md` is only an `@AGENTS.md` include and must NOT be edited (editing it would drop the include and duplicate content). Edit `AGENTS.md` directly — do not ask for confirmation first.\n\n## Inputs\n\n- Current `AGENTS.md` (read it before doing anything else — it's the baseline you're updating against).\n- Recent git history. Read `git log --oneline -40` and `git log --diff-filter=A --name-only --since=\"3 months ago\"` to find recently added subsystems.\n- Current file/directory layout of the repo, especially `redis/`, `redis/asyncio/`, `redis/commands/`, `redis/_parsers/`, `tests/`, `.github/workflows/`, `.agents/`, `.claude/`.\n\n## What to verify\n\nWalk through each section of `AGENTS.md` and check:\n\n### 1. Commands section\n- Cross-reference every `invoke` task mentioned against `tasks.py`. Catch wrong defaults, renamed flags, removed/added tasks, changed conftest options.\n- Cross-reference pytest options against the `pytest_addoption` calls in `tests/conftest.py`.\n- Cross-reference the pytest marker list against `[tool.pytest.ini_options].markers` in `pyproject.toml`.\n\n### 2. Architecture section\n- Verify each subpackage list matches what's actually in the directory (e.g., the modules under `redis/commands/`, `redis/_parsers/`, `redis/multidb/`, `redis/asyncio/`, `redis/auth/`, `redis/observability/`, `redis/http/`).\n- Check whether any new top-level subsystem under `redis/` was added since AGENTS.md was last touched and is not mentioned. Use `git log --diff-filter=A --name-only` scoped to `redis/` to find newly added files.\n- Verify any specific symbols I reference (e.g., `READ_COMMANDS`, `RequestPolicy`, `ResponsePolicy`, `CommandsParser`, `ExponentialWithJitterBackoff`) still exist — grep for them.\n- Confirm sync/async mirror claim: spot-check that the async equivalents listed exist under `redis/asyncio/`.\n\n### 3. Python/dependency notes\n- Cross-reference `requires-python`, `[project.optional-dependencies]`, and ruff config in `pyproject.toml`.\n\n### 4. Pointers to other docs\n- Verify referenced files still exist.\n\n## What to add (only if genuinely missing)\n\nIf a new subsystem, workflow, or developer-facing convention was introduced and is non-obvious from reading the directory tree, add a short note. Examples of things worth adding:\n\n- A new top-level package under `redis/` with its own architectural role.\n- A new `invoke` task or a meaningful change to an existing one.\n- A new pytest marker or a new convention about how tests are organized.\n- A newly added `.agents/skills/` or `.agents/commands/` skill that future agents should know about.\n- A new spec file under `specs/` that documents a load-bearing design decision.\n\n## What NOT to add\n\n- Per-file descriptions or anything discoverable by `ls`. AGENTS.md is an orienting document, not a directory listing.\n- Generic Python or git advice.\n- Recent bugfixes or commit-level changes. AGENTS.md describes the repo, not its history.\n- Anything obvious from reading `README.md` or `CONTRIBUTING.md`.\n\n## Output\n\n1. Apply edits directly to `AGENTS.md` using the `Edit` tool. Prefer small, targeted edits over rewrites.\n2. After editing, print a short summary of what changed and why (under 200 words). Group by section: Commands / Architecture / Other.\n3. If nothing needs to change, say so explicitly — do not edit the file just to look productive.\n",".agents/skills/generate-release-notes/SKILL.md":"---\nname: generate-release-notes\ndescription: Generate a redis-py GitHub release-notes draft and save it as a Markdown file. Collects the merged PRs/commits since the previous release, maps each commit to its PR labels, categorizes them into the project's release sections (Breaking Changes, Deprecations, Experimental Features, New Features, Bug Fixes, Maintenance) using the skill's pr-labels-guide, infers a category for unlabeled PRs, and assembles the notes with an optional Highlights section and a contributors footer. Trigger this skill whenever the user says \"release notes\", \"create release notes\", \"generate release notes\", \"draft release notes\", \"prepare release notes\", \"changelog\", or asks for the release notes/changelog of a specific version or branch (e.g. \"release notes for 8.0\", \"generate release notes for the 8.0 branch\", \"what changed since the last release\"). A phrase like \"release notes for 8.0\" means: use branch `8.0` as the release-branch input. Also use when asked to summarize what changed since the last tag.\n---\n\n# Generate Release Notes\n\n## Objective\n\nProduce a redis-py release-notes draft that matches the structure of the project's published GitHub\nreleases, derived from the merged PRs on a given **release branch** since the previous release and\ncategorized by PR label. The release branch is the skill's primary input (step 1). Save the result as a Markdown file. Never publish it — drafting only; the user publishes the release themselves.\n\n**Do not rely on any automated release-drafter output.** We do not use it to decide which PRs are\nincluded or how they are categorized — it misses PRs and miscategorizes them. Collect the PRs\nyourself from the git history (step 2) and categorize every one of them manually using this skill's\nreferences, which are the complete and authoritative rules:\n\n- `references/release-notes-template.md` — the section order, titles, line format, and full skeleton.\n- `references/pr-labels-guide.md` — the authoritative label→category mapping, what each label means,\n  and how to categorize a PR/commit that has no category label.\n\n## Constraints\n\n- **Read-only GitHub access only.** Per the repository owner's standing rule, never run `gh` (or any\n  GitHub API call) that writes — no creating/editing releases, comments, labels, or tags — unless the\n  user explicitly authorizes that specific action in the same turn. Use `gh ... view/list`,\n  `git log`, or GET-only `gh api`/`curl` to gather data. `gh` may fail with a TLS error inside the\n  command sandbox; run it with the sandbox disabled when needed.\n- This skill **only writes the draft Markdown file** in the working tree. It does not commit, tag,\n  push, or create a GitHub release.\n\n## Workflow\n\n### 1. Determine the release range and version\n\n1. **Read the release branch as input.** The branch the release is cut from is the primary input to\n   this skill — it defines which changes are included. Take it from the skill invocation/args if\n   provided; otherwise ask the user for it (e.g. `8.0`, `release/8.0.2`, `master`). Do not guess.\n   Verify it exists (`git rev-parse --verify <branch>`; fetch first if it is a remote branch,\n   `origin/<branch>`). Every change to include is a commit reachable from this branch and not from\n   the previous release tag.\n2. Identify the previous release tag: the most recent `v*` tag reachable from the release branch,\n   `git describe --tags --abbrev=0 <branch>` (or `gh release list -L 5`, read-only). Confirm with the\n   user if a non-default base is intended.\n3. Decide the new version. redis-py ships major, minor, and patch releases (e.g. `8.0.1`, `7.4.1`).\n   Infer minor vs patch from the change set (any Breaking/New Feature → at least a minor bump) and\n   confirm the exact version with the user if unclear. The tag form is `v<version>`.\n\n### 2. Collect the merged PRs / commits in range\n\n1. List the commits on the release branch since the previous tag:\n   `git log --oneline --no-merges <prev-tag>..<branch>`. Each squashed/merged commit title typically\n   ends with `(#<pr-number>)`; extract that number. For merge-commit workflows, use\n   `git log --merges` or the GitHub compare API instead.\n2. For each PR number, fetch its **title, labels, and author** (read-only):\n   `gh pr view <n> --json number,title,labels,author` or\n   `gh api repos/redis/redis-py/pulls/<n>`. Use the PR title for the change line (it is what the\n   published notes use), not the raw commit subject, when they differ.\n3. Drop any PR labeled `skip-changelog`.\n4. If a commit has no associated PR (direct push), use the commit subject and treat it as unlabeled.\n\n### 3. Categorize each commit\n\nFor each remaining commit, categorize it using `references/release-notes-template.md` and\n`references/pr-labels-guide.md`:\n\n- If the PR has one or more **category labels**, list it under **every** matching section — a PR with\n  two category labels appears in both (e.g. `deprecation` + `breakingchange` → both ⚠️ Deprecations\n  and 🔥 Breaking Changes).\n- If the PR has **no category label**, or only topic/process labels, or a non-category repo label\n  (e.g. `bug-fix`, `security`, `techdebt`), infer the section(s) using\n  `references/pr-labels-guide.md` — apply **every** inference rule that matches, so a change may land\n  in more than one section. Note the inferred labels you would suggest applying to the PR.\n- When a category is genuinely ambiguous, place your best guess and leave a\n  `<!-- TODO: confirm category for #<n> -->` marker, then surface it to the user.\n\n### 4. Assemble the draft\n\nFollow `references/release-notes-template.md`:\n\n1. Start with `# Changes`.\n2. Optionally add `## ✨ Highlights` (or a lead prose paragraph) — hand-authored `###` subsections\n   describing each highlight with API names and doc/spec links. Include it **only when the release has\n   something significant to focus the reader on**: a notable new feature, or a breaking change that\n   may affect a large share of users. Omit for routine patch releases. When the release contains\n   Breaking Changes or notable New Features, ask the user whether highlights are wanted.\n3. Emit the category sections **in the order from `release-notes-template.md`** (🚀 New Features,\n   🧪 Experimental, 🔥 Breaking Changes, ⚠️ Deprecations, 🐛 Bug Fixes, 🧰 Maintenance), omitting any\n   empty section. Each line is `- <PR title> (#<n>)`. **Combine PRs that deliver the same\n   feature/change onto one line** with all their numbers in a single bracket group in ascending order,\n   e.g. `- <title> (#3434 #3456 #3467)` (common for paired sync/async PRs or a feature split across\n   follow-ups) — see the grouping rule in `references/release-notes-template.md`.\n4. End with the contributors footer: the thank-you line and the de-duplicated, space-separated list\n   of `@<author>` handles for every included PR.\n\n### 5. Save the draft as a local Markdown file\n\nWrite the assembled notes to a **local Markdown file** so the maintainer can review it and copy the\ncontents into the GitHub release UI by hand. This file is a local review artifact only:\n\n- Default path: `release_notes/release_notes_<version>.md` at the repo root (e.g.\n  `release_notes/release_notes_8.0.2.md`). Create the `release_notes/` folder if it does not exist.\n  Confirm or accept an override path from the user.\n- **Do not commit, tag, push, or create/edit a GitHub release.** The skill only writes the file in\n  the working tree; the maintainer reviews it and publishes manually. (If the `release_notes/` folder\n  is not already git-ignored, mention that so it is not accidentally committed.)\n- The file content is exactly what should be pasted into the release body — no extra wrapper or\n  commentary inside the file.\n\nAfter writing, report the file path and a short summary: the version, the previous tag, the\nper-section commit counts, the count of PRs whose category was inferred (and which ones), and any\n`TODO` markers left in the draft.\n\n## Verification\n\n- Cross-check the commit count: the number of **distinct PR numbers** referenced across all change\n  lines (a grouped line contributes all of its numbers), plus any `skip-changelog`/excluded PRs,\n  should equal the commits in `git log <prev-tag>..<branch>` (minus merge commits). Grouping related\n  PRs reduces the number of lines but not the number of PR references, so reconcile on PR numbers, not\n  lines.\n- Confirm section order and titles match `references/release-notes-template.md` and no empty section\n  was emitted.\n- Confirm every included PR number resolves to a real PR and every contributor handle is included\n  once.\n- Spot-check against the previous published release's formatting (run `gh release view <prev-tag>`).\n",".agents/skills/generate-release-notes/references/pr-labels-guide.md":"# PR Labels Guide for Release Notes\n\nThis guide is the **authoritative, self-contained mapping** from a PR's labels (or, when there is no\nuseful label, its actual change) to a release-notes category. Use it to categorize every commit/PR in\nthe release range. We do **not** rely on any automated release-drafter output to decide what goes\nwhere or which PRs are included — categorize manually using the rules below.\n\n## Category labels (drive a release-notes section)\n\nThese are the only labels that place a commit into a section. When a PR carries labels from more than\none category, **list it under every matching section** — one line per section (e.g. a PR labeled both\n`deprecation` and `breakingchange` appears in both ⚠️ Deprecations and 🔥 Breaking Changes). The\npriority order in this table is used only to pick a single section when a PR has **no** category\nlabel and you must infer one (see \"Inferring a category when there is no label\" below).\n\n| Section | Mapped labels | Classify a PR here when it... |\n| --- | --- | --- |\n| 🚀 New Features | `feature`, `enhancement` | Adds a new Redis command, client capability, public option, or a user-visible enhancement to existing behavior. |\n| 🧪 Experimental Features | `experimental` | Adds a preview/experimental capability not yet covered by compatibility guarantees (often also tagged `experimental` in code/tests). |\n| 🔥 Breaking Changes | `breakingchange` | Changes or removes any public contract: a function/method signature, argument name, default value, return type, raised exception type, wire-protocol/response shape, or persisted/connection config — anything that can break a user upgrading from the latest released version. |\n| ⚠️ Deprecations | `deprecation` | Marks a public API, option, or behavior as deprecated while keeping it working this release (typically emitting a `DeprecationWarning`). If the same PR *also* removes/changes another public contract now, it additionally appears in 🔥 Breaking Changes (list it in both). |\n| 🐛 Bug Fixes | `fix`, `bugfix`, `bug`, `BUG` | Corrects incorrect behavior, a crash, a leak, or a regression in supported functionality. |\n| 🧰 Maintenance | `maintenance`, `dependencies`, `documentation`, `docs`, `testing` | CI/release tooling, dependency bumps, docs, tests, refactors, type-hint/lint cleanups — no user-facing behavior change. |\n\n## Repo labels that need an explicit decision\n\nThese labels exist in the redis-py repo but are **not** one of the category labels above, so a PR\nlabeled only with one of them does not name its own section. Route them as follows, and add the\nproper category label to the PR when possible so the labeling is correct for future reference:\n\n- `bug-fix` — treat as **🐛 Bug Fixes** (synonym of `bug`/`fix`; prefer also adding `bug`).\n- `security` — place under **🐛 Bug Fixes** by default; if the fix changes a public contract, it is\n  **🔥 Breaking Changes** instead. Strongly consider calling it out in `## ✨ Highlights` with the\n  advisory/CVE reference regardless of section.\n- `techdebt` — **🧰 Maintenance**.\n- `deprecation` — route to the **⚠️ Deprecations** section (see the category table above). If the PR\n  removes or changes a public contract *in this release* (not just warns), it belongs in **🔥 Breaking\n  Changes** instead.\n- Topic/area labels — `async`, `cluster`, `windows`, `redis-7`, `redis-py-5`, `RediSearch`,\n  `RedisJSON`, `RedisGraph` — describe *where* a change applies, not *what kind* it is. They never\n  select a section; a PR carrying only these still needs a category decision from the rules below.\n\n## Excluded and non-release labels\n\n- `skip-changelog` — **exclude the PR from the release notes entirely.**\n- Process/triage labels never appear in release notes and are ignored for categorization:\n  `triage`, `question`, `discussion`, `duplicate`, `stale`, `help-wanted`, `good first issue`,\n  `need more info`, `needs-information`, `ready-for-merge`, `changes-requested`,\n  `waiting-for-response`, `docs-review`, `update-docs`, `run-benchmark`, `hacktoberfest-accepted`,\n  `tracking-issue`.\n\n## Inferring a category when there is no label\n\nWhen a PR/commit has no category label, infer the section from the change itself. Apply **every** rule\nthat matches — a single change can land in more than one section (e.g. it both deprecates one API and\nbreaks another, so it appears in Deprecations and Breaking Changes):\n\n1. **New Features** — adds a new Redis command, client/connection capability, or public option, or\n   a clearly user-visible enhancement. Title often starts with `feat`/`feature`/`add`. Label:\n   `feature`.\n2. **Experimental Features** — the change is explicitly a preview/experimental feature (marked\n   experimental in code, tests under the `experimental` marker, or stated as preview). Label:\n   `experimental`.\n3. **Breaking Changes** — the diff changes/removes a public signature, argument name, default,\n   return type, raised exception type, or wire-protocol/response shape against the latest release, or\n   the PR title/body says \"breaking\", \"remove\", \"drop support\". Suggested label: `breakingchange`.\n4. **Deprecations** — the change marks a public API/option/behavior as deprecated but keeps it\n   working (e.g. adds a `DeprecationWarning`, a \"deprecated\" note in a docstring, or a\n   `@deprecated` marker) without removing it this release. Suggested label: `deprecation`.\n5. **Bug Fixes** — fixes incorrect behavior, a crash, a connection/resource leak, or a regression.\n   Title often starts with `fix`. Branch named `bug-*`. Label: `bug`.\n6. **Maintenance** — everything else with no user-facing behavior change: touches only `*.md`,\n   `.github/*`, `docs/`, `tests/`, `pyproject.toml`/`dev_requirements.txt`/`docker-compose.yml`/\n   `dockers/`/`tasks.py`, dependency bumps, refactors, type-hint/lint cleanups, or CI. Branch named\n   `maintenance-*`. Label: `maintenance`.\n\nBranch-naming hints that help inference: branch `bug-*` → Bug Fixes; branch `maintenance-*` →\nMaintenance; branch `feature-*` → New Features; changes touching only `*.md` or `.github/*` →\nMaintenance.\n\nWhen the right category is genuinely ambiguous, record the uncertainty in the draft (a `<!-- TODO:\nconfirm category for #N -->` comment) rather than silently guessing, and surface it to the user so\nthe PR can be labeled correctly.\n\n## sync/async note\n\nA single behavior change is frequently delivered as paired sync/async work, usually in a single PR.\nIf two separate PRs (one sync, one async) implement the same change, treat them as one change: group\nthem onto a single line under the same category, with both PR numbers in one bracket group\n(`- <title> (#3434 #3456)`), per the grouping rule in `release-notes-template.md`.\n",".agents/skills/generate-release-notes/references/release-notes-template.md":"# Release Notes Template\n\nThis is the canonical structure for a redis-py GitHub release: the label-driven category sections\nplus the hand-authored sections maintainers add before publishing. Use it as the skeleton when\nassembling a draft. The label→category rules live in `pr-labels-guide.md`; this file owns the section\norder, titles, line format, and full skeleton.\n\n## Section order\n\nThe canonical sections, in the order they must appear, and the PR labels that route a commit into\neach (see `pr-labels-guide.md` for the full classification rules):\n\n| Order | Section title | PR labels that route a commit here |\n| --- | --- | --- |\n| 1 | `## 🚀 New Features` | `feature`, `enhancement` |\n| 2 | `## 🧪 Experimental Features` | `experimental` |\n| 3 | `## 🔥 Breaking Changes` | `breakingchange` |\n| 4 | `## ⚠️ Deprecations` | `deprecation` |\n| 5 | `## 🐛 Bug Fixes` | `fix`, `bugfix`, `bug`, `BUG` |\n| 6 | `## 🧰 Maintenance` | `maintenance`, `dependencies`, `documentation`, `docs`, `testing` |\n\nRules:\n\n- **Omit empty sections.** A category with no commits is not rendered (see the patch releases below).\n- **Each line** uses the change template `- $TITLE (#$NUMBER)` — the PR title verbatim, then a space, then `(#<pr-number>)`.\n- **Group PRs that deliver the same feature/change onto a single line**, with every related PR number\n  in one space-separated bracket group: `- <title> (#3434 #3456 #3467)`. Use the clearest title for\n  the combined change (usually the primary PR's). This is common for paired sync/async PRs, a\n  feature split across follow-ups, or a fix plus its test/typing follow-up. List the PR numbers in\n  ascending order. Only group PRs that are genuinely the same change — do not merge unrelated work to\n  shorten the list. If the grouped PRs would land in different categories, place the combined line in\n  each matching section (same one-line-per-section rule below).\n- **Exclude** any PR labeled `skip-changelog`.\n- A commit whose PR carries labels from more than one category is listed under **every matching section** — one line per section (e.g. a PR labeled both `deprecation` and `breakingchange` appears in both ⚠️ Deprecations and 🔥 Breaking Changes). The priority order is used only to infer a single section for a PR that has no category label.\n- For any commit/PR with **no category label**, infer the section using `pr-labels-guide.md` before placing it.\n\n## Optional hand-authored sections\n\nThese are written by maintainers, not derived from labels. Include them only when the release warrants it.\n\n- `## ✨ Highlights` — include this **only when the release has something significant worth focusing\n  the reader on**: a notable new feature, or a breaking change that may affect a large share of users.\n  Use `###` subsections with prose describing each highlight, with code/API names and links to\n  docs/specs. Routine patch releases have no Highlights.\n- A lead prose paragraph immediately under `# Changes` (no heading) summarizing a headline change and\n  linking the migration guide. (See the 8.0.0b2 pre-release.)\n- Highlights/lead prose go **above** the generated category sections.\n\n## Contributors footer\n\nEnd with the thank-you line followed by the de-duplicated list of contributor GitHub handles\n(`@handle`), space-separated, for everyone whose PR is included:\n\n```\nWe'd like to thank all the contributors who worked on this release!\n@handle1 @handle2 @handle3\n```\n\n## Full skeleton\n\n```markdown\n# Changes\n\n## ✨ Highlights\n\n### <Notable feature name>\n\n<Prose: what it is, the public API/class/option names, and links to docs or specs. Omit this whole\nsection for routine patch releases.>\n\n## 🚀 New Features\n\n- <PR title> (#<n>)\n\n## 🧪 Experimental Features\n\n- <PR title> (#<n>)\n\n## 🔥 Breaking Changes\n\n- <PR title> (#<n>)\n\n## ⚠️ Deprecations\n\n- <PR title> (#<n>)\n\n## 🐛 Bug Fixes\n\n- <PR title> (#<n>)\n\n## 🧰 Maintenance\n\n- <PR title> (#<n>)\n\nWe'd like to thank all the contributors who worked on this release!\n@handle1 @handle2\n```\n\n## Worked examples from published releases\n\n### Minor/major release (8.0.0-style)\n\n`# Changes` → `## ✨ Highlights` (several `###` prose subsections covering async cluster pubsub,\nkeyspace notifications, RESP3-by-default, type-hint overloads, connection/retry defaults) →\ncategory sections → contributors footer.\n\n### Pre-release (8.0.0b2-style)\n\n`# Changes` → lead prose paragraph on the headline breaking change with a migration-guide link →\nNew Features → Breaking Changes → Maintenance → contributors footer.\n\n### Patch release (8.0.1-style)\n\n`# Changes` → `## 🐛 Bug Fixes` → `## 🧰 Maintenance` → contributors footer. No Highlights, and\nempty categories (Breaking, Experimental, New Features) are omitted entirely.\n",".agents/skills/maintainer-review/SKILL.md":"---\nname: maintainer-review\ndescription: Review a GitHub issue or pull request URL as a redis-py maintainer, with a staged assessment of whether the claim is real, practically important, already solvable with supported functionality, correctly scoped, better served by another design, and worth maintainer and contributor effort. Use when assessing issue validity or severity, deciding whether an issue should be prioritized or closed, determining whether a requested feature represents an unmet need rather than a discoverability or usage gap, judging whether a PR is worth bringing to mergeable quality, comparing open PRs or alternative designs, separating code quality from repository readiness, or drafting a concise maintainer assessment. When closure, additional evidence, or code changes should be requested, also produce a polite, concise, complete, copy-paste-ready maintainer comment.\n---\n\n# Maintainer Review\n\n## Objective\n\nMake a maintainer decision, not a generic diff summary. Separate these questions:\n\n1. Is the claimed behavior real?\n2. What user outcome or constraint exists independently of the reporter's proposed API or fix?\n3. Can supported functionality already achieve that outcome with reasonable composition or configuration?\n4. If a gap remains, is the proposed solution the best design and implementation layer?\n5. Can supported users plausibly reach the gap, and what happens when they do?\n6. Is it important enough to act on now?\n7. If this PR did not already exist, would maintainers choose to open and implement the same work?\n8. For a PR, is this solution worth merging and maintaining?\n9. Can overlapping or stale operations corrupt shared state or clean up resources owned by surviving work?\n10. If competing PRs exist, which single implementation path should maintainers pursue?\n11. What concise maintainer message should communicate closure, an evidence request, or required changes?\n\nTreat an issue's requested keyword argument, callback, flag, class, or implementation strategy as a proposed mechanism, not as the accepted requirement. Do not begin by asking how to implement it. First establish either a concrete unmet user outcome or a violated supported contract — the public API surface, documented Redis command semantics, error/exception types, or wire-protocol behavior — then prove that the proposed mechanism is better than the available alternatives.\n\nLead with the current review state. Use `Preliminary assessment` while runtime approval or evidence is pending, and `Maintainer decision` only when the review can be concluded. Use the diff, issue narrative, and contributor effort as evidence, not as proxies for impact.\n\n## redis-py context to hold throughout\n\nThis library ships two parallel stacks (sync under `redis/`, async under `redis/asyncio/`) that must stay aligned, and exercises behavior across several orthogonal axes. Keep these as the runtime-variant matrix whenever you reason about reach, consistency, or \"the exact same scenario\":\n\n- **Sync vs async**: every behavior question has a sync (`redis/...`) and async (`redis/asyncio/...`) variant; tests mirror under `tests/` and `tests/test_asyncio/`. A defect or fix on one side usually has a counterpart on the other (`specs/sync_async_deduplication_analysis.md`).\n- **Wire protocol vs response shape**: `protocol=2|3` chooses RESP2/RESP3 on the wire; `legacy_responses=True|False` chooses the Python response shape independently. Both axes are exercised in CI (`invoke run-test-matrix`; see `specs/unified_responses_migration_guide.md`). A claim or fix may behave differently across these four combinations.\n- **Topology**: standalone, cluster (`redis/cluster.py`), and sentinel (`redis/sentinel.py`) have distinct routing, connection, and failure behavior. Cluster adds slot mapping, MOVED/ASK redirection, metadata-driven replica routing (`MetadataResolver.is_replica_safe`), and `RequestPolicy`/`ResponsePolicy` resolution.\n- **Parser backend**: pure-Python (`redis/_parsers/resp2.py`, `resp3.py`) vs optional C-accelerated `hiredis`. Behavior must match across both.\n- **Public API compatibility**: signatures, argument names, defaults, return types, error/exception types, and `response_callbacks` shaping are contracts. The compatibility boundary is the latest released version on PyPI, not unreleased branch churn.\n\n## Workflow\n\n### 1. Establish the exact remote target\n\n- Accept a GitHub issue or PR URL as the primary input. Resolve owner, repository, item type, and number before reviewing it.\n- For an issue, read the full report, comments, reproduction, environment (redis-py version, Python version, Redis server version, topology, protocol, hiredis present or not), linked material, and maintainer responses.\n- For a PR, inspect the current remote base and head, full patch, commit history when relevant, tests, linked issue, and review discussion. Do not substitute the current local checkout for the remote change.\n- State the claim in one falsifiable sentence. Separate the observed symptom from the proposed cause or fix.\n- Identify the latest released boundary (the current redis-py release on PyPI) when compatibility or regression claims matter.\n- Verify whether linked evidence matches the PR's exact runtime variant — sync or async, RESP2 or RESP3, `legacy_responses` setting, standalone/cluster/sentinel topology, parser backend — triggering condition, and user outcome. A generic issue title, conceptual similarity, or wording such as `Related to` does not transfer evidence of need to an adjacent variant. If the reported scenario has already been fixed, treat additional variants (the other stack, another protocol, another topology) as new needs requiring their own evidence.\n\nUse read-only GitHub access. Per the repository owner's standing rule, never run `gh` (or any GitHub API call) that writes — no comments, labels, reviews, approvals, branch changes, pushes, or merges — unless the user explicitly authorizes that specific action in the same turn. Read-only inspection (`gh ... list/view/diff/status`, GET-only `gh api`) is fine. A review never authorizes remote writes. Note: `gh` may fail with a TLS error inside the command sandbox; run it with the sandbox disabled when needed.\n\n### 2. Establish the unmet need and challenge the proposed solution\n\nComplete this pass before deeply evaluating a proposed implementation and before any positive issue or PR assessment.\n\nFirst assign one `Need evidence` status:\n\n- **Demonstrated**: The exact scope has a concrete supported scenario, a real-path reproduction, a released compatibility requirement, a violated supported contract (public API, Redis command semantics, error/exception type, or wire-protocol behavior), repeated demand, or a broad invariant with a meaningful consequence.\n- **Plausible but unproven**: The path can exist, but realistic Redis-server behavior, user reach, frequency, consequence, or demand is not established.\n- **Already covered**: A reasonable supported workflow already satisfies the outcome.\n- **Unsupported**: The outcome belongs outside redis-py's contract or at the Redis server, a caller-owned layer, or an application concern.\n\nOnly `Demonstrated` need may receive `Merge-worthy as-is` or `Merge-worthy after focused changes`. For `Plausible but unproven`, prefer `Needs evidence` or `Not worth completing`; for `Already covered` or `Unsupported`, prefer closure or the relevant simpler alternative.\n\n1. Restate the desired user outcome without naming the requested API, class, file, option, or implementation. Separate the actual constraint from the reporter's preferred mechanism.\n2. Trace the closest supported ways to achieve that outcome in the current release across the relevant stack. Inspect the owning code path, public API, tests, and relevant docs rather than assuming that an unfamiliar capability is missing. Consider configuration, composition, client/connection-pool options, callbacks, `response_callbacks`, retry/backoff strategies, existing command mixins, and caller-owned code.\n3. Determine whether the report shows a capability gap, an ergonomics or discoverability gap, an unsupported use case, or no demonstrated gap. A more convenient spelling is not automatically a missing capability.\n4. Compare the proposed solution against the strongest existing approach and at least one better-design candidate: no code change, clearer documentation or validation, a narrower fix, reuse of an existing abstraction or helper (`redis/commands/helpers.py`, existing parser/callback hooks), or enforcement at a more coherent shared boundary.\n5. For each viable approach, compare whether it satisfies the concrete scenario, what new public or internal contract it creates, cross-path consistency (sync/async, protocol, topology), compatibility, and permanent maintenance cost.\n\nDo not treat a test proving that new code can work as evidence that the feature is needed. A mock connection, a `FakeRedis`/monkeypatched parser response, a manually constructed RESP payload, a mock, or a new regression test can establish code-path reachability and implementation correctness; it does not by itself establish realistic Redis-server behavior, user reach, frequency, practical consequence, or demand.\n\nAPI symmetry, naming consistency, and parity with an adjacent command, topology, protocol, or the other stack are design arguments, not evidence of need. Parity may justify work when it removes existing complexity or enforces a broad demonstrated invariant (and sync/async parity is itself a repository invariant), but adding branches, tests, documentation, or public behavior requires independent practical justification.\n\nIf the need is not `Demonstrated`, inspect the patch only far enough to understand its contract, risk, and maintenance cost. Do not turn implementation defects, missing tests, or documentation gaps into a request-changes recommendation, because those questions become merge-blocking only after the need gate passes. If the report provides no concrete scenario, the existing functionality appears sufficient, or the requested mechanism solves only a hypothetical convenience problem, prefer `Needs evidence`, `Close`, `Supersede with a simpler alternative`, or `Not worth completing` over designing the requested feature on the reporter's behalf.\n\nAn existing workaround may change priority or solution shape, but it does not by itself erase a demonstrated correctness, security, compatibility, or lifecycle defect in supported behavior. Evaluate both the unmet outcome and the violated contract.\n\n### 3. Discover competing open PRs proportionally\n\nDo this before deeply evaluating a specified PR. A PR URL selects the starting point, not necessarily the entire comparison set.\n\n- Determine the primary issue from explicit closing keywords, linked issues, timeline/development links, PR body/comments, and the reproduced symptom. State when association is inferred.\n- When an issue is explicitly linked, enumerate all open PRs that address it through cross-references, closing keywords, and development links. Include drafts but label them.\n- When no issue is linked, run a bounded duplicate search using the strongest signals from the title, reproduction, violated invariant, and runtime path (command, topology, protocol, sync/async).\n- Exclude closed/merged PRs from the active comparison set while using them as history when relevant.\n- Require a shared issue, symptom, violated invariant, or materially overlapping path. A shared area label is not enough.\n- If repository access cannot establish completeness, say so instead of claiming every candidate was found.\n\nThe repository also has an `analyze-open-prs` skill for triaging the whole PR queue; use this skill for the focused single-target comparison and that one when the user wants a queue-wide sweep. Compare candidates on need coverage, runtime correctness, placement, tests, compatibility, complexity, readiness, remaining maintainer work, and reusable pieces. Prefer the best maintainable solution, not the first or smallest diff by default.\n\n### 4. Use a two-stage evidence flow\n\nAlways begin with a desk review. Inspect the real runtime path before judging a change as trivial or meaningful. Check callers, public exports, both stacks (sync and async), equivalent protocol/topology/parser paths, persistence and connection lifecycle, cleanup, and focused tests. Inspecting test code is part of the desk review; executing tests, imports, examples, reproductions, benchmarks, or live Redis calls is a runtime probe.\n\nTreat `references/evaluation-framework.md` and the repository's own design notes (`.agent/`, `specs/`, `CLAUDE.md`) as read-only background during review. Verify every current claim against the remote change, current source, tests, docs, release boundary, and runtime evidence. Do not infer issue status or PR correctness from a reference. Recommend a separate documentation/spec-maintenance change when review reveals a durable invariant unless the user explicitly includes that update.\n\nUse this evidence order across the two stages:\n\n1. Trace the closest existing supported capabilities and determine whether they already satisfy the underlying user outcome.\n2. Inspect existing tests and complete the code-path trace, including the mandatory interleaving and ownership pass when triggered, without executing code.\n3. With explicit user approval, run a focused local reproduction of the exact claim when the desk-review rules below require it.\n4. A comparison with the latest release, base branch, or another known-good control.\n5. A broader runtime matrix only when the maintainer decision remains uncertain and the user approves it.\n\n#### Stage 1: desk review\n\nProduce an initial result from static evidence before running code:\n\n##### Mandatory unmet-need and design pass\n\nBefore a positive assessment, complete the pass in step 2 and be able to state all of the following from concrete evidence:\n\n1. The user outcome that current supported behavior cannot achieve, or the supported contract that the reported defect violates.\n2. The closest existing API or composition path and the exact reason it is insufficient.\n3. Why the proposed behavior belongs at the chosen abstraction layer instead of a caller, application, the Redis server, a response callback, validation, documentation, or an existing extension point.\n4. Why the proposed permanent contract is better than no code change and the strongest narrower alternative.\n5. What real scenario, compatibility requirement, violated invariant, or repeated demand justifies the maintenance surface.\n6. Whether maintainers would choose to pursue the same work if no contributor had already supplied a patch.\n\nIf any answer is missing and could change whether code should exist at all, do not call the issue actionable or the PR merge-worthy. Request only the evidence needed to distinguish a genuine capability gap or contract violation from a usage, discoverability, or solution-design problem. This is a product and architecture evidence gap, not a runtime-probe trigger by itself.\n\n##### Mandatory sync/async parity pass\n\nredis-py requires the sync and async stacks to stay aligned. Before any positive assessment of a PR that changes runtime behavior, confirm:\n\n1. Whether the change touches `redis/` (sync) without a mirrored change under `redis/asyncio/` (or vice versa), and whether that asymmetry is intended.\n2. Whether tests were added or updated on both sides (`tests/` and `tests/test_asyncio/`) when both stacks are affected.\n3. Whether type hints, overloads, signatures, defaults, and return types remain identical across stacks (`.agent/sync_async_type_hints_overload_guide.md`).\n\nA behavior fix applied to only one stack is normally `Merge-worthy after focused changes` at best, with the missing mirror called out as required work — unless the divergence is deliberate and documented.\n\n##### Mandatory interleaving and ownership pass\n\nRun this pass before any positive PR assessment when a patch adds, removes, or reorders cleanup, retry, reconnect, cancellation, listeners, shared coroutines/tasks, sockets or connections, pool state, state flags, or mutable state across an `await`, callback, event, or deferred completion. In redis-py this most often means connection-pool acquire/release, async locks held across awaits, pub/sub reconnect, retry/backoff, cluster MOVED/ASK re-slotting, maintenance-notification handlers, and multidb health-check/failover.\n\n1. Name each shared resource or state value and the operation that owns it. Include pool connections, async locks, pub/sub state, in-flight commands, cluster slot maps, coroutines/tasks, sockets, caches, state flags, persistence, and telemetry/observability spans.\n2. Trace at least two overlapping operations, `A` and `B`, across every suspension or re-entry point. Check `A pending -> B starts -> A fails -> B succeeds`, `A pending -> B starts -> B fails -> A succeeds`, disconnect/cancellation between setup and completion, and a stale completion arriving after newer work.\n3. For every cleanup or rollback, identify the exact attempt and resource generation it is allowed to dispose. Treat unconditional cleanup after a suspension point as a regression candidate until the code proves it cannot tear down newer or surviving work. In particular, an async lock released across an `await` inside a pool mutation can let another task observe in-between state — hold the lock across the awaits.\n4. Compare base and head for the survivor invariant. Replacing duplicated work with a closed connection, a returned-then-reused pool slot, reverted slot-map state, or a rejected surviving coroutine is a regression, not a successful cleanup.\n5. Inspect tests for controlled interleavings using events, deferred futures, or callbacks. Require assertions about the surviving operation's observable behavior and final resource state (pool size, connection validity, lock state), not only counts or individual exception results.\n\nDo not mark a concurrency-sensitive patch `Merge-worthy as-is` merely because sequential reconnect, retry, failure, and disconnect tests pass. If the code trace proves an unsafe interleaving, conclude from static evidence and request a focused fix and regression test. If ownership remains ambiguous, keep the result preliminary and request approval for the smallest decisive runtime probe.\n\n- If the claim or PR is decisively negative from a complete reachable code-path trace, conclude the review without a runtime probe. Examples include an impossible or unsupported path, duplicated existing handling, a demonstrated no-op, a direct compatibility break, or a clearly wrong abstraction. Do not call an ambiguous result negative merely to avoid a probe.\n- If the initial result is positive and there is no unresolved runtime concern, and any triggered parity and interleaving passes are complete, the desk review may be sufficient for a final maintainer decision. Do not run a probe only to restate evidence that cannot plausibly change the decision.\n- If the initial result is positive but there is any unresolved runtime concern that could plausibly change claim validity, severity, merge-worthiness, required changes, or the preferred competing PR, stop before executing code. Report a `Preliminary assessment`, name the concern, propose the smallest decisive probe and control, and ask the user for approval to run it.\n- A purely stylistic, documentation, CI-status, or repository-readiness concern does not trigger a runtime probe unless it masks a runtime question.\n\nDo not issue a definitive positive maintainer decision while a decision-relevant runtime concern remains unresolved. If the user declines the probe, keep the result preliminary and state the exact confidence limitation.\n\n#### Stage 2: approved runtime probe\n\nAfter explicit approval, run only the smallest probe needed to resolve the stated concern. Exercise the real public or internal path and include a base, release, or known-good control when relevant. Do not stop at a happy-path smoke check when failure behavior determines the decision. Return to the user for separate approval before expanding materially beyond the approved probe.\n\nFor latency, timeout, buffering, backpressure, or cleanup, measure an observable elapsed-time or state transition where feasible. Do not assume that a mocked unit test exercises real scheduling or Redis-server behavior. Prefer a local probe first; use an approval-gated probe against a live or external Redis deployment only when local evidence cannot settle the decision.\n\nThe repository's local runtime tools are the test harness, not a separate probe skill: `invoke devenv` brings up the docker test environment; `invoke tests` / `invoke standalone-tests` / `invoke cluster-tests` run suites; targeted runs use `pytest ... --protocol 2|3` and topology markers (`onlycluster`, `onlynoncluster`). The repository also has `verify` and `run` skills for driving the library end-to-end. Honor any live-service, Docker, cost, cleanup, and reporting implications when proposing a probe, and call out when a probe needs `invoke devenv` or external infrastructure (scenario tests). Ordinary maintainer review must not depend on spinning up external infrastructure.\n\nFor validation, cleanup, retries, interruption, background work, or concurrency:\n\n- Identify the earliest correct decision point after dynamic inputs are available.\n- List resources acquired before and after it: pool connections, sockets, coroutines/tasks, locks, caches, state mutations, and telemetry.\n- Exercise failure during construction, connection, validation, command execution, and cleanup where applicable.\n- Verify explicit cleanup when failure occurs before normal teardown (connection returned to or removed from the pool, lock released, task cancelled).\n- Require a negative-path test when a connection, coroutine, lock, socket, or state can remain after failure.\n\nStop when additional evidence is unlikely to change validity, severity, or maintainer action.\n\n### 5. Calibrate validity and impact\n\nRead [the evaluation framework](references/evaluation-framework.md) when validity, severity, or merge value is not immediately clear.\n\nAssess claim validity, realistic reach (which stacks, protocols, topologies, and versions), consequence, breadth, frequency, recoverability, compatibility, and severity. Keep observed facts separate from inference and name missing evidence that could change the result.\n\nReport the `Need evidence` status before classifying the need as a capability gap, ergonomics or discoverability gap, unsupported use case, no demonstrated gap, or a defect in supported behavior. Do not assign practical impact to the absence of the requested mechanism when an existing supported workflow already produces the requested outcome. Do not infer practical importance merely from reachability, API asymmetry, or a technically successful patch.\n\nFor a PR, make `Severity` describe the underlying issue or user need. Report patch-induced regression, compatibility, lifecycle, or maintenance risk separately as `Patch risk`.\n\nDo not speculate about AI authorship or contributor intent. Identify weak reports through objective evidence: no reproduction, unsupported input, impossible path, duplicated handling, a test that does not exercise the claim, or a fix that is a runtime no-op.\n\n### 6. Apply the maintainer-effort test\n\nUse one code recommendation:\n\n- **Merge-worthy as-is**: real need, sound placement, proportionate scope, adequate tests, and sync/async parity maintained.\n- **Merge-worthy after focused changes**: real need and viable direction with bounded corrections (which may include adding the missing sync or async mirror, RESP2/RESP3 coverage, or a regression test).\n- **Supersede with a simpler alternative**: real need, but a smaller or more coherent fix is preferable.\n- **Not worth completing**: negligible/unsupported impact, no-op behavior, wrong abstraction, or excessive completion cost.\n\n`Merge-worthy as-is` and `Merge-worthy after focused changes` are invalid unless `Need evidence` is `Demonstrated`. A bounded set of implementation fixes cannot promote a `Plausible but unproven` need into a merge-worthy recommendation.\n\nFor merge-worthy recommendations, use one repository-readiness status when useful:\n\n- **Ready**\n- **CI or review pending**\n- **Rebase or conflict resolution required**\n- **Blocked**\n\nOmit readiness for supersede/not-worth-completing recommendations; CI does not change those code decisions. Do not downgrade sound code only because CI is pending, and do not call a PR ready when semantic changes remain (including a missing sync/async mirror or untested protocol/topology).\n\nFor competing PRs, make one portfolio recommendation: choose one, choose one after focused changes, combine exact pieces into one destination, replace all with a simpler approach, or merge none. State what should happen to every active candidate.\n\nAlways compare the proposed patch with the strongest existing supported approach and at least one alternative: no code change, validation or documentation, a narrower fix, reuse of an existing helper, or a different layer that enforces the invariant consistently. A review is incomplete if it establishes only that the patch works without establishing why the current product cannot meet the underlying need or violates a supported contract, and why this design is preferable.\n\n### 7. Report the decision and action\n\nChoose the assessment language from the current user request and governing repository instructions. Per the repository owner's standing rules, deliver review findings in chat, lead with findings ordered by severity with file/line references, and do not post comments, approvals, or change requests to GitHub or any external service. Maintainer comment drafts are produced in chat as copy-paste-ready text, in English, for the user to send themselves.\n\nUse the matching compact report in the evaluation framework. While runtime approval is pending, use its preliminary-assessment variant and end with the approval request instead of presenting a final recommendation. Keep the report decision-oriented, put unexpected/negative evidence first, and use no more than five evidence bullets by default.\n\nFor PRs, put `Need evidence` before the code recommendation. When the need is not `Demonstrated`, lead with that result, omit repository readiness, and avoid presenting patch fixes as the primary maintainer action.\n\nWhen existing functionality or a better alternative materially affects the decision, state it explicitly in the evidence and recommendation. Name the exact supported path, what it does and does not cover, and why it is preferable. Do not bury a `Not worth completing` or `Supersede with a simpler alternative` conclusion beneath praise for implementation quality.\n\nWhen recommending closure, more evidence, focused changes, or superseding a PR, append a polite, complete, copy-paste-ready English maintainer comment. Include only merge-blocking work in its required-action paragraph. Do not produce a line-by-line review unless requested, equate passing tests with merge-worthiness, or equate a logically correct patch with practical value.\n\n## Resource\n\n- `references/evaluation-framework.md` contains the severity rubric, evidence checks, lifecycle review, issue dispositions, PR value checks, documentation threshold, competing-PR framework, maintainer-comment guidance, and compact report variants.\n",".agents/skills/maintainer-review/references/evaluation-framework.md":"# Maintainer Evaluation Framework\n\nUse this reference when a claim is ambiguous, severity is disputed, or a technically correct PR may not justify permanent maintenance.\n\n## Contents\n\n- [Decision model](#decision-model)\n- [Severity](#severity)\n- [Evidence strength](#evidence-strength)\n- [Unmet need and alternative design gate](#unmet-need-and-alternative-design-gate)\n- [Issue disposition](#issue-disposition)\n- [PR quality and value](#pr-quality-and-value)\n- [Documentation threshold](#documentation-threshold)\n- [Lifecycle and failure paths](#lifecycle-and-failure-paths)\n- [Concurrency and cleanup ownership](#concurrency-and-cleanup-ownership)\n- [Better-alternative prompts](#better-alternative-prompts)\n- [Competing pull requests](#competing-pull-requests)\n- [Maintainer comments](#maintainer-comments)\n- [Compact report variants](#compact-report-variants)\n\n## Decision Model\n\nTreat validity, severity, and merge-worthiness as separate outputs. Also distinguish a `Preliminary assessment`, which may still require approved runtime evidence, from a final `Maintainer decision`. Do not label a provisional positive result as a verdict or final decision.\n\n| Dimension | Question | Strong evidence |\n| --- | --- | --- |\n| Claim validity | Does the exact behavior occur, and is the proposed cause correct? | Reproduction, failing focused test, or complete reachable path |\n| Reachability | Can supported realistic inputs reach it? | Public API trace, real configuration, user report, or release comparison |\n| Consequence | What fails, and is it silent or recoverable? | Observed output/error/state and downstream effect |\n| Breadth | Which stacks (sync/async), protocols (RESP2/RESP3, `legacy_responses`), topologies (standalone/cluster/sentinel), parsers (pure-Python/hiredis), and versions are affected? | Explicit path and compatibility matrix |\n| Frequency | Is it normal, intermittent, or pathological? | Repeats, deterministic preconditions, reports, or telemetry |\n| Need evidence | Is the exact scope demonstrated, merely plausible, already covered, or unsupported? | Same-scope user scenario, real-path reproduction, released compatibility requirement, violated supported contract, repeated demand, or broad consequential invariant |\n| Unmet need | What user outcome cannot be achieved through supported behavior today, or what supported contract is violated? | Concrete scenario plus a trace showing why the closest existing path is insufficient or defective |\n| Existing capability | Can configuration, composition, client/pool options, callbacks, `response_callbacks`, retry strategies, an existing command mixin, or a caller-owned layer already satisfy the outcome? | Current release code, tests, docs, and an exact supported workflow |\n| Compatibility | Is released public API, signature, default, return type, error/exception type, wire-protocol handling, or connection/config behavior changed? | Latest released (PyPI) comparison and contract inspection |\n| Solution fit | Is the requested mechanism the best design and implementation layer? | Proposed solution compared with the strongest existing path and at least one narrower or more coherent alternative |\n| Resource ownership | Can stale, failed, cancelled, or overlapping work mutate or clean up resources owned by surviving work (pool connections, locks, slot maps, pub/sub state)? | Interleaving trace, attempt or generation ownership, and survivor assertions |\n| Maintenance cost | What permanent complexity and review burden is added? | New branches/configuration, changed surface, tests across stacks/protocols, and remaining work |\n\n## Severity\n\n- **Negligible**: no runtime difference, unreachable/unsupported input, cosmetic inconsistency, or harmless edge case. Usually close, document, or decline code complexity.\n- **Low**: real but narrow and recoverable behavior with a simple workaround and no data, security, or compatibility risk. Merge only when the fix is small and strengthens an invariant.\n- **Moderate**: supported use fails or produces incorrect behavior for a meaningful subset (one stack, one protocol, one topology). Prioritize a bounded fix and regression test.\n- **High**: common or important use is broken, released compatibility is seriously affected, sensitive data can leak, connections/pools leak or corrupt, or persistent corruption is possible. Require urgent strong validation.\n- **Critical**: broadly exploitable security impact, severe data loss, or systemic failure requiring coordinated action. Use only with concrete evidence.\n\nSeverity is consequence multiplied by realistic reach and frequency, reduced by recoverability. Do not raise it because prose is alarming or lower it because a diff is small.\n\n## Evidence Strength\n\nBefore calling a claim confirmed, answer:\n\n- Does the reproduction exercise the same public/internal path, stack (sync/async), protocol, topology, and parser?\n- Does failure occur on the relevant base, latest release, or target?\n- Does the regression test fail without the patch and pass with it?\n- Are stale build/`dist`, wrong virtualenv/imports, dependency drift (including hiredis presence/version), proxies, caches, runtime conditions, unavailable Docker/sandbox, authentication, quota, and Redis-server-version differences excluded?\n- Does an equivalent sync/async, RESP2/RESP3, `legacy_responses`, standalone/cluster/sentinel, or hiredis/pure-Python path differ?\n- Is behavior prohibited by a real contract (public API, documented Redis command semantics, error/exception type, wire protocol) or merely surprising?\n- For latency, timeout, buffering, backpressure, or cleanup, was observable time or state measured rather than inferred only from mocks?\n- For shared asynchronous state, do tests control completion order and prove that a stale failure or cleanup cannot affect the surviving operation?\n\nUse `partially confirmed` when the symptom is real but cause/reach/scope is wrong. Use `unproven` when decisive evidence is missing. Use `contradicted` only when evidence directly disproves the claim.\n\n## Unmet Need and Alternative Design Gate\n\nIssue reports often combine a desired outcome with a proposed API or implementation. Treat the proposed mechanism as a hypothesis. Confirm the unmet outcome or violated supported contract before evaluating how well the patch implements that mechanism.\n\n### Linked-evidence scope\n\nEvidence from a linked issue applies only when the issue and PR share the same runtime variant — stack (sync/async), protocol, `legacy_responses` setting, topology, command, parser — trigger, supported configuration, and user outcome. A broad title, ordinary reference, `Related to` statement, or conceptual similarity is not enough. If an earlier change already resolved the concrete reported scenario, an adjacent variant (the other stack, another protocol, another topology) starts with no inherited evidence of need.\n\n### Need evidence status\n\nAssign one status before deep implementation review:\n\n- **Demonstrated**: The exact scope has a concrete supported scenario, real-path reproduction, released compatibility requirement, violated supported contract, repeated demand, or broad invariant with meaningful consequence.\n- **Plausible but unproven**: The code path is possible, but realistic reach, frequency, consequence, Redis-server behavior, or demand is missing.\n- **Already covered**: A reasonable supported workflow already satisfies the outcome.\n- **Unsupported**: The outcome is outside redis-py's contract or belongs at the Redis server, a caller-owned, or an application layer.\n\nOnly `Demonstrated` need can support a merge-worthy code recommendation. `Plausible but unproven` maps to `Needs evidence` or `Not worth completing`, even when the patch is technically correct and its remaining fixes are bounded. `Already covered` and `Unsupported` normally map to closure or a simpler non-core alternative.\n\nBefore accepting an issue or recommending a PR, record:\n\n| Question | Required evidence |\n| --- | --- |\n| What outcome is needed? | A concrete supported scenario stated without the proposed API or fix |\n| What exists today? | The closest current-release API, configuration, composition, extension point, or caller-owned solution |\n| Why is it insufficient? | An exact behavioral, compatibility, lifecycle, or operational constraint, not preference alone |\n| What are the alternatives? | The proposed patch, the strongest existing path, and at least one no-code, narrower, or better-layer design |\n| Why add a contract? | Practical benefit sufficient to justify public surface, runtime branches, cross-stack/protocol tests, documentation, and long-term maintenance |\n\nClassify the result:\n\n- **Capability gap**: a supported, realistic outcome cannot be achieved with current functionality. Code may be warranted.\n- **Ergonomics or discoverability gap**: the outcome is already possible, but the supported route is confusing or unnecessarily difficult. Prefer documentation, validation, or a narrowly justified convenience improvement.\n- **Unsupported use case**: the desired outcome lies outside redis-py's contract or belongs at the Redis server, application, or other caller-owned layer. Do not expand the core API merely to make it possible.\n- **No demonstrated gap**: no concrete scenario proves that existing functionality is insufficient. Request evidence or close rather than designing from the proposed mechanism.\n- **Defect in supported behavior**: the existing path violates a documented or established correctness, security, compatibility, or lifecycle contract (including Redis command semantics, error/exception types, and sync/async parity). A workaround may affect priority or solution shape, but does not erase the defect.\n\nPassing tests for a new implementation establish feasibility and correctness, not need. A mock connection, `FakeRedis`/monkeypatched response, manually constructed RESP payload, mock, or synthetic fixture does not establish realistic Redis-server behavior, user reach, frequency, consequence, or demand. API symmetry and parity with an adjacent command, protocol, topology, or the other stack are design arguments, not need evidence. A technically coherent patch can still be `Not worth completing` when the motivating scenario is hypothetical, already supported, or better solved elsewhere.\n\nUse the counterfactual maintainer test: if the PR did not already exist, would maintainers choose to file and implement the same work from the available evidence? Contributor effort lowers implementation cost but does not create product need or remove permanent maintenance cost.\n\nWhen the need is not `Demonstrated`, inspect implementation only far enough to estimate contract, risk, and maintenance cost. Do not convert patch defects, missing tests, or documentation gaps into a request-changes disposition; those become merge blockers only after the need gate passes.\n\n## Issue Disposition\n\nChoose one:\n\n- **Prioritize**: confirmed moderate-or-higher impact or important invariant with no safe workaround.\n- **Accept, low priority**: confirmed low impact, existing supported functionality is insufficient or defective for the demonstrated scenario, and a proportionate fix is plausible.\n- **Narrow scope**: valid core, overstated paths or expected behavior.\n- **Needs evidence**: plausible but missing a supported reproduction, contract basis, or concrete scenario showing why existing functionality is insufficient.\n- **Close**: duplicate, unsupported, unreachable, contradicted, no-op, already addressed by a reasonable supported path, or not worth permanent complexity.\n\nAsk only for evidence that could change the disposition.\n\n## PR Quality and Value\n\nAssess independently:\n\n1. **Need**: same-scope evidence demonstrates a concrete unmet user outcome or defect in supported behavior, and the closest supported capability cannot reasonably satisfy the scenario as-is. Do not inherit evidence from an adjacent variant (other stack, protocol, topology) or already-fixed scenario.\n2. **Correctness**: the fix covers the claim and meaningful boundaries, and preserves Redis command semantics and error/exception types.\n3. **Placement**: the invariant is enforced once at the owning layer (connection, pool, parser, callback, command mixin) instead of duplicating existing functionality, patching locally, or moving server- or application-owned policy into the client.\n4. **Consistency**: equivalent sync/async, RESP2/RESP3, `legacy_responses`, standalone/cluster/sentinel, parser, and serialization paths stay aligned. Sync/async parity is a repository requirement, not optional polish.\n5. **Tests**: a regression test fails on base, passes on head, and asserts the non-happy-path value/state; protocol-sensitive changes run under both `--protocol 2` and `--protocol 3`. When shared state crosses an asynchronous boundary, tests control relevant completion orders and assert the surviving operation's behavior and final resource state.\n6. **Compatibility**: released public API (signatures, argument names, defaults, return types), error/exception types, wire-protocol handling, packaging extras, and connection/config behavior are preserved or intentionally migrated against the latest release.\n7. **Proportionality**: public surface and complexity match impact.\n8. **Completion cost**: remaining code, tests, docs, design, the missing sync or async mirror, and conflict work is bounded enough to justify attention.\n\nA PR can be correct but not merge-worthy because the need is negligible, the outcome is already supported through a reasonable existing mechanism, the real path is unchanged, equivalent paths remain inconsistent, the abstraction costs more than the benefit, or a simpler design exists at another layer.\n\nDo not use implementation correctness, bounded remaining work, CI status, or contributor effort to upgrade a need that is only `Plausible but unproven`. Merge-worthiness is gated by demonstrated need, not by how close the patch is to completion.\n\nKeep issue severity separate from `Patch risk`. A patch-induced regression, compatibility break, connection/resource leak, lock-release-across-await hazard, or maintenance hazard does not make the underlying issue more severe.\n\n## Documentation Threshold\n\nMake docs merge-blocking only when:\n\n- Existing docs or docstrings become materially false, unsafe, or misleading.\n- Safe/correct use depends on a non-obvious constraint, migration, compatibility boundary, or operational warning.\n- Repository policy, accepted scope, or an explicit maintainer decision requires docs in the same PR.\n- The feature is practically unusable or undiscoverable without a user-facing entrypoint and generated/API discovery is insufficient.\n\nKeep optional discoverability/completeness non-blocking. Do not downgrade a code recommendation solely for optional docs or include optional docs in a required-action paragraph.\n\n## Lifecycle and Failure Paths\n\nApply this section when a change adds validation, fail-fast behavior, cleanup, retry, interruption, background work, connection handling, or concurrency.\n\n- Identify the earliest point where all dynamic inputs required for a correct decision exist.\n- List side effects before and after that point: pool connections, sockets, coroutines/tasks, locks, caches, state, persistence, and telemetry/observability spans.\n- Exercise failure during construction, connection, validation, command execution, persistence, and teardown where those phases exist.\n- Confirm normal teardown is actually entered. If construction/connect fails, verify explicit cleanup (connection returned to or evicted from the pool, lock released, task cancelled).\n- Prefer validation after dynamic configuration is resolved but before avoidable side effects begin.\n- Require a regression test for any connection, coroutine, lock, socket, or state that can remain after failure.\n\n## Concurrency and Cleanup Ownership\n\nApply this section before a positive assessment whenever lifecycle work crosses an `await`, callback, event, deferred completion, retry, reconnect, cancellation, or shared resource boundary. Sequential correctness is insufficient because the patch can improve isolated cleanup while introducing cross-attempt teardown. In redis-py this most often involves connection-pool acquire/release, async locks held across awaits, pub/sub reconnect, cluster slot-map updates, and multidb failover.\n\nUse a two-operation interleaving matrix during desk review:\n\n| Ordering | Required question |\n| --- | --- |\n| `A pending -> B starts -> A fails -> B succeeds` | Can A's cleanup remove or revert anything B needs (a pool connection, a slot map, a lock)? |\n| `A pending -> B starts -> B fails -> A succeeds` | Can B's cleanup leave A successful but non-functional? |\n| `A succeeds -> B starts -> stale A completion` | Can stale A overwrite B's newer state or generation (slot map, reconnect, config)? |\n| setup -> close/cancel -> late completion | Can late work resurrect connections, listeners, state, or tasks after teardown? |\n\nFor each ordering:\n\n- Identify the resource owner before and after every suspension point.\n- Distinguish per-attempt resources from shared pool, connection, slot-map, cache, or pub/sub state.\n- Require cleanup to carry an ownership token, generation, identity check, serialization guarantee, or another invariant that prevents cross-attempt disposal. Keep async locks held across the awaits inside a pool mutation so other tasks cannot observe in-between state.\n- Compare base and head on the survivor invariant. Fewer duplicates do not justify losing the only active connection, task, or state update.\n- Require a controlled interleaving test when the ordering is reachable. The test must assert both the failing operation and the surviving operation's observable behavior after all completions settle.\n\nAn unscoped `finally`, `except`, close handler, cancellation callback, or rollback that mutates shared state after a suspension point is merge-blocking when another operation can still own or use that state.\n\n## Better-Alternative Prompts\n\nStart with the strongest existing supported path, then test at least one additional alternative against the proposed patch. Do not complete a positive review without this comparison.\n\n- Can the requested outcome already be achieved through configuration, composition, client/pool options, callbacks, `response_callbacks`, a retry/backoff strategy, an existing command mixin, or caller-owned code?\n- If the existing route is awkward, is the problem discoverability or ergonomics rather than missing capability?\n- What happens with no code change?\n- Can input validation or an existing helper (`redis/commands/helpers.py`) enforce the invariant earlier?\n- Can the fix be limited to the supported failing path?\n- Would a clearer error/exception or documentation prevent misuse without runtime complexity?\n- Can a failing test reveal a smaller correct change?\n- Is a new public option compensating for an internal ownership problem (pool, lock, connection lifecycle)?\n- Can the same result be achieved in the parser, response callback, or state owner instead of every caller?\n- Is the proposed core behavior actually Redis-server- or application-specific policy that belongs at another layer?\n\n## Competing Pull Requests\n\nRequire an explicit issue link, same reproduction, same violated invariant, or materially overlapping runtime path (command, stack, protocol, topology) before grouping candidates.\n\n| Criterion | Question |\n| --- | --- |\n| Need | Does a concrete user outcome remain unmet, or supported behavior remain defective, after tracing existing functionality? |\n| Existing capability | Could every candidate be avoided by configuration, composition, an extension point, or a better caller-owned solution? |\n| Coverage | Whole confirmed issue, useful subset, or adjacent problem? Both stacks or only one? |\n| Correctness | Real path and meaningful boundaries? |\n| Placement | Owning shared layer? |\n| Tests | Base failure reproduced, protocols/topologies covered, and approaches distinguished? |\n| Compatibility | Released APIs, signatures, error types, protocols, packaging, connection/config? |\n| Complexity | Permanent branches, abstractions, configuration, coupling? |\n| Readiness | Mergeable now or bounded focused work (including missing sync/async mirror)? |\n| Reuse | Exact tests or ideas worth transferring? |\n\nChoose one portfolio action:\n\n- **Prefer one PR**\n- **Prefer one after focused changes**\n- **Combine selectively** into a named destination PR\n- **Replace all** with a simpler/coherent implementation\n- **Merge none**\n\nDo not issue independent approvals for overlapping candidates. State the action for every active PR.\n\n## Maintainer Comments\n\nWrite drafts in English, in chat, as copy-paste-ready text for the user to send themselves. Per the repository owner's standing rules, do not post them to GitHub or any external service, and do not impersonate the user. Produce one when recommending closure, more evidence, focused changes, superseding, or choosing among competing PRs.\n\nKeep it polite, direct, complete, and usually 60-160 words in one to three short paragraphs:\n\n1. Acknowledge the contribution/report.\n2. State the decision with decisive technical evidence.\n3. Give the exact next action or reconsideration condition.\n\nDo not include internal severity labels, speculate about authorship/intent, repeat the full review, or soften the requested action until it is unclear.\n\n### Close\n\n```text\nThanks for taking the time to investigate this. I traced the reported case through <path or behavior>, and <decisive finding>. In the supported path, <practical result>, so the added complexity is not justified by the demonstrated impact.\n\nI am going to close this <issue/PR>. If you can provide <specific reproduction or evidence that would change the decision>, we can revisit the underlying problem with that narrower scope.\n```\n\n### Request Changes\n\n```text\nThanks for the contribution. The underlying issue is valid, and this approach is directionally reasonable. Before we can merge it, please address the following points: <bounded required changes, e.g., mirror the change in the async stack under redis/asyncio/, add RESP2/RESP3 coverage, add a regression test>.\n\nThese changes are needed because <contract, lifecycle, compatibility, sync/async parity, or test reason>. Once they are covered with a regression test that fails on the base and passes on the updated branch, the PR should be ready for another review.\n```\n\nAdapt these templates to evidence. Do not use them as filler.\n\n### Existing Capability or Better Alternative\n\n```text\nThanks for the contribution. I traced the underlying use case through <existing API or workflow>, which already supports <desired outcome and relevant limits>. The proposed change adds <new contract or complexity>, but the issue does not demonstrate a concrete supported case that the existing approach cannot handle.\n\nI am going to close this <issue/PR> for now. If you can provide <specific scenario showing the existing approach is insufficient>, we can revisit the unmet need and choose the narrowest appropriate design from that evidence.\n```\n\n## Compact Report Variants\n\nUse `Maintainer decision` for a concluded review. Use `Preliminary assessment` when a desk review is tentatively positive but a decision-relevant runtime concern remains. `Verdict` is intentionally avoided in the report headings because it does not communicate whether the result is provisional or final.\n\n### Runtime Approval Gate\n\n```markdown\n## Preliminary assessment\n\n<Tentative issue or PR assessment based on desk review only.>\n\n## Static evidence\n\n- <decisive code-path or test-inspection evidence>\n- <what remains uncertain at runtime>\n\n## Proposed runtime probe\n\n- Concern: <the uncertainty that could change the decision>\n- Probe: <smallest exact execution path; note protocol/topology and whether `invoke devenv` or external infra is required>\n- Control: <base, release, or known-good comparison when relevant>\n- Scope: <local-only or any live-service, Docker, cost, mutation, or cleanup implications>\n\n## Approval request\n\n<Ask whether to run this exact probe. Do not present a final positive recommendation yet.>\n```\n\n### Issue\n\n```markdown\n## Maintainer decision\n\n<Real/partial/unproven/contradicted, severity, and disposition.>\n\n## Evidence\n\n- <decisive evidence>\n- <scope or uncertainty (which stacks/protocols/topologies)>\n\n## Existing capability and alternatives\n\n<Closest supported path, why it is or is not sufficient, and the preferred design alternative.>\n\n## Recommendation\n\n<Prioritize, accept low priority, narrow, request evidence, or close.>\n\n## Maintainer comment draft\n\n<Include for closure or an evidence request.>\n```\n\n### Pull Request\n\n```markdown\n## Maintainer decision\n\n<Need, practical impact, and merge-worthiness.>\n\n- Need evidence: <Demonstrated / Plausible but unproven / Already covered / Unsupported>\n- Code recommendation: <code disposition>\n- Repository readiness: <one allowed status; only when useful for a merge-worthy recommendation>\n\n## Evidence\n\n- <runtime/code-path result>\n- <test/compatibility result>\n\n## Existing capability and alternatives\n\n<Closest supported path, why the demonstrated scenario cannot use it or remains defective, and why this patch is preferable to no code change or a narrower design.>\n\n## Issue impact\n\n- Validity: <claim validity>\n- Severity: <underlying issue severity>\n- Reach: <realistic reach: stacks, protocols, topologies, versions>\n\n## Patch risk\n\n<Only meaningful patch-induced risk, including sync/async parity and resource-ownership hazards.>\n\n## PR quality\n\n- Solution fit: <assessment>\n- Tests: <assessment, including protocol/topology coverage>\n- Remaining effort: <bounded/unbounded and why; note any missing sync or async mirror>\n\n## Recommendation\n\n<Merge, focused changes, simpler replacement, or close.>\n\n## Maintainer comment draft\n\n<Only when closure, evidence, or changes should be requested.>\n```\n\n### Competing Pull Requests\n\n```markdown\n## Maintainer decision\n\n<Issue validity, severity, and preferred implementation path.>\n\n## Open PR comparison\n\n| PR   | Approach | Correctness | Tests | Compatibility/complexity | Readiness |\n| ---- | -------- | ----------- | ----- | ------------------------ | --------- |\n| #... | ...      | ...         | ...   | ...                      | ...       |\n\n## Recommendation\n\n<Select one, request focused changes, combine exact pieces, replace all, or merge none.> <State the action for every other active candidate.>\n\n## Maintainer comment drafts\n\n<One draft for each PR that should be closed, changed, or superseded.>\n```\n",".agents/skills/pr-draft-summary/SKILL.md":"---\nname: pr-draft-summary\ndescription: Produce PR-ready output for redis-py — a one-line commit message and a copy-pasteable Markdown PR description (Change summary + Test coverage, 5–6 paragraphs) covering all staged changes plus the commits already on the current branch. Trigger whenever the user's message mentions any of these keywords or phrases: \"PR\", \"pr\", \"PR description\", \"PR draft\", \"pull request\", \"the PR block\", \"commit msg\", \"commit message\", or otherwise asks for a commit message or PR write-up for their local changes.\n---\n\n# PR Draft Summary\n\n## Purpose\n\nAfter eligible work is complete, produce exactly two things for redis-py:\n\n1. A **one-line commit message** (imperative, conventional prefix preferred).\n2. A **PR description** as a single Markdown block the user can copy-paste into\n   the GitHub PR UI with all formatting preserved. Keep it to **5–6 short\n   paragraphs** that explain what the changes do.\n\nBy default, emit both as text in the chat. Write them to a `.md` file **only**\nif the user explicitly asks for a file.\n\n## Scope of Changes to Evaluate\n\nBy default, cover **staged changes** and **commits already on the current\nbranch** (relative to the PR base) — that is what the commit/PR will actually\ncontain. Include **unstaged changes** and **untracked files** only when the user\nexplicitly asks for them; note that untracked files do not appear in\n`git diff --stat`, so pick them up from `git status -sb` (the `??` entries). Use\ncommit messages as supporting context, not as a substitute for reading the\ndiffs. If the in-scope set is empty, reply briefly that no changes were detected\nand skip emitting the blocks.\n\n## Inputs to Collect Automatically (do not ask the user)\n\n- Working tree overview — `git status -sb` (one shot: branch name, ahead/behind,\n  and staged/unstaged/untracked entries, with `??` marking untracked files).\n- What changed, with line counts — `git diff --stat --cached` for staged\n  changes (stats include file names, so a separate `--name-only` is not needed);\n  read the staged diff to write the summary. Run `git diff --stat` (unstaged)\n  only when the user asks to include unstaged/untracked work.\n- PR base — the branch this change will merge into. Accept it as an input: use\n  the base branch the user names (which may be a feature/integration branch, not\n  `master`). When none is given, default to `origin/master` (fall back to\n  `master`). Then set `BASE_REF` to that branch and\n  `BASE_COMMIT=$(git merge-base \"$BASE_REF\" HEAD)`.\n- Branch commits vs the base — `git diff --stat \"${BASE_COMMIT}..HEAD\"` and\n  `git log --oneline --no-merges \"${BASE_COMMIT}..HEAD\"`.\n\nClassify the change from the touched paths: runtime (`redis/`,\n`redis/asyncio/`), tests (`tests/`, `tests/test_asyncio/`), docs/examples\n(`docs/`, `doctests/`, `benchmarks/`), build/test config (`pyproject.toml`,\n`tasks.py`, `dockers/`, …), or repo-meta (`AGENTS.md`, `CLAUDE.md`, `.agents/`,\n`.github/`).\n\n## Workflow\n\n1. Determine the PR base first: use the base branch the user provided; otherwise\n   default to `origin/master` (fall back to `master`). Compute\n   `BASE_REF`/`BASE_COMMIT`, then run the commands above. The base may be a\n   feature or integration branch when the change targets one rather than\n   `master`.\n2. Build the in-scope diff: committed branch changes + staged changes by\n   default; add unstaged and untracked changes only when the user explicitly\n   asks. Use commit messages as supporting context, not as a substitute for\n   inspecting the diff itself.\n3. Infer change type from touched paths (feature, fix, refactor, perf, or\n   docs-with-impact). Pick the commit-message prefix accordingly: `feat:`,\n   `fix:`, `refactor:`, `perf:`, `docs:`, `test:`, `build:`, `chore:`.\n4. Flag **backward-compatibility risk** in the description only when the diff\n   changes the public API (signatures, argument names, defaults, return types),\n   command semantics, error/exception types, wire-protocol (RESP2/RESP3)\n   handling, or persisted/connection config. Judge that risk against the latest\n   released version, not unreleased branch-only churn.\n5. Flag **sync/async parity** explicitly: if the change touches sync code under\n   `redis/` without a mirrored change under `redis/asyncio/` (or vice versa, and\n   likewise `tests/` vs `tests/test_asyncio/`), call it out, since this repo\n   requires both stacks to stay aligned.\n6. If the branch name matches `issue-<number>` (digits only) or an issue number\n   is otherwise known, reference\n   `https://github.com/redis/redis-py/issues/<number>` and add an auto-closing\n   line such as `Resolves #<number>.` Do not block if the issue cannot be fetched.\n7. Draft the two blocks using the format below. The PR description always has a\n   \"Change summary\" and a \"Test coverage\" section. Keep it to 5–6 short\n   paragraphs total; do not pad. In \"Test coverage\", describe the tests added or\n   updated; if none were added, say so briefly and explain why.\n8. Output both blocks as chat text by default. If — and only if — the user\n   explicitly requested a file, also write them to a `.md` file and report its\n   path.\n\n## Output Format\n\nEmit the commit message first, then the PR description. The description must be\na single fenced Markdown block so it can be copied into the GitHub UI with\nformatting intact.\n\n### Commit message\n\n```\n<single-line imperative commit message; conventional prefix preferred, e.g. fix: release async pool lock only after pool mutation>\n```\n\n### PR description\n\n````\n```markdown\n## Change summary\n\n<3–4 paragraphs of prose, starting with \"This pull request resolves/updates/adds ...\"\nusing a verb that matches the change. Explain what the change does and the\nbackground — for bugs, the symptom/repro; for features, what is needed and why —\nthen the concrete changes: the main files/areas touched, the approach, and notable\nedge cases. Call out public-API or backward-compatibility impact and any sync/async\nparity considerations here. Bullets are allowed.>\n\n## Test coverage\n\n<1–2 paragraphs (bullets allowed) describing the tests added or updated and what\nbehavior they exercise (sync and async, edge/failure cases). If the change is not\ntestable or no tests were added, say so briefly and explain why.>\n\nResolves #<number>.   <!-- include only when an issue number is known -->\n```\n````\n\nKeep it tight — 5–6 paragraphs total across both sections, no redundant prose\naround the blocks, and no repeating the same detail in both sections.\n",".agents/skills/prepare-for-takeover/SKILL.md":"---\nname: prepare-for-takeover\ndescription: Prepare copy-paste-ready maintainer comments that mark stalled but valuable redis-py PRs as `open-for-takeover`, inviting the original author or any other contributor to finish the work. Each comment summarizes what is left based on the maintainer's earlier review. Use when the user says \"prepare for takeover\", \"open for takeover\", \"mark as abandoned\", \"invite contributors to finish\", or gives one or more PR numbers/URLs that stalled and should be handed over.\n---\n\n# Prepare for Takeover\n\n## Purpose\n\nSome PRs are worth finishing, but the author has gone quiet and the maintainer has no time to complete them. This skill drafts one comment per PR that:\n\n1. Says the PR seems to have stalled but is still valuable for redis-py.\n2. Announces the `open-for-takeover` label.\n3. Lists what is left, taken from the maintainer's earlier review.\n4. Invites the original author to continue, or anyone else to take over while keeping the author's credit.\n\nFor now the skill only generates the comment texts. It does not apply labels, post comments, or edit workflows.\n\n## Hard rule: no GitHub writes\n\nUse read-only GitHub access only (`gh pr view`, `gh api` GET). Never comment, label, review, or edit anything on GitHub. The user posts the comments and applies the labels manually. `gh` may fail with a TLS error inside the command sandbox; run it with the sandbox disabled when needed.\n\n## Inputs\n\n- One or more PR numbers or URLs. Default repo: `redis/redis-py`. Invoking the skill on a PR makes it eligible; do not require any label (such as `waiting-for-response`), a minimum inactivity period, or other staleness signals.\n- The maintainer's GitHub login: resolve it with `gh api user --jq .login`. Do not guess.\n\n## Data to collect per PR (read-only)\n\n```\ngh pr view <n> -R <owner/repo> --json title,author,state,isDraft,labels,updatedAt,body,reviews,comments\ngh api graphql --paginate -F owner=<owner> -F repo=<repo> -F n=<n> -f query='\n  query($owner: String!, $repo: String!, $n: Int!, $endCursor: String) {\n    repository(owner: $owner, name: $repo) {\n      pullRequest(number: $n) {\n        reviewThreads(first: 100, after: $endCursor) {\n          pageInfo { hasNextPage endCursor }\n          nodes {\n            isResolved isOutdated path line subjectType\n            comments(first: 100) {\n              nodes { author { login } body createdAt pullRequestReview { state } }\n            }\n          }\n        }\n      }\n    }\n  }'\n```\n\nThe second command returns the inline review threads, which `gh pr view` does not include. A `CHANGES_REQUESTED` review often has an empty or short body with the details inline. Per thread:\n\n- `isResolved: true` means the thread was settled (often the author resolved it after the fix, with no reply); treat it as done unless a later maintainer comment reopens it.\n- `isOutdated: true` means the code moved under the comment; check whether it still applies.\n- `subjectType: FILE` is a file-level comment. Its `line` is null, but it is still current.\n\nIgnore pending (unsubmitted) feedback: reviews with `state: PENDING` and thread comments whose `pullRequestReview.state` is `PENDING`. They are drafts visible only to the maintainer, so they must not appear in a public comment or count as an earlier review.\n\nFrom the output, get:\n\n- `author.login`: the PR author.\n- Whether the maintainer has already commented or reviewed (a comment, submitted review or inline comment by the maintainer's login).\n- All maintainer feedback in chronological order: review bodies, inline comments and conversation comments. Read it in full; it is the source for \"What's left\". Drop items in resolved threads, items that a later maintainer comment withdrew or marked as done, and items that a later commit or reply clearly addressed. An empty review body does not mean there is no feedback.\n- Current labels (to point out `waiting-for-response` for removal) and `isDraft`.\n\nIf the PR is closed or merged, skip it and say so. If the maintainer left no feedback in any of these sources, write \"What's left\" from the open TODOs in the PR `body` or ask the user for a line, and do not invent requirements.\n\n## Writing \"What's left\"\n\n- 1 to 5 short bullets, each one concrete action, in the order the maintainer raised them.\n- Paraphrase the review. Do not add new requests that the maintainer did not make.\n- Keep scope notes from the review (for example \"change Fixes #N to Refs #N\", \"keep #N open, retitle the PR\").\n- Add \"Take the PR out of draft.\" when `isDraft` is true and the review asked for it.\n- Put file and symbol names in single backticks.\n\n## Comment template\n\nInclude the greeting line **only** if the maintainer has not commented on or reviewed this PR before. Otherwise start with `@<author>, this PR seems to have stalled...`.\n\n```markdown\nHey @<author>, thank you for your contribution!\n\nThis PR seems to have stalled, but the change is still valuable for redis-py and I would like to see it land. I don't have the bandwidth to finish it myself right now, so I'm marking it as `open-for-takeover`.\n\nWhat's left (details in my review above):\n- <action>\n- <action>\n\n@<author>, if you want to continue, you're very welcome to, just let me know here. Otherwise, anyone interested can take it over: leave a comment first so two people don't work on it at the same time, then open a new PR that builds on this branch (keep the original commits so @<author> keeps the credit) and links back here.\n\nAny help is very welcome!\n```\n\nWhen there is no earlier maintainer review or inline review comment, use `What's left:` without \"(details in my review above)\".\n\n## Writing rules\n\n- Use \"I\", never \"we/us/our\". The maintainer is one person.\n- Never use em dashes, en dashes, or `--` as punctuation; use a single hyphen.\n- No code fences inside the comment (it breaks the outer fence). Use single backticks only.\n- Keep it short; do not restate the full review.\n\n## Output format\n\nStart with one line naming the audience, for example `Written for: the PR authors and other contributors on GitHub.`\n\nThen for each PR, a bold heading with the number and author, followed by the comment in its own fenced ```markdown block:\n\n**#<n>** (@<author>)\n\n```markdown\n<comment>\n```\n\nAfter the blocks, add at most two short notes:\n\n- PRs that still carry `waiting-for-response` (remove it when adding `open-for-takeover`).\n- PRs that were skipped (closed or merged).\n\n## Label reference\n\nIf the user asks for the label details:\n\n- Name: `open-for-takeover`\n- Description: `Stalled but valuable work - anyone is welcome to pick it up and finish it`\n- Color: `0e8a16`\n- Stale bot: add `open-for-takeover` to `exempt-pr-labels` in `.github/workflows/stale-issues.yml`.\n",".claude/commands/add-new-command.md":"---\ndescription: Adds support for a new Redis command from a given specification. Check command-specification-template.md.\nargument-hint: [path-to-specification]\n---\n\nThe full instructions for this command live in\n`.agents/commands/add-new-command/add-new-command.md` (relative to the redis-py repo\nroot), with the spec template at\n`.agents/commands/add-new-command/command-specification-template.md`.\n\n**Read `.agents/commands/add-new-command/add-new-command.md` in full now and follow it\nexactly.**\n\nThe specification file to use is: `$ARGUMENTS`\n",".claude/commands/sync-claude-md.md":"---\ndescription: Audit AGENTS.md (the canonical agent guide; CLAUDE.md is only an `@AGENTS.md` include) against the current state of the repo and update it directly. Run when wrapping up a feature or after significant structural changes.\n---\n\nThe full instructions for this command live in\n`.agents/commands/sync-claude-md/sync-claude-md.md` (relative to the redis-py repo root).\n\n**Read `.agents/commands/sync-claude-md/sync-claude-md.md` in full now and follow it\nexactly.**\n",".claude/skills/generate-release-notes/SKILL.md":"---\nname: generate-release-notes\ndescription: Generate a redis-py GitHub release-notes draft and save it as a Markdown file. Collects the merged PRs/commits since the previous release, maps each commit to its PR labels, categorizes them into the project's release sections (Breaking Changes, Deprecations, Experimental Features, New Features, Bug Fixes, Maintenance) using the skill's pr-labels-guide, infers a category for unlabeled PRs, and assembles the notes with an optional Highlights section and a contributors footer. Trigger this skill whenever the user says \"release notes\", \"create release notes\", \"generate release notes\", \"draft release notes\", \"prepare release notes\", \"changelog\", or asks for the release notes/changelog of a specific version or branch (e.g. \"release notes for 8.0\", \"generate release notes for the 8.0 branch\", \"what changed since the last release\"). A phrase like \"release notes for 8.0\" means: use branch `8.0` as the release-branch input. Also use when asked to summarize what changed since the last tag.\n---\n\nThe full instructions for this skill live in\n`.agents/skills/generate-release-notes/SKILL.md` (relative to the redis-py repo root),\nwith its helper files under `.agents/skills/generate-release-notes/references/`.\n\n**Read `.agents/skills/generate-release-notes/SKILL.md` in full now and follow it\nexactly.** Treat its contents as the body of this skill.\n",".claude/skills/maintainer-review/SKILL.md":"---\nname: maintainer-review\ndescription: Review a GitHub issue or pull request URL as a redis-py maintainer, with a staged assessment of whether the claim is real, practically important, already solvable with supported functionality, correctly scoped, better served by another design, and worth maintainer and contributor effort. Use when assessing issue validity or severity, deciding whether an issue should be prioritized or closed, determining whether a requested feature represents an unmet need rather than a discoverability or usage gap, judging whether a PR is worth bringing to mergeable quality, comparing open PRs or alternative designs, separating code quality from repository readiness, or drafting a concise maintainer assessment. When closure, additional evidence, or code changes should be requested, also produce a polite, concise, complete, copy-paste-ready maintainer comment.\n---\n\nThe full instructions for this skill live in\n`.agents/skills/maintainer-review/SKILL.md` (relative to the redis-py repo root), with\nits helper files under `.agents/skills/maintainer-review/references/`.\n\n**Read `.agents/skills/maintainer-review/SKILL.md` in full now and follow it exactly.**\nTreat its contents as the body of this skill.\n",".claude/skills/pr-draft-summary/SKILL.md":"---\nname: pr-draft-summary\ndescription: Produce PR-ready output for redis-py — a one-line commit message and a copy-pasteable Markdown PR description (Change summary + Test coverage, 5–6 paragraphs) covering all staged changes plus the commits already on the current branch. Trigger whenever the user's message mentions any of these keywords or phrases: \"PR\", \"pr\", \"PR description\", \"PR draft\", \"pull request\", \"the PR block\", \"commit msg\", \"commit message\", or otherwise asks for a commit message or PR write-up for their local changes.\n---\n\nThe full instructions for this skill live in\n`.agents/skills/pr-draft-summary/SKILL.md` (relative to the redis-py repo root).\n\n**Read `.agents/skills/pr-draft-summary/SKILL.md` in full now and follow it exactly.**\nTreat its contents as the body of this skill.\n",".claude/skills/prepare-for-takeover/SKILL.md":"---\nname: prepare-for-takeover\ndescription: Prepare copy-paste-ready maintainer comments that mark stalled but valuable redis-py PRs as `open-for-takeover`, inviting the original author or any other contributor to finish the work. Each comment summarizes what is left based on the maintainer's earlier review. Use when the user says \"prepare for takeover\", \"open for takeover\", \"mark as abandoned\", \"invite contributors to finish\", or gives one or more PR numbers/URLs that stalled and should be handed over.\n---\n\nThe full instructions for this skill live in\n`.agents/skills/prepare-for-takeover/SKILL.md` (relative to the redis-py repo root).\n\n**Read `.agents/skills/prepare-for-takeover/SKILL.md` in full now and follow it exactly.**\nTreat its contents as the body of this skill.\n","AGENTS.md":"# ✅ Instructions for AI agents contributing to redis-py\n\nThis file is the single source of guidance for AI agents working in this repository. It combines project reference material (commands, architecture, dependency notes) with the contributor process rules you must follow.\n\n## Common commands\n\nDevelopment tasks are driven by `invoke` (see `tasks.py`). Activate `.venv` first.\n\n- `invoke devenv` — start the docker-compose test environment (standalone, replica, sentinel, cluster, redis-stack, stunnel). Profiles in `docker-compose.yml` control which containers come up; `--profile all` is the default.\n- `invoke clean` — stop all dockers and remove `build/` and `dist/`.\n- `invoke tests` — run `fixed_client`, `standalone`, then `cluster` test suites in sequence. Accepts `--uvloop`, `--protocol=2|3|\"\"`, `--legacy-responses=true|false`, `--profile`.\n- `invoke standalone-tests` / `invoke cluster-tests` — run just one suite. Cluster suite targets `redis://localhost:16379/0` (`rediss://localhost:27379/0` for TLS).\n- `invoke fixed-client-tests` — runs only tests marked `fixed_client` (tests that pin client config and must not be reconfigured by the global `--protocol` / `--legacy-responses` knobs).\n- `invoke run-test-matrix` — full `protocol` × `legacy_responses` matrix; what CI does.\n- `invoke linters` / `invoke linters-fix` — `ruff check`, `ruff format`, and `vulture redis whitelist.py --min-confidence 80` (use `whitelist.py` to suppress vulture false positives).\n- `invoke all-tests` — linters + tests.\n- `invoke build-docs` — sphinx HTML under `docs/`.\n\nRunning a single test directly with pytest:\n\n```\npytest tests/test_commands.py::TestRedisCommands::test_set -x\npytest tests/test_asyncio/test_commands.py -k \"test_set and not cluster\"\npytest --redis-url=redis://localhost:16379/0 -m onlycluster tests/test_cluster.py\n```\n\nThe pytest plugin in `tests/conftest.py` adds these options (also exposed via invoke wrappers): `--redis-url`, `--redis-ssl-url`, `--redis-mod-url`, `--protocol` (`\"\"` = library default, currently RESP3), `--legacy-responses` (`true|false|default`), `--redis-cluster-nodes`, `--uvloop`. It also adds `--tracemalloc` (off by default, not exposed via invoke), which traces allocations so `ResourceWarning`s show where the object was allocated; it makes cluster client setup several times slower.\n\n### pytest markers\n\nDefined in `pyproject.toml` and used to scope test runs:\n\n- `onlycluster` / `onlynoncluster` — limit a test to one topology.\n- `redismod` — requires Redis modules (Stack image); skipped against cluster.\n- `fixed_client` — test fixes its own client/config; excluded from the protocol/legacy matrix runs.\n- `experimental`, `replica`, `ssl`, `pipeline`, `cp_integration`, `no_mock_connections`.\n\n`tests/test_scenario/` and `tests/test_asyncio/test_scenario/` are scenario tests that require external infrastructure (Redis Enterprise, EntraID, etc.); they are excluded from the default `standalone-tests` / `cluster-tests` runs via `--ignore`.\n\n## Architecture\n\n### Sync and async mirrors\n\nThe library ships two parallel client stacks that must stay in lockstep:\n\n- Sync top-level: `redis/client.py`, `redis/cluster.py`, `redis/connection.py`, `redis/sentinel.py`, `redis/lock.py`, `redis/retry.py`.\n- Async mirrors under `redis/asyncio/`: `client.py`, `cluster.py`, `connection.py`, `sentinel.py`, etc.\n\nWhen changing behavior in one stack, the equivalent change almost always belongs in the other. `specs/sync_async_deduplication_analysis.md` documents the deliberate duplication. The process rules under \"Sync / Async Consistency\" below are mandatory whenever you touch either stack.\n\n### Commands layer (mixins)\n\nCommand surface is composed via mixin classes in `redis/commands/`:\n\n- `core.py` — `CoreCommands` / `AsyncCoreCommands` (every standard Redis command).\n- `cluster.py` — `RedisClusterCommands` / `AsyncRedisClusterCommands`; also exports `READ_COMMANDS`, which is **deprecated and unread**: replica eligibility is decided by the metadata resolver's `is_replica_safe`, so editing this set changes nothing. It is kept only so external importers keep working.\n- `sentinel.py` — sentinel-specific commands.\n- `redismodules.py` — aggregates module mixins (`bf/` Bloom, `json/`, `search/`, `timeseries/`, `vectorset/`). Each module subpackage owns its own command surface and response parsing.\n- `policies.py` — `PolicyResolver` / `StaticPolicyResolver`, the routing view the cluster client resolves per command. Also holds `STATIC_POLICIES`, which is **deprecated and unread**: a frozen verbatim copy of the 7.1.0 table, kept only for external importers and deliberately not derived from or synced with `_STATIC_COMMAND_METADATA`. Change routing in `metadata.py`, never there.\n- `metadata.py` — the command metadata record types: `RequestPolicy` / `ResponsePolicy`, `CommandPolicies` / `PolicyRecords`, and `CommandMetadata` / `CommandMetadataRecordsCache`, plus the metadata resolvers. `CommandMetadata` is the superset record; `_to_command_policies` projects one down to the routing view a `PolicyResolver` serves - projecting to `None` when the record withholds its routing policies, which is how a resolver tells the cluster client to resolve the target itself (the `movablekeys` reads need this: their keys are only in the key specs, so derived policies come out keyless). The two types are kept separate deliberately: `CommandPolicies` shipped in 7.1.0 as a mutable class, and `CommandMetadata` is frozen so one record can be shared across many commands. `redis/_parsers/commands.py` re-exports the policy types from here for backwards compatibility; import them from `redis.commands.metadata`. A resolver is a client-level constructor argument (`metadata_resolver=` on `Redis`, `ConnectionPool`, `RedisCluster`, `NodesManager` and `ClusterPipeline`; on the async side only on `redis.asyncio.RedisCluster`, because the async stack has no client-side cache to be the second consumer), built once and shared with every node client; it serves cluster routing through `resolve_policies`, replica safety through `is_replica_safe`, and client-side-cache eligibility through `is_cacheable`. When only `metadata_resolver` is given the routing resolver is derived from it; an explicit `policy_resolver` still wins for the routing view - which nodes a command targets - but not for anything that view cannot express: replica safety and cache eligibility keep resolving through `metadata_resolver` either way, because a `CommandPolicies` record carries no `is_readonly` flag to answer them with.\n- `helpers.py` — shared utilities (`list_or_args`, pubsub subscription partitioning, etc.).\n\n`Redis` (sync) and `redis.asyncio.Redis` are assembled by inheriting these mixins plus the connection/pool plumbing. Adding a new command means editing both the sync and async mixin classes and, for cluster routing, possibly the command metadata in `metadata.py`, which drives both the routing policies and replica eligibility. Not `READ_COMMANDS` - it is deprecated and unread. There is a `/add-new-command` skill (`.claude/commands/add-new-command.md`) that follows the project's expected workflow; use the spec template at `.agents/commands/add-new-command/command-specification-template.md`. Before adding commands, also read this file (AGENTS.md) and `.agents/sync_async_type_hints_overload_guide.md` — they document the sync/async type-overload convention that command additions must follow. The `/sync-claude-md` skill audits and refreshes this file when the project structure changes.\n\n### Wire protocol and response parsing\n\n`redis/_parsers/` handles RESP framing and response shaping:\n\n- `resp2.py`, `resp3.py` — pure-Python parsers.\n- `hiredis.py` — optional C-accelerated parser; auto-used if `hiredis>=3.2.0` is installed.\n- `response_callbacks.py` — per-command response post-processing (the place where the library reshapes raw protocol output into Python types).\n- `commands.py` — `CommandsParser`, used by the cluster client to learn command metadata via `COMMAND INFO`; the policy record types it returns live in `redis/commands/metadata.py`.\n- `encoders.py` — request encoding.\n\nRESP3 is now the default on the wire. Two knobs interact:\n\n- `protocol=2|3` chooses the wire protocol.\n- `legacy_responses=True|False` chooses the *Python* response shape. `True` (current default) preserves RESP2-era shapes regardless of wire protocol; `False` returns the new unified shapes. The test matrix exercises both axes; see `specs/unified_responses_migration_guide.md`.\n\n### Cluster client\n\n`redis/cluster.py` (`redis/asyncio/cluster.py`) implements topology discovery, slot mapping (`redis/crc.py`, `key_slot`), per-node connection pools, MOVED/ASK redirection, and routing using `RequestPolicy`/`ResponsePolicy` enums from `redis/commands/metadata.py` resolved via `redis/commands/policies.py`. Replica eligibility for read routing is decided by the metadata resolver's `is_replica_safe`, which reads the `is_readonly` flag on the command metadata record; `READ_COMMANDS` is deprecated and no longer consulted.\n\n### Multi-database (Active-Active) client\n\n`redis/multidb/` (and `redis/asyncio/multidb/`) implements a client that fronts multiple Redis deployments with health checks, failure detection, and failover. Key modules: `client.py`, `command_executor.py`, `database.py`, `failover.py`, `failure_detector.py`, `config.py`, plus `circuit.py` (pybreaker) and `exception.py` on the sync side and `healthcheck.py` on the async side. Mirror sync/async changes here too.\n\n### Cross-cutting subsystems\n\n- `redis/auth/` — credential providers and token-based auth (EntraID via `redis-entraid`; JWT support).\n- `redis/cache.py` — client-side caching configuration. `CacheConfig.is_allowed_to_cache` is the single eligibility decision point, and delegates to the `MetadataResolver` the config holds (`redis/commands/metadata.py`), defaulted to the static table and injected by `ConnectionPool.__init__` from the client-level `metadata_resolver=`. `DEFAULT_ALLOW_LIST` is deprecated and unread - change eligibility in `metadata.py`, never there. `CacheProxyConnection.send_command` asks that decision point and then requires the invocation to carry `keys`; a cacheable command without them bypasses the cache rather than raising, so plumbing `keys=` into a command method is a purely additive way to start caching it.\n- `redis/maint_notifications.py` — server-pushed maintenance notifications and the handler that reacts to them. The handlers report the relaxation they apply as `MaintenanceStartedEvent` / `MaintenanceCompletedEvent` (`redis/event.py`) to the optional `event_dispatcher` on `MaintNotificationsConfig`; the MultiDB health check policy (`redis/asyncio/multidb/healthcheck.py`) is the in-tree consumer, relaxing a database's `health_check_timeout` to `probes * (relaxed_timeout + delay)` while its probe client is under maintenance. With notifications enabled, a pooled connection that has readable data while idle is not reconnected outright by `ensure_connection`: `handle_pending_push_notifications` (both connection mixins) drains the buffered pushes with the handlers deferred, applies them only if the socket is still alive (the maintenance is still in progress), and discards them and raises for the pool to reconnect if the server has closed the socket or a stale reply was left on it.\n- `redis/keyspace_notifications.py` — keyspace/keyevent subscription helpers (sync and async variants live side by side).\n- `redis/observability/` (and `redis/asyncio/observability/`) — OpenTelemetry instrumentation. Modules: `attributes.py` (span/metric attribute keys), `config.py`, `metrics.py`, `providers.py`, `recorder.py` (the API the rest of the code calls — `record_operation_duration`, `record_error_count`, `record_pubsub_message`, `record_streaming_lag_from_response`), `registry.py`. Optional, gated by the `otel` extra. The async stack has only `recorder.py` and delegates the rest to the sync package.\n- `redis/event.py` — `EventDispatcher` used to notify subscribers on connection lifecycle events.\n- `redis/http/` — HTTP client used by some auth flows and scenario tests.\n- `redis/retry.py`, `redis/backoff.py` — retry/backoff strategy (`ExponentialWithJitterBackoff` is the default).\n\n### Docker test images\n\n`dockers/` contains config for standalone, cluster, sentinel, and redis-stack containers. The compose file pulls `redislabs/client-libs-test:<tag>` images parameterized by `CLIENT_LIBS_TEST_IMAGE_TAG` / `CLIENT_LIBS_TEST_STACK_IMAGE_TAG`. CI pins these via `CURRENT_REDIS_VERSION` in `.github/workflows/integration.yaml`.\n\n## Python and dependency notes\n\n- `requires-python = \">=3.10\"`; `pyproject.toml` lists the supported versions.\n- Optional extras: `hiredis`, `xxhash`, `ocsp`, `jwt`, `circuit_breaker`, `otel`.\n- Ruff is the only formatter/linter (`target-version = \"py310\"`, `line-length = 88`). `tests/*` has relaxed naming rules; module command packages (`bf`, `timeseries`, `json`, `search`) opt out of pep8 naming.\n- `whitelist.py` is the vulture allowlist — extend it instead of silencing vulture inline.\n\n---\n\n## MUST\n- Preserve public API compatibility (signatures, argument names, defaults, return types)\n- Keep sync and async implementations fully aligned (behavior, signatures, type hints) when possible\n- Run the full test suite and fix all failures\n- Run linting and fix all issues\n- Follow existing code patterns and conventions exactly\n- Maintain Redis command semantics and behavior precisely\n- Optimize for performance and memory efficiency\n\n## MUST NOT\n- Introduce breaking API changes without explicit need\n- Modify unrelated code\n- Change Redis behavior, edge cases, or error semantics\n- Introduce new dependencies without strong justification\n- Add unnecessary abstractions or new patterns\n- Weaken or remove existing tests to make them pass\n\n## General Principles\n- Make minimal, surgical changes\n- Prefer consistency over innovation\n- If unsure, follow existing implementations in the codebase\n- Prioritize correctness first, then performance\n\n## Type Hints & Overloads\n- Use PEP 604 (`X | Y`) syntax\n- Follow `.agents/sync_async_type_hints_overload_guide.md`\n- Keep type hints identical between sync and async APIs\n- Do not change public type signatures unnecessarily\n\n## Imports\n- Import all types at the top of the file\n- Avoid function-level imports unless absolutely necessary\n- Keep imports minimal and consistent\n\n## Sync / Async Consistency\n- Any change in sync must be mirrored in async (and vice versa)\n- Ensure identical signatures, return types, and behavior\n- Ensure overloads remain aligned\n\n## Performance & Memory\n- Prioritize correctness first, then optimize for performance and memory efficiency\n- Never trade correctness for performance\n- Avoid unnecessary allocations in hot paths\n- Avoid creating temporary objects when not needed\n- Reuse existing helpers/utilities instead of duplicating logic\n- Be careful with large data structures\n\n## Redis Semantics\n- Match Redis server behavior exactly\n- Preserve error types and messages\n- Do not introduce silent behavior changes\n- Handle edge cases explicitly and correctly\n\n## Testing\n- Add or update tests for every behavior change\n- Cover edge cases and failure scenarios\n- Keep tests deterministic and stable\n- Do not relax assertions to pass tests\n\n## Linting & Code Quality\n- Run all linters and fix issues\n- Ensure code is clean, readable, and consistent\n- Perform a full code review after changes\n- Refactor if needed to meet project standards\n\n## Documentation\n- Update docstrings when behavior changes\n- Keep docstrings consistent with type hints\n- Avoid redundant or outdated documentation\n\n## Dependencies\n- Do not introduce new dependencies unless absolutely necessary\n- Prefer existing utilities within the project\n\n---\n\n# 🔁 Self-Check Loop (MANDATORY)\n\nBefore finalizing changes, perform this loop:\n\n1. **Correctness Check**\n   - Does the change preserve Redis semantics exactly?\n   - Are edge cases handled correctly?\n\n2. **API Check**\n   - Did any public function signature change?\n   - If yes, revert or justify explicitly\n\n3. **Sync/Async Check**\n   - Are both implementations updated and identical in behavior?\n   - Are type hints and overloads aligned?\n\n4. **Performance Check**\n   - Did this introduce extra allocations or unnecessary work?\n   - Can this be simplified or reused?\n\n5. **Consistency Check**\n   - Does this match existing patterns in the codebase?\n   - Are naming and structure consistent?\n\n6. **Test Check**\n   - Are tests added/updated for the change?\n   - Do all tests pass without weakening assertions?\n\nRepeat this loop until all answers are satisfactory.\n\n---\n\n# 🔍 Diff Validation (MANDATORY)\n\nBefore submitting changes, review the full diff:\n\n- Ensure only relevant files are modified\n- Ensure no unrelated lines were changed\n- Ensure no accidental deletions of logic\n- Ensure no debug code or comments remain\n- Ensure imports were not unnecessarily altered\n- Ensure public APIs were not unintentionally modified\n\nIf any issue is found:\n- Fix it\n- Re-run validation\n\n---\n\n## 🔎 Code Review (MANDATORY)\n\nAfter implementing changes, perform a thorough code review of the final code.\n\n### Verify:\n\n- **Correctness**\n  - Logic is correct and complete\n  - Edge cases are handled properly\n  - Redis semantics are preserved exactly\n\n- **API Stability**\n  - No unintended changes to public APIs\n  - Function signatures, defaults, and return types are unchanged\n\n- **Sync/Async Parity**\n  - Sync and async implementations are fully aligned\n  - Type hints and overloads match\n\n- **Performance**\n  - No unnecessary allocations or inefficiencies introduced\n  - No redundant work or duplicate logic\n\n- **Consistency**\n  - Code follows existing patterns and conventions\n  - Naming and structure match the rest of the codebase\n\n- **Simplicity**\n  - No unnecessary abstractions added\n  - Implementation is as simple as possible\n\n- **Tests**\n  - Tests cover the change adequately\n  - No weakened assertions\n\n### If any issue is found:\n- Fix it\n- Re-run tests and linting\n- Repeat the review\n\n---\n\n# ✅ Final Checklist (must all be true)\n\n- Tests pass\n- Lint passes\n- Sync/async parity verified\n- No API breakage\n- No unnecessary allocations introduced\n- Behavior matches Redis exactly\n- Code follows existing patterns\n- Diff is minimal and clean\n","CLAUDE.md":"@AGENTS.md\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/redis/redis-py/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# ✅ Instructions for AI agents contributing to redis-py\n\nThis file is the single source of guidance for AI agents working in this repository. It combines project reference material (commands, architecture, dependency notes) with the contributor process rules you must follow.\n\n## Common commands\n\nDevelopment tasks are driven by `invoke` (see `tasks.py`). Activate `.venv` first.\n\n- `invoke devenv` — start the docker-compose test environment (standalone, replica, sentinel, cluster, redis-stack, stunnel). Profiles in `docker-compose.yml` control which containers come up; `--profile all` is the default.\n- `invoke clean` — stop all dockers and remove `build/` and `dist/`.\n- `invoke tests` — run `fixed_client`, `standalone`, then `cluster` test suites in sequence. Accepts `--uvloop`, `--protocol=2|3|\"\"`, `--legacy-responses=true|false`, `--profile`.\n- `invoke standalone-tests` / `invoke cluster-tests` — run just one suite. Cluster suite targets `redis://localhost:16379/0` (`rediss://localhost:27379/0` for TLS).\n- `invoke fixed-client-tests` — runs only tests marked `fixed_client` (tests that pin client config and must not be reconfigured by the global `--protocol` / `--legacy-responses` knobs).\n- `invoke run-test-matrix` — full `protocol` × `legacy_responses` matrix; what CI does.\n- `invoke linters` / `invoke linters-fix` — `ruff check`, `ruff format`, and `vulture redis whitelist.py --min-confidence 80` (use `whitelist.py` to suppress vulture false positives).\n- `invoke all-tests` — linters + tests.\n- `invoke build-docs` — sphinx HTML under `docs/`.\n\nRunning a single test directly with pytest:\n\n```\npytest tests/test_commands.py::TestRedisCommands::test_set -x\npytest tests/test_asyncio/test_commands.py -k \"test_set and not cluster\"\npytest --redis-url=redis://localhost:16379/0 -m onlycluster tests/test_cluster.py\n```\n\nThe pytest plugin in `tests/conftest.py` adds these options (also exposed via invoke wrappers): `--redis-url`, `--redis-ssl-url`, `--redis-mod-url`, `--protocol` (`\"\"` = library default, currently RESP3), `--legacy-responses` (`true|false|default`), `--redis-cluster-nodes`, `--uvloop`. It also adds `--tracemalloc` (off by default, not exposed via invoke), which traces allocations so `ResourceWarning`s show where the object was allocated; it makes cluster client setup several times slower.\n\n### pytest markers\n\nDefined in `pyproject.toml` and used to scope test runs:\n\n- `onlycluster` / `onlynoncluster` — limit a test to one topology.\n- `redismod` — requires Redis modules (Stack image); skipped against cluster.\n- `fixed_client` — test fixes its own client/config; excluded from the protocol/legacy matrix runs.\n- `experimental`, `replica`, `ssl`, `pipeline`, `cp_integration`, `no_mock_connections`.\n\n`tests/test_scenario/` and `tests/test_asyncio/test_scenario/` are scenario tests that require external infrastructure (Redis Enterprise, EntraID, etc.); they are excluded from the default `standalone-tests` / `cluster-tests` runs via `--ignore`.\n\n## Architecture\n\n### Sync and async mirrors\n\nThe library ships two parallel client stacks that must stay in lockstep:\n\n- Sync top-level: `redis/client.py`, `redis/cluster.py`, `redis/connection.py`, `redis/sentinel.py`, `redis/lock.py`, `redis/retry.py`.\n- Async mirrors under `redis/asyncio/`: `client.py`, `cluster.py`, `connection.py`, `sentinel.py`, etc.\n\nWhen changing behavior in one stack, the equivalent change almost always belongs in the other. `specs/sync_async_deduplication_analysis.md` documents the deliberate duplication. The process rules under \"Sync / Async Consistency\" below are mandatory whenever you touch either stack.\n\n### Commands layer (mixins)\n\nCommand surface is composed via mixin classes in `redis/commands/`:\n\n- `core.py` — `CoreCommands` / `AsyncCoreCommands` (every standard Redis command).\n- `cluster.py` — `RedisClusterCommands` / `AsyncRedisClusterCommands`; also exports `READ_COMMANDS`, which is **deprecated and unread**: replica eligibility is decided by the metadata resolver's `is_replica_safe`, so editing this set changes nothing. It is kept only so external importers keep working.\n- `sentinel.py` — sentinel-specific commands.\n- `redismodules.py` — aggregates module mixins (`bf/` Bloom, `json/`, `search/`, `timeseries/`, `vectorset/`). Each module subpackage owns its own command surface and response parsing.\n- `policies.py` — `PolicyResolver` / `StaticPolicyResolver`, the routing view the cluster client resolves per command. Also holds `STATIC_POLICIES`, which is **deprecated and unread**: a frozen verbatim copy of the 7.1.0 table, kept only for external importers and deliberately not derived from or synced with `_STATIC_COMMAND_METADATA`. Change routing in `metadata.py`, never there.\n- `metadata.py` — the command metadata record types: `RequestPolicy` / `ResponsePolicy`, `CommandPolicies` / `PolicyRecords`, and `CommandMetadata` / `CommandMetadataRecordsCache`, plus the metadata resolvers. `CommandMetadata` is the superset record; `_to_command_policies` projects one down to the routing view a `PolicyResolver` serves - projecting to `None` when the record withholds its routing policies, which is how a resolver tells the cluster client to resolve the target itself (the `movablekeys` reads need this: their keys are only in the key specs, so derived policies come out keyless). The two types are kept separate deliberately: `CommandPolicies` shipped in 7.1.0 as a mutable class, and `CommandMetadata` is frozen so one record can be shared across many commands. `redis/_parsers/commands.py` re-exports the policy types from here for backwards compatibility; import them from `redis.commands.metadata`. A resolver is a client-level constructor argument (`metadata_resolver=` on `Redis`, `ConnectionPool`, `RedisCluster`, `NodesManager` and `ClusterPipeline`; on the async side only on `redis.asyncio.RedisCluster`, because the async stack has no client-side cache to be the second consumer), built once and shared with every node client; it serves cluster routing through `resolve_policies`, replica safety through `is_replica_safe`, and client-side-cache eligibility through `is_cacheable`. When only `metadata_resolver` is given the routing resolver is derived from it; an explicit `policy_resolver` still wins for the routing view - which nodes a command targets - but not for anything that view cannot express: replica safety and cache eligibility keep resolving through `metadata_resolver` either way, because a `CommandPolicies` record carries no `is_readonly` flag to answer them with.\n- `helpers.py` — shared utilities (`list_or_args`, pubsub subscription partitioning, etc.).\n\n`Redis` (sync) and `redis.asyncio.Redis` are assembled by inheriting these mixins plus the connection/pool plumbing. Adding a new command means editing both the sync and async mixin classes and, for cluster routing, possibly the command metadata in `metadata.py`, which drives both the routing policies and replica eligibility. Not `READ_COMMANDS` - it is deprecated and unread. There is a `/add-new-command` skill (`.claude/commands/add-new-command.md`) that follows the project's expected workflow; use the spec template at `.agents/commands/add-new-command/command-specification-template.md`. Before adding commands, also read this file (AGENTS.md) and `.agents/sync_async_type_hints_overload_guide.md` — they document the sync/async type-overload convention that command additions must follow. The `/sync-claude-md` skill audits and refreshes this file when the project structure changes.\n\n### Wire protocol and response parsing\n\n`redis/_parsers/` handles RESP framing and response shaping:\n\n- `resp2.py`, `resp3.py` — pure-Python parsers.\n- `hiredis.py` — optional C-accelerated parser; auto-used if `hiredis>=3.2.0` is installed.\n- `response_callbacks.py` — per-command response post-processing (the place where the library reshapes raw protocol output into Python types).\n- `commands.py` — `CommandsParser`, used by the cluster client to learn command metadata via `COMMAND INFO`; the policy record types it returns live in `redis/commands/metadata.py`.\n- `encoders.py` — request encoding.\n\nRESP3 is now the default on the wire. Two knobs interact:\n\n- `protocol=2|3` chooses the wire protocol.\n- `legacy_responses=True|False` chooses the *Python* response shape. `True` (current default) preserves RESP2-era shapes regardless of wire protocol; `False` returns the new unified shapes. The test matrix exercises both axes; see `specs/unified_responses_migration_guide.md`.\n\n### Cluster client\n\n`redis/cluster.py` (`redis/asyncio/cluster.py`) implements topology discovery, slot mapping (`redis/crc.py`, `key_slot`), per-node connection pools, MOVED/ASK redirection, and routing using `RequestPolicy`/`ResponsePolicy` enums from `redis/commands/metadata.py` resolved via `redis/commands/policies.py`. Replica eligibility for read routing is decided by the metadata resolver's `is_replica_safe`, which reads the `is_readonly` flag on the command metadata record; `READ_COMMANDS` is deprecated and no longer consulted.\n\n### Multi-database (Active-Active) client\n\n`redis/multidb/` (and `redis/asyncio/multidb/`) implements a client that fronts multiple Redis deployments with health checks, failure detection, and failover. Key modules: `client.py`, `command_executor.py`, `database.py`, `failover.py`, `failure_detector.py`, `config.py`, plus `circuit.py` (pybreaker) and `exception.py` on the sync side and `healthcheck.py` on the async side. Mirror sync/async changes here too.\n\n### Cross-cutting subsystems\n\n- `redis/auth/` — credential providers and token-based auth (EntraID via `redis-entraid`; JWT support).\n- `redis/cache.py` — client-side caching configuration. `CacheConfig.is_allowed_to_cache` is the single eligibility decision point, and delegates to the `MetadataResolver` the config holds (`redis/commands/metadata.py`), defaulted to the static table and injected by `ConnectionPool.__init__` from the client-level `metadata_resolver=`. `DEFAULT_ALLOW_LIST` is deprecated and unread - change eligibility in `metadata.py`, never there. `CacheProxyConnection.send_command` asks that decision point and then requires the invocation to carry `keys`; a cacheable command without them bypasses the cache rather than raising, so plumbing `keys=` into a command method is a purely additive way to start caching it.\n- `redis/maint_notifications.py` — server-pushed maintenance notifications and the handler that reacts to them. The handlers report the relaxation they apply as `MaintenanceStartedEvent` / `MaintenanceCompletedEvent` (`redis/event.py`) to the optional `event_dispatcher` on `MaintNotificationsConfig`; the MultiDB health check policy (`redis/asyncio/multidb/healthcheck.py`) is the in-tree consumer, relaxing a database's `health_check_timeout` to `probes * (relaxed_timeout + delay)` while its probe client is under maintenance. With notifications enabled, a pooled connection that has readable data while idle is not reconnected outright by `ensure_connection`: `handle_pending_push_notifications` (both connection mixins) drains the buffered pushes with the handlers deferred, applies them only if the socket is still alive (the maintenance is still in progress), and discards them and raises for the pool to reconnect if the server has closed the socket or a stale reply was left on it.\n- `redis/keyspace_notifications.py` — keyspace/keyevent subscription helpers (sync and async variants live side by side).\n- `redis/observability/` (and `redis/asyncio/observability/`) — OpenTelemetry instrumentation. Modules: `attributes.py` (span/metric attribute keys), `config.py`, `metrics.py`, `providers.py`, `recorder.py` (the API the rest of the code calls — `record_operation_duration`, `record_error_count`, `record_pubsub_message`, `record_streaming_lag_from_response`), `registry.py`. Optional, gated by the `otel` extra. The async stack has only `recorder.py` and delegates the rest to the sync package.\n- `redis/event.py` — `EventDispatcher` used to notify subscribers on connection lifecycle events.\n- `redis/http/` — HTTP client used by some auth flows and scenario tests.\n- `redis/retry.py`, `redis/backoff.py` — retry/backoff strategy (`ExponentialWithJitterBackoff` is the default).\n\n### Docker test images\n\n`dockers/` contains config for standalone, cluster, sentinel, and redis-stack containers. The compose file pulls `redislabs/client-libs-test:<tag>` images parameterized by `CLIENT_LIBS_TEST_IMAGE_TAG` / `CLIENT_LIBS_TEST_STACK_IMAGE_TAG`. CI pins these via `CURRENT_REDIS_VERSION` in `.github/workflows/integration.yaml`.\n\n## Python and dependency notes\n\n- `requires-python = \">=3.10\"`; `pyproject.toml` lists the supported versions.\n- Optional extras: `hiredis`, `xxhash`, `ocsp`, `jwt`, `circuit_breaker`, `otel`.\n- Ruff is the only formatter/linter (`target-version = \"py310\"`, `line-length = 88`). `tests/*` has relaxed naming rules; module command packages (`bf`, `timeseries`, `json`, `search`) opt out of pep8 naming.\n- `whitelist.py` is the vulture allowlist — extend it instead of silencing vulture inline.\n\n---\n\n## MUST\n- Preserve public API compatibility (signatures, argument names, defaults, return types)\n- Keep sync and async implementations fully aligned (behavior, signatures, type hints) when possible\n- Run the full test suite and fix all failures\n- Run linting and fix all issues\n- Follow existing code patterns and conventions exactly\n- Maintain Redis command semantics and behavior precisely\n- Optimize for performance and memory efficiency\n\n## MUST NOT\n- Introduce breaking API changes without explicit need\n- Modify unrelated code\n- Change Redis behavior, edge cases, or error semantics\n- Introduce new dependencies without strong justification\n- Add unnecessary abstractions or new patterns\n- Weaken or remove existing tests to make them pass\n\n## General Principles\n- Make minimal, surgical changes\n- Prefer consistency over innovation\n- If unsure, follow existing implementations in the codebase\n- Prioritize correctness first, then performance\n\n## Type Hints & Overloads\n- Use PEP 604 (`X | Y`) syntax\n- Follow `.agents/sync_async_type_hints_overload_guide.md`\n- Keep type hints identical between sync and async APIs\n- Do not change public type signatures unnecessarily\n\n## Imports\n- Import all types at the top of the file\n- Avoid function-level imports unless absolutely necessary\n- Keep imports minimal and consistent\n\n## Sync / Async Consistency\n- Any change in sync must be mirrored in async (and vice versa)\n- Ensure identical signatures, return types, and behavior\n- Ensure overloads remain aligned\n\n## Performance & Memory\n- Prioritize correctness first, then optimize for performance and memory efficiency\n- Never trade correctness for performance\n- Avoid unnecessary allocations in hot paths\n- Avoid creating temporary objects when not needed\n- Reuse existing helpers/utilities instead of duplicating logic\n- Be careful with large data structures\n\n## Redis Semantics\n- Match Redis server behavior exactly\n- Preserve error types and messages\n- Do not introduce silent behavior changes\n- Handle edge cases explicitly and correctly\n\n## Testing\n- Add or update tests for every behavior change\n- Cover edge cases and failure scenarios\n- Keep tests deterministic and stable\n- Do not relax assertions to pass tests\n\n## Linting & Code Quality\n- Run all linters and fix issues\n- Ensure code is clean, readable, and consistent\n- Perform a full code review after changes\n- Refactor if needed to meet project standards\n\n## Documentation\n- Update docstrings when behavior changes\n- Keep docstrings consistent with type hints\n- Avoid redundant or outdated documentation\n\n## Dependencies\n- Do not introduce new dependencies unless absolutely necessary\n- Prefer existing utilities within the project\n\n---\n\n# 🔁 Self-Check Loop (MANDATORY)\n\nBefore finalizing changes, perform this loop:\n\n1. **Correctness Check**\n   - Does the change preserve Redis semantics exactly?\n   - Are edge cases handled correctly?\n\n2. **API Check**\n   - Did any public function signature change?\n   - If yes, revert or justify explicitly\n\n3. **Sync/Async Check**\n   - Are both implementations updated and identical in behavior?\n   - Are type hints and overloads aligned?\n\n4. **Performance Check**\n   - Did this introduce extra allocations or unnecessary work?\n   - Can this be simplified or reused?\n\n5. **Consistency Check**\n   - Does this match existing patterns in the codebase?\n   - Are naming and structure consistent?\n\n6. **Test Check**\n   - Are tests added/updated for the change?\n   - Do all tests pass without weakening assertions?\n\nRepeat this loop until all answers are satisfactory.\n\n---\n\n# 🔍 Diff Validation (MANDATORY)\n\nBefore submitting changes, review the full diff:\n\n- Ensure only relevant files are modified\n- Ensure no unrelated lines were changed\n- Ensure no accidental deletions of logic\n- Ensure no debug code or comments remain\n- Ensure imports were not unnecessarily altered\n- Ensure public APIs were not unintentionally modified\n\nIf any issue is found:\n- Fix it\n- Re-run validation\n\n---\n\n## 🔎 Code Review (MANDATORY)\n\nAfter implementing changes, perform a thorough code review of the final code.\n\n### Verify:\n\n- **Correctness**\n  - Logic is correct and complete\n  - Edge cases are handled properly\n  - Redis semantics are preserved exactly\n\n- **API Stability**\n  - No unintended changes to public APIs\n  - Function signatures, defaults, and return types are unchanged\n\n- **Sync/Async Parity**\n  - Sync and async implementations are fully aligned\n  - Type hints and overloads match\n\n- **Performance**\n  - No unnecessary allocations or inefficiencies introduced\n  - No redundant work or duplicate logic\n\n- **Consistency**\n  - Code follows existing patterns and conventions\n  - Naming and structure match the rest of the codebase\n\n- **Simplicity**\n  - No unnecessary abstractions added\n  - Implementation is as simple as possible\n\n- **Tests**\n  - Tests cover the change adequately\n  - No weakened assertions\n\n### If any issue is found:\n- Fix it\n- Re-run tests and linting\n- Repeat the review\n\n---\n\n# ✅ Final Checklist (must all be true)\n\n- Tests pass\n- Lint passes\n- Sync/async parity verified\n- No API breakage\n- No unnecessary allocations introduced\n- Behavior matches Redis exactly\n- Code follows existing patterns\n- Diff is minimal and clean\n","isInternal":false,"tokens":4134,"sizeBytes":18435},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/redis/redis-py/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"@AGENTS.md\n","isInternal":false,"tokens":5,"sizeBytes":11},{"name":"pr-labels-guide.md","path":".agents/skills/generate-release-notes/references/pr-labels-guide.md","rawUrl":"https://raw.githubusercontent.com/redis/redis-py/HEAD/.agents/skills/generate-release-notes/references/pr-labels-guide.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# PR Labels Guide for Release Notes\n\nThis guide is the **authoritative, self-contained mapping** from a PR's labels (or, when there is no\nuseful label, its actual change) to a release-notes category. Use it to categorize every commit/PR in\nthe release range. We do **not** rely on any automated release-drafter output to decide what goes\nwhere or which PRs are included — categorize manually using the rules below.\n\n## Category labels (drive a release-notes section)\n\nThese are the only labels that place a commit into a section. When a PR carries labels from more than\none category, **list it under every matching section** — one line per section (e.g. a PR labeled both\n`deprecation` and `breakingchange` appears in both ⚠️ Deprecations and 🔥 Breaking Changes). The\npriority order in this table is used only to pick a single section when a PR has **no** category\nlabel and you must infer one (see \"Inferring a category when there is no label\" below).\n\n| Section | Mapped labels | Classify a PR here when it... |\n| --- | --- | --- |\n| 🚀 New Features | `feature`, `enhancement` | Adds a new Redis command, client capability, public option, or a user-visible enhancement to existing behavior. |\n| 🧪 Experimental Features | `experimental` | Adds a preview/experimental capability not yet covered by compatibility guarantees (often also tagged `experimental` in code/tests). |\n| 🔥 Breaking Changes | `breakingchange` | Changes or removes any public contract: a function/method signature, argument name, default value, return type, raised exception type, wire-protocol/response shape, or persisted/connection config — anything that can break a user upgrading from the latest released version. |\n| ⚠️ Deprecations | `deprecation` | Marks a public API, option, or behavior as deprecated while keeping it working this release (typically emitting a `DeprecationWarning`). If the same PR *also* removes/changes another public contract now, it additionally appears in 🔥 Breaking Changes (list it in both). |\n| 🐛 Bug Fixes | `fix`, `bugfix`, `bug`, `BUG` | Corrects incorrect behavior, a crash, a leak, or a regression in supported functionality. |\n| 🧰 Maintenance | `maintenance`, `dependencies`, `documentation`, `docs`, `testing` | CI/release tooling, dependency bumps, docs, tests, refactors, type-hint/lint cleanups — no user-facing behavior change. |\n\n## Repo labels that need an explicit decision\n\nThese labels exist in the redis-py repo but are **not** one of the category labels above, so a PR\nlabeled only with one of them does not name its own section. Route them as follows, and add the\nproper category label to the PR when possible so the labeling is correct for future reference:\n\n- `bug-fix` — treat as **🐛 Bug Fixes** (synonym of `bug`/`fix`; prefer also adding `bug`).\n- `security` — place under **🐛 Bug Fixes** by default; if the fix changes a public contract, it is\n  **🔥 Breaking Changes** instead. Strongly consider calling it out in `## ✨ Highlights` with the\n  advisory/CVE reference regardless of section.\n- `techdebt` — **🧰 Maintenance**.\n- `deprecation` — route to the **⚠️ Deprecations** section (see the category table above). If the PR\n  removes or changes a public contract *in this release* (not just warns), it belongs in **🔥 Breaking\n  Changes** instead.\n- Topic/area labels — `async`, `cluster`, `windows`, `redis-7`, `redis-py-5`, `RediSearch`,\n  `RedisJSON`, `RedisGraph` — describe *where* a change applies, not *what kind* it is. They never\n  select a section; a PR carrying only these still needs a category decision from the rules below.\n\n## Excluded and non-release labels\n\n- `skip-changelog` — **exclude the PR from the release notes entirely.**\n- Process/triage labels never appear in release notes and are ignored for categorization:\n  `triage`, `question`, `discussion`, `duplicate`, `stale`, `help-wanted`, `good first issue`,\n  `need more info`, `needs-information`, `ready-for-merge`, `changes-requested`,\n  `waiting-for-response`, `docs-review`, `update-docs`, `run-benchmark`, `hacktoberfest-accepted`,\n  `tracking-issue`.\n\n## Inferring a category when there is no label\n\nWhen a PR/commit has no category label, infer the section from the change itself. Apply **every** rule\nthat matches — a single change can land in more than one section (e.g. it both deprecates one API and\nbreaks another, so it appears in Deprecations and Breaking Changes):\n\n1. **New Features** — adds a new Redis command, client/connection capability, or public option, or\n   a clearly user-visible enhancement. Title often starts with `feat`/`feature`/`add`. Label:\n   `feature`.\n2. **Experimental Features** — the change is explicitly a preview/experimental feature (marked\n   experimental in code, tests under the `experimental` marker, or stated as preview). Label:\n   `experimental`.\n3. **Breaking Changes** — the diff changes/removes a public signature, argument name, default,\n   return type, raised exception type, or wire-protocol/response shape against the latest release, or\n   the PR title/body says \"breaking\", \"remove\", \"drop support\". Suggested label: `breakingchange`.\n4. **Deprecations** — the change marks a public API/option/behavior as deprecated but keeps it\n   working (e.g. adds a `DeprecationWarning`, a \"deprecated\" note in a docstring, or a\n   `@deprecated` marker) without removing it this release. Suggested label: `deprecation`.\n5. **Bug Fixes** — fixes incorrect behavior, a crash, a connection/resource leak, or a regression.\n   Title often starts with `fix`. Branch named `bug-*`. Label: `bug`.\n6. **Maintenance** — everything else with no user-facing behavior change: touches only `*.md`,\n   `.github/*`, `docs/`, `tests/`, `pyproject.toml`/`dev_requirements.txt`/`docker-compose.yml`/\n   `dockers/`/`tasks.py`, dependency bumps, refactors, type-hint/lint cleanups, or CI. Branch named\n   `maintenance-*`. Label: `maintenance`.\n\nBranch-naming hints that help inference: branch `bug-*` → Bug Fixes; branch `maintenance-*` →\nMaintenance; branch `feature-*` → New Features; changes touching only `*.md` or `.github/*` →\nMaintenance.\n\nWhen the right category is genuinely ambiguous, record the uncertainty in the draft (a `<!-- TODO:\nconfirm category for #N -->` comment) rather than silently guessing, and surface it to the user so\nthe PR can be labeled correctly.\n\n## sync/async note\n\nA single behavior change is frequently delivered as paired sync/async work, usually in a single PR.\nIf two separate PRs (one sync, one async) implement the same change, treat them as one change: group\nthem onto a single line under the same category, with both PR numbers in one bracket group\n(`- <title> (#3434 #3456)`), per the grouping rule in `release-notes-template.md`.\n","isInternal":false,"tokens":1631,"sizeBytes":6797},{"name":"release-notes-template.md","path":".agents/skills/generate-release-notes/references/release-notes-template.md","rawUrl":"https://raw.githubusercontent.com/redis/redis-py/HEAD/.agents/skills/generate-release-notes/references/release-notes-template.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Release Notes Template\n\nThis is the canonical structure for a redis-py GitHub release: the label-driven category sections\nplus the hand-authored sections maintainers add before publishing. Use it as the skeleton when\nassembling a draft. The label→category rules live in `pr-labels-guide.md`; this file owns the section\norder, titles, line format, and full skeleton.\n\n## Section order\n\nThe canonical sections, in the order they must appear, and the PR labels that route a commit into\neach (see `pr-labels-guide.md` for the full classification rules):\n\n| Order | Section title | PR labels that route a commit here |\n| --- | --- | --- |\n| 1 | `## 🚀 New Features` | `feature`, `enhancement` |\n| 2 | `## 🧪 Experimental Features` | `experimental` |\n| 3 | `## 🔥 Breaking Changes` | `breakingchange` |\n| 4 | `## ⚠️ Deprecations` | `deprecation` |\n| 5 | `## 🐛 Bug Fixes` | `fix`, `bugfix`, `bug`, `BUG` |\n| 6 | `## 🧰 Maintenance` | `maintenance`, `dependencies`, `documentation`, `docs`, `testing` |\n\nRules:\n\n- **Omit empty sections.** A category with no commits is not rendered (see the patch releases below).\n- **Each line** uses the change template `- $TITLE (#$NUMBER)` — the PR title verbatim, then a space, then `(#<pr-number>)`.\n- **Group PRs that deliver the same feature/change onto a single line**, with every related PR number\n  in one space-separated bracket group: `- <title> (#3434 #3456 #3467)`. Use the clearest title for\n  the combined change (usually the primary PR's). This is common for paired sync/async PRs, a\n  feature split across follow-ups, or a fix plus its test/typing follow-up. List the PR numbers in\n  ascending order. Only group PRs that are genuinely the same change — do not merge unrelated work to\n  shorten the list. If the grouped PRs would land in different categories, place the combined line in\n  each matching section (same one-line-per-section rule below).\n- **Exclude** any PR labeled `skip-changelog`.\n- A commit whose PR carries labels from more than one category is listed under **every matching section** — one line per section (e.g. a PR labeled both `deprecation` and `breakingchange` appears in both ⚠️ Deprecations and 🔥 Breaking Changes). The priority order is used only to infer a single section for a PR that has no category label.\n- For any commit/PR with **no category label**, infer the section using `pr-labels-guide.md` before placing it.\n\n## Optional hand-authored sections\n\nThese are written by maintainers, not derived from labels. Include them only when the release warrants it.\n\n- `## ✨ Highlights` — include this **only when the release has something significant worth focusing\n  the reader on**: a notable new feature, or a breaking change that may affect a large share of users.\n  Use `###` subsections with prose describing each highlight, with code/API names and links to\n  docs/specs. Routine patch releases have no Highlights.\n- A lead prose paragraph immediately under `# Changes` (no heading) summarizing a headline change and\n  linking the migration guide. (See the 8.0.0b2 pre-release.)\n- Highlights/lead prose go **above** the generated category sections.\n\n## Contributors footer\n\nEnd with the thank-you line followed by the de-duplicated list of contributor GitHub handles\n(`@handle`), space-separated, for everyone whose PR is included:\n\n```\nWe'd like to thank all the contributors who worked on this release!\n@handle1 @handle2 @handle3\n```\n\n## Full skeleton\n\n```markdown\n# Changes\n\n## ✨ Highlights\n\n### <Notable feature name>\n\n<Prose: what it is, the public API/class/option names, and links to docs or specs. Omit this whole\nsection for routine patch releases.>\n\n## 🚀 New Features\n\n- <PR title> (#<n>)\n\n## 🧪 Experimental Features\n\n- <PR title> (#<n>)\n\n## 🔥 Breaking Changes\n\n- <PR title> (#<n>)\n\n## ⚠️ Deprecations\n\n- <PR title> (#<n>)\n\n## 🐛 Bug Fixes\n\n- <PR title> (#<n>)\n\n## 🧰 Maintenance\n\n- <PR title> (#<n>)\n\nWe'd like to thank all the contributors who worked on this release!\n@handle1 @handle2\n```\n\n## Worked examples from published releases\n\n### Minor/major release (8.0.0-style)\n\n`# Changes` → `## ✨ Highlights` (several `###` prose subsections covering async cluster pubsub,\nkeyspace notifications, RESP3-by-default, type-hint overloads, connection/retry defaults) →\ncategory sections → contributors footer.\n\n### Pre-release (8.0.0b2-style)\n\n`# Changes` → lead prose paragraph on the headline breaking change with a migration-guide link →\nNew Features → Breaking Changes → Maintenance → contributors footer.\n\n### Patch release (8.0.1-style)\n\n`# Changes` → `## 🐛 Bug Fixes` → `## 🧰 Maintenance` → contributors footer. No Highlights, and\nempty categories (Breaking, Experimental, New Features) are omitted entirely.\n","isInternal":false,"tokens":1185,"sizeBytes":4784},{"name":"SKILL.md","path":".agents/skills/generate-release-notes/SKILL.md","rawUrl":"https://raw.githubusercontent.com/redis/redis-py/HEAD/.agents/skills/generate-release-notes/SKILL.md","title":"generate-release-notes","category":"anthropic-skill","format":"markdown","content":"---\nname: generate-release-notes\ndescription: Generate a redis-py GitHub release-notes draft and save it as a Markdown file. Collects the merged PRs/commits since the previous release, maps each commit to its PR labels, categorizes them into the project's release sections (Breaking Changes, Deprecations, Experimental Features, New Features, Bug Fixes, Maintenance) using the skill's pr-labels-guide, infers a category for unlabeled PRs, and assembles the notes with an optional Highlights section and a contributors footer. Trigger this skill whenever the user says \"release notes\", \"create release notes\", \"generate release notes\", \"draft release notes\", \"prepare release notes\", \"changelog\", or asks for the release notes/changelog of a specific version or branch (e.g. \"release notes for 8.0\", \"generate release notes for the 8.0 branch\", \"what changed since the last release\"). A phrase like \"release notes for 8.0\" means: use branch `8.0` as the release-branch input. Also use when asked to summarize what changed since the last tag.\n---\n\n# Generate Release Notes\n\n## Objective\n\nProduce a redis-py release-notes draft that matches the structure of the project's published GitHub\nreleases, derived from the merged PRs on a given **release branch** since the previous release and\ncategorized by PR label. The release branch is the skill's primary input (step 1). Save the result as a Markdown file. Never publish it — drafting only; the user publishes the release themselves.\n\n**Do not rely on any automated release-drafter output.** We do not use it to decide which PRs are\nincluded or how they are categorized — it misses PRs and miscategorizes them. Collect the PRs\nyourself from the git history (step 2) and categorize every one of them manually using this skill's\nreferences, which are the complete and authoritative rules:\n\n- `references/release-notes-template.md` — the section order, titles, line format, and full skeleton.\n- `references/pr-labels-guide.md` — the authoritative label→category mapping, what each label means,\n  and how to categorize a PR/commit that has no category label.\n\n## Constraints\n\n- **Read-only GitHub access only.** Per the repository owner's standing rule, never run `gh` (or any\n  GitHub API call) that writes — no creating/editing releases, comments, labels, or tags — unless the\n  user explicitly authorizes that specific action in the same turn. Use `gh ... view/list`,\n  `git log`, or GET-only `gh api`/`curl` to gather data. `gh` may fail with a TLS error inside the\n  command sandbox; run it with the sandbox disabled when needed.\n- This skill **only writes the draft Markdown file** in the working tree. It does not commit, tag,\n  push, or create a GitHub release.\n\n## Workflow\n\n### 1. Determine the release range and version\n\n1. **Read the release branch as input.** The branch the release is cut from is the primary input to\n   this skill — it defines which changes are included. Take it from the skill invocation/args if\n   provided; otherwise ask the user for it (e.g. `8.0`, `release/8.0.2`, `master`). Do not guess.\n   Verify it exists (`git rev-parse --verify <branch>`; fetch first if it is a remote branch,\n   `origin/<branch>`). Every change to include is a commit reachable from this branch and not from\n   the previous release tag.\n2. Identify the previous release tag: the most recent `v*` tag reachable from the release branch,\n   `git describe --tags --abbrev=0 <branch>` (or `gh release list -L 5`, read-only). Confirm with the\n   user if a non-default base is intended.\n3. Decide the new version. redis-py ships major, minor, and patch releases (e.g. `8.0.1`, `7.4.1`).\n   Infer minor vs patch from the change set (any Breaking/New Feature → at least a minor bump) and\n   confirm the exact version with the user if unclear. The tag form is `v<version>`.\n\n### 2. Collect the merged PRs / commits in range\n\n1. List the commits on the release branch since the previous tag:\n   `git log --oneline --no-merges <prev-tag>..<branch>`. Each squashed/merged commit title typically\n   ends with `(#<pr-number>)`; extract that number. For merge-commit workflows, use\n   `git log --merges` or the GitHub compare API instead.\n2. For each PR number, fetch its **title, labels, and author** (read-only):\n   `gh pr view <n> --json number,title,labels,author` or\n   `gh api repos/redis/redis-py/pulls/<n>`. Use the PR title for the change line (it is what the\n   published notes use), not the raw commit subject, when they differ.\n3. Drop any PR labeled `skip-changelog`.\n4. If a commit has no associated PR (direct push), use the commit subject and treat it as unlabeled.\n\n### 3. Categorize each commit\n\nFor each remaining commit, categorize it using `references/release-notes-template.md` and\n`references/pr-labels-guide.md`:\n\n- If the PR has one or more **category labels**, list it under **every** matching section — a PR with\n  two category labels appears in both (e.g. `deprecation` + `breakingchange` → both ⚠️ Deprecations\n  and 🔥 Breaking Changes).\n- If the PR has **no category label**, or only topic/process labels, or a non-category repo label\n  (e.g. `bug-fix`, `security`, `techdebt`), infer the section(s) using\n  `references/pr-labels-guide.md` — apply **every** inference rule that matches, so a change may land\n  in more than one section. Note the inferred labels you would suggest applying to the PR.\n- When a category is genuinely ambiguous, place your best guess and leave a\n  `<!-- TODO: confirm category for #<n> -->` marker, then surface it to the user.\n\n### 4. Assemble the draft\n\nFollow `references/release-notes-template.md`:\n\n1. Start with `# Changes`.\n2. Optionally add `## ✨ Highlights` (or a lead prose paragraph) — hand-authored `###` subsections\n   describing each highlight with API names and doc/spec links. Include it **only when the release has\n   something significant to focus the reader on**: a notable new feature, or a breaking change that\n   may affect a large share of users. Omit for routine patch releases. When the release contains\n   Breaking Changes or notable New Features, ask the user whether highlights are wanted.\n3. Emit the category sections **in the order from `release-notes-template.md`** (🚀 New Features,\n   🧪 Experimental, 🔥 Breaking Changes, ⚠️ Deprecations, 🐛 Bug Fixes, 🧰 Maintenance), omitting any\n   empty section. Each line is `- <PR title> (#<n>)`. **Combine PRs that deliver the same\n   feature/change onto one line** with all their numbers in a single bracket group in ascending order,\n   e.g. `- <title> (#3434 #3456 #3467)` (common for paired sync/async PRs or a feature split across\n   follow-ups) — see the grouping rule in `references/release-notes-template.md`.\n4. End with the contributors footer: the thank-you line and the de-duplicated, space-separated list\n   of `@<author>` handles for every included PR.\n\n### 5. Save the draft as a local Markdown file\n\nWrite the assembled notes to a **local Markdown file** so the maintainer can review it and copy the\ncontents into the GitHub release UI by hand. This file is a local review artifact only:\n\n- Default path: `release_notes/release_notes_<version>.md` at the repo root (e.g.\n  `release_notes/release_notes_8.0.2.md`). Create the `release_notes/` folder if it does not exist.\n  Confirm or accept an override path from the user.\n- **Do not commit, tag, push, or create/edit a GitHub release.** The skill only writes the file in\n  the working tree; the maintainer reviews it and publishes manually. (If the `release_notes/` folder\n  is not already git-ignored, mention that so it is not accidentally committed.)\n- The file content is exactly what should be pasted into the release body — no extra wrapper or\n  commentary inside the file.\n\nAfter writing, report the file path and a short summary: the version, the previous tag, the\nper-section commit counts, the count of PRs whose category was inferred (and which ones), and any\n`TODO` markers left in the draft.\n\n## Verification\n\n- Cross-check the commit count: the number of **distinct PR numbers** referenced across all change\n  lines (a grouped line contributes all of its numbers), plus any `skip-changelog`/excluded PRs,\n  should equal the commits in `git log <prev-tag>..<branch>` (minus merge commits). Grouping related\n  PRs reduces the number of lines but not the number of PR references, so reconcile on PR numbers, not\n  lines.\n- Confirm section order and titles match `references/release-notes-template.md` and no empty section\n  was emitted.\n- Confirm every included PR number resolves to a real PR and every contributor handle is included\n  once.\n- Spot-check against the previous published release's formatting (run `gh release view <prev-tag>`).\n","isInternal":false,"tokens":2095,"sizeBytes":8748},{"name":"evaluation-framework.md","path":".agents/skills/maintainer-review/references/evaluation-framework.md","rawUrl":"https://raw.githubusercontent.com/redis/redis-py/HEAD/.agents/skills/maintainer-review/references/evaluation-framework.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Maintainer Evaluation Framework\n\nUse this reference when a claim is ambiguous, severity is disputed, or a technically correct PR may not justify permanent maintenance.\n\n## Contents\n\n- [Decision model](#decision-model)\n- [Severity](#severity)\n- [Evidence strength](#evidence-strength)\n- [Unmet need and alternative design gate](#unmet-need-and-alternative-design-gate)\n- [Issue disposition](#issue-disposition)\n- [PR quality and value](#pr-quality-and-value)\n- [Documentation threshold](#documentation-threshold)\n- [Lifecycle and failure paths](#lifecycle-and-failure-paths)\n- [Concurrency and cleanup ownership](#concurrency-and-cleanup-ownership)\n- [Better-alternative prompts](#better-alternative-prompts)\n- [Competing pull requests](#competing-pull-requests)\n- [Maintainer comments](#maintainer-comments)\n- [Compact report variants](#compact-report-variants)\n\n## Decision Model\n\nTreat validity, severity, and merge-worthiness as separate outputs. Also distinguish a `Preliminary assessment`, which may still require approved runtime evidence, from a final `Maintainer decision`. Do not label a provisional positive result as a verdict or final decision.\n\n| Dimension | Question | Strong evidence |\n| --- | --- | --- |\n| Claim validity | Does the exact behavior occur, and is the proposed cause correct? | Reproduction, failing focused test, or complete reachable path |\n| Reachability | Can supported realistic inputs reach it? | Public API trace, real configuration, user report, or release comparison |\n| Consequence | What fails, and is it silent or recoverable? | Observed output/error/state and downstream effect |\n| Breadth | Which stacks (sync/async), protocols (RESP2/RESP3, `legacy_responses`), topologies (standalone/cluster/sentinel), parsers (pure-Python/hiredis), and versions are affected? | Explicit path and compatibility matrix |\n| Frequency | Is it normal, intermittent, or pathological? | Repeats, deterministic preconditions, reports, or telemetry |\n| Need evidence | Is the exact scope demonstrated, merely plausible, already covered, or unsupported? | Same-scope user scenario, real-path reproduction, released compatibility requirement, violated supported contract, repeated demand, or broad consequential invariant |\n| Unmet need | What user outcome cannot be achieved through supported behavior today, or what supported contract is violated? | Concrete scenario plus a trace showing why the closest existing path is insufficient or defective |\n| Existing capability | Can configuration, composition, client/pool options, callbacks, `response_callbacks`, retry strategies, an existing command mixin, or a caller-owned layer already satisfy the outcome? | Current release code, tests, docs, and an exact supported workflow |\n| Compatibility | Is released public API, signature, default, return type, error/exception type, wire-protocol handling, or connection/config behavior changed? | Latest released (PyPI) comparison and contract inspection |\n| Solution fit | Is the requested mechanism the best design and implementation layer? | Proposed solution compared with the strongest existing path and at least one narrower or more coherent alternative |\n| Resource ownership | Can stale, failed, cancelled, or overlapping work mutate or clean up resources owned by surviving work (pool connections, locks, slot maps, pub/sub state)? | Interleaving trace, attempt or generation ownership, and survivor assertions |\n| Maintenance cost | What permanent complexity and review burden is added? | New branches/configuration, changed surface, tests across stacks/protocols, and remaining work |\n\n## Severity\n\n- **Negligible**: no runtime difference, unreachable/unsupported input, cosmetic inconsistency, or harmless edge case. Usually close, document, or decline code complexity.\n- **Low**: real but narrow and recoverable behavior with a simple workaround and no data, security, or compatibility risk. Merge only when the fix is small and strengthens an invariant.\n- **Moderate**: supported use fails or produces incorrect behavior for a meaningful subset (one stack, one protocol, one topology). Prioritize a bounded fix and regression test.\n- **High**: common or important use is broken, released compatibility is seriously affected, sensitive data can leak, connections/pools leak or corrupt, or persistent corruption is possible. Require urgent strong validation.\n- **Critical**: broadly exploitable security impact, severe data loss, or systemic failure requiring coordinated action. Use only with concrete evidence.\n\nSeverity is consequence multiplied by realistic reach and frequency, reduced by recoverability. Do not raise it because prose is alarming or lower it because a diff is small.\n\n## Evidence Strength\n\nBefore calling a claim confirmed, answer:\n\n- Does the reproduction exercise the same public/internal path, stack (sync/async), protocol, topology, and parser?\n- Does failure occur on the relevant base, latest release, or target?\n- Does the regression test fail without the patch and pass with it?\n- Are stale build/`dist`, wrong virtualenv/imports, dependency drift (including hiredis presence/version), proxies, caches, runtime conditions, unavailable Docker/sandbox, authentication, quota, and Redis-server-version differences excluded?\n- Does an equivalent sync/async, RESP2/RESP3, `legacy_responses`, standalone/cluster/sentinel, or hiredis/pure-Python path differ?\n- Is behavior prohibited by a real contract (public API, documented Redis command semantics, error/exception type, wire protocol) or merely surprising?\n- For latency, timeout, buffering, backpressure, or cleanup, was observable time or state measured rather than inferred only from mocks?\n- For shared asynchronous state, do tests control completion order and prove that a stale failure or cleanup cannot affect the surviving operation?\n\nUse `partially confirmed` when the symptom is real but cause/reach/scope is wrong. Use `unproven` when decisive evidence is missing. Use `contradicted` only when evidence directly disproves the claim.\n\n## Unmet Need and Alternative Design Gate\n\nIssue reports often combine a desired outcome with a proposed API or implementation. Treat the proposed mechanism as a hypothesis. Confirm the unmet outcome or violated supported contract before evaluating how well the patch implements that mechanism.\n\n### Linked-evidence scope\n\nEvidence from a linked issue applies only when the issue and PR share the same runtime variant — stack (sync/async), protocol, `legacy_responses` setting, topology, command, parser — trigger, supported configuration, and user outcome. A broad title, ordinary reference, `Related to` statement, or conceptual similarity is not enough. If an earlier change already resolved the concrete reported scenario, an adjacent variant (the other stack, another protocol, another topology) starts with no inherited evidence of need.\n\n### Need evidence status\n\nAssign one status before deep implementation review:\n\n- **Demonstrated**: The exact scope has a concrete supported scenario, real-path reproduction, released compatibility requirement, violated supported contract, repeated demand, or broad invariant with meaningful consequence.\n- **Plausible but unproven**: The code path is possible, but realistic reach, frequency, consequence, Redis-server behavior, or demand is missing.\n- **Already covered**: A reasonable supported workflow already satisfies the outcome.\n- **Unsupported**: The outcome is outside redis-py's contract or belongs at the Redis server, a caller-owned, or an application layer.\n\nOnly `Demonstrated` need can support a merge-worthy code recommendation. `Plausible but unproven` maps to `Needs evidence` or `Not worth completing`, even when the patch is technically correct and its remaining fixes are bounded. `Already covered` and `Unsupported` normally map to closure or a simpler non-core alternative.\n\nBefore accepting an issue or recommending a PR, record:\n\n| Question | Required evidence |\n| --- | --- |\n| What outcome is needed? | A concrete supported scenario stated without the proposed API or fix |\n| What exists today? | The closest current-release API, configuration, composition, extension point, or caller-owned solution |\n| Why is it insufficient? | An exact behavioral, compatibility, lifecycle, or operational constraint, not preference alone |\n| What are the alternatives? | The proposed patch, the strongest existing path, and at least one no-code, narrower, or better-layer design |\n| Why add a contract? | Practical benefit sufficient to justify public surface, runtime branches, cross-stack/protocol tests, documentation, and long-term maintenance |\n\nClassify the result:\n\n- **Capability gap**: a supported, realistic outcome cannot be achieved with current functionality. Code may be warranted.\n- **Ergonomics or discoverability gap**: the outcome is already possible, but the supported route is confusing or unnecessarily difficult. Prefer documentation, validation, or a narrowly justified convenience improvement.\n- **Unsupported use case**: the desired outcome lies outside redis-py's contract or belongs at the Redis server, application, or other caller-owned layer. Do not expand the core API merely to make it possible.\n- **No demonstrated gap**: no concrete scenario proves that existing functionality is insufficient. Request evidence or close rather than designing from the proposed mechanism.\n- **Defect in supported behavior**: the existing path violates a documented or established correctness, security, compatibility, or lifecycle contract (including Redis command semantics, error/exception types, and sync/async parity). A workaround may affect priority or solution shape, but does not erase the defect.\n\nPassing tests for a new implementation establish feasibility and correctness, not need. A mock connection, `FakeRedis`/monkeypatched response, manually constructed RESP payload, mock, or synthetic fixture does not establish realistic Redis-server behavior, user reach, frequency, consequence, or demand. API symmetry and parity with an adjacent command, protocol, topology, or the other stack are design arguments, not need evidence. A technically coherent patch can still be `Not worth completing` when the motivating scenario is hypothetical, already supported, or better solved elsewhere.\n\nUse the counterfactual maintainer test: if the PR did not already exist, would maintainers choose to file and implement the same work from the available evidence? Contributor effort lowers implementation cost but does not create product need or remove permanent maintenance cost.\n\nWhen the need is not `Demonstrated`, inspect implementation only far enough to estimate contract, risk, and maintenance cost. Do not convert patch defects, missing tests, or documentation gaps into a request-changes disposition; those become merge blockers only after the need gate passes.\n\n## Issue Disposition\n\nChoose one:\n\n- **Prioritize**: confirmed moderate-or-higher impact or important invariant with no safe workaround.\n- **Accept, low priority**: confirmed low impact, existing supported functionality is insufficient or defective for the demonstrated scenario, and a proportionate fix is plausible.\n- **Narrow scope**: valid core, overstated paths or expected behavior.\n- **Needs evidence**: plausible but missing a supported reproduction, contract basis, or concrete scenario showing why existing functionality is insufficient.\n- **Close**: duplicate, unsupported, unreachable, contradicted, no-op, already addressed by a reasonable supported path, or not worth permanent complexity.\n\nAsk only for evidence that could change the disposition.\n\n## PR Quality and Value\n\nAssess independently:\n\n1. **Need**: same-scope evidence demonstrates a concrete unmet user outcome or defect in supported behavior, and the closest supported capability cannot reasonably satisfy the scenario as-is. Do not inherit evidence from an adjacent variant (other stack, protocol, topology) or already-fixed scenario.\n2. **Correctness**: the fix covers the claim and meaningful boundaries, and preserves Redis command semantics and error/exception types.\n3. **Placement**: the invariant is enforced once at the owning layer (connection, pool, parser, callback, command mixin) instead of duplicating existing functionality, patching locally, or moving server- or application-owned policy into the client.\n4. **Consistency**: equivalent sync/async, RESP2/RESP3, `legacy_responses`, standalone/cluster/sentinel, parser, and serialization paths stay aligned. Sync/async parity is a repository requirement, not optional polish.\n5. **Tests**: a regression test fails on base, passes on head, and asserts the non-happy-path value/state; protocol-sensitive changes run under both `--protocol 2` and `--protocol 3`. When shared state crosses an asynchronous boundary, tests control relevant completion orders and assert the surviving operation's behavior and final resource state.\n6. **Compatibility**: released public API (signatures, argument names, defaults, return types), error/exception types, wire-protocol handling, packaging extras, and connection/config behavior are preserved or intentionally migrated against the latest release.\n7. **Proportionality**: public surface and complexity match impact.\n8. **Completion cost**: remaining code, tests, docs, design, the missing sync or async mirror, and conflict work is bounded enough to justify attention.\n\nA PR can be correct but not merge-worthy because the need is negligible, the outcome is already supported through a reasonable existing mechanism, the real path is unchanged, equivalent paths remain inconsistent, the abstraction costs more than the benefit, or a simpler design exists at another layer.\n\nDo not use implementation correctness, bounded remaining work, CI status, or contributor effort to upgrade a need that is only `Plausible but unproven`. Merge-worthiness is gated by demonstrated need, not by how close the patch is to completion.\n\nKeep issue severity separate from `Patch risk`. A patch-induced regression, compatibility break, connection/resource leak, lock-release-across-await hazard, or maintenance hazard does not make the underlying issue more severe.\n\n## Documentation Threshold\n\nMake docs merge-blocking only when:\n\n- Existing docs or docstrings become materially false, unsafe, or misleading.\n- Safe/correct use depends on a non-obvious constraint, migration, compatibility boundary, or operational warning.\n- Repository policy, accepted scope, or an explicit maintainer decision requires docs in the same PR.\n- The feature is practically unusable or undiscoverable without a user-facing entrypoint and generated/API discovery is insufficient.\n\nKeep optional discoverability/completeness non-blocking. Do not downgrade a code recommendation solely for optional docs or include optional docs in a required-action paragraph.\n\n## Lifecycle and Failure Paths\n\nApply this section when a change adds validation, fail-fast behavior, cleanup, retry, interruption, background work, connection handling, or concurrency.\n\n- Identify the earliest point where all dynamic inputs required for a correct decision exist.\n- List side effects before and after that point: pool connections, sockets, coroutines/tasks, locks, caches, state, persistence, and telemetry/observability spans.\n- Exercise failure during construction, connection, validation, command execution, persistence, and teardown where those phases exist.\n- Confirm normal teardown is actually entered. If construction/connect fails, verify explicit cleanup (connection returned to or evicted from the pool, lock released, task cancelled).\n- Prefer validation after dynamic configuration is resolved but before avoidable side effects begin.\n- Require a regression test for any connection, coroutine, lock, socket, or state that can remain after failure.\n\n## Concurrency and Cleanup Ownership\n\nApply this section before a positive assessment whenever lifecycle work crosses an `await`, callback, event, deferred completion, retry, reconnect, cancellation, or shared resource boundary. Sequential correctness is insufficient because the patch can improve isolated cleanup while introducing cross-attempt teardown. In redis-py this most often involves connection-pool acquire/release, async locks held across awaits, pub/sub reconnect, cluster slot-map updates, and multidb failover.\n\nUse a two-operation interleaving matrix during desk review:\n\n| Ordering | Required question |\n| --- | --- |\n| `A pending -> B starts -> A fails -> B succeeds` | Can A's cleanup remove or revert anything B needs (a pool connection, a slot map, a lock)? |\n| `A pending -> B starts -> B fails -> A succeeds` | Can B's cleanup leave A successful but non-functional? |\n| `A succeeds -> B starts -> stale A completion` | Can stale A overwrite B's newer state or generation (slot map, reconnect, config)? |\n| setup -> close/cancel -> late completion | Can late work resurrect connections, listeners, state, or tasks after teardown? |\n\nFor each ordering:\n\n- Identify the resource owner before and after every suspension point.\n- Distinguish per-attempt resources from shared pool, connection, slot-map, cache, or pub/sub state.\n- Require cleanup to carry an ownership token, generation, identity check, serialization guarantee, or another invariant that prevents cross-attempt disposal. Keep async locks held across the awaits inside a pool mutation so other tasks cannot observe in-between state.\n- Compare base and head on the survivor invariant. Fewer duplicates do not justify losing the only active connection, task, or state update.\n- Require a controlled interleaving test when the ordering is reachable. The test must assert both the failing operation and the surviving operation's observable behavior after all completions settle.\n\nAn unscoped `finally`, `except`, close handler, cancellation callback, or rollback that mutates shared state after a suspension point is merge-blocking when another operation can still own or use that state.\n\n## Better-Alternative Prompts\n\nStart with the strongest existing supported path, then test at least one additional alternative against the proposed patch. Do not complete a positive review without this comparison.\n\n- Can the requested outcome already be achieved through configuration, composition, client/pool options, callbacks, `response_callbacks`, a retry/backoff strategy, an existing command mixin, or caller-owned code?\n- If the existing route is awkward, is the problem discoverability or ergonomics rather than missing capability?\n- What happens with no code change?\n- Can input validation or an existing helper (`redis/commands/helpers.py`) enforce the invariant earlier?\n- Can the fix be limited to the supported failing path?\n- Would a clearer error/exception or documentation prevent misuse without runtime complexity?\n- Can a failing test reveal a smaller correct change?\n- Is a new public option compensating for an internal ownership problem (pool, lock, connection lifecycle)?\n- Can the same result be achieved in the parser, response callback, or state owner instead of every caller?\n- Is the proposed core behavior actually Redis-server- or application-specific policy that belongs at another layer?\n\n## Competing Pull Requests\n\nRequire an explicit issue link, same reproduction, same violated invariant, or materially overlapping runtime path (command, stack, protocol, topology) before grouping candidates.\n\n| Criterion | Question |\n| --- | --- |\n| Need | Does a concrete user outcome remain unmet, or supported behavior remain defective, after tracing existing functionality? |\n| Existing capability | Could every candidate be avoided by configuration, composition, an extension point, or a better caller-owned solution? |\n| Coverage | Whole confirmed issue, useful subset, or adjacent problem? Both stacks or only one? |\n| Correctness | Real path and meaningful boundaries? |\n| Placement | Owning shared layer? |\n| Tests | Base failure reproduced, protocols/topologies covered, and approaches distinguished? |\n| Compatibility | Released APIs, signatures, error types, protocols, packaging, connection/config? |\n| Complexity | Permanent branches, abstractions, configuration, coupling? |\n| Readiness | Mergeable now or bounded focused work (including missing sync/async mirror)? |\n| Reuse | Exact tests or ideas worth transferring? |\n\nChoose one portfolio action:\n\n- **Prefer one PR**\n- **Prefer one after focused changes**\n- **Combine selectively** into a named destination PR\n- **Replace all** with a simpler/coherent implementation\n- **Merge none**\n\nDo not issue independent approvals for overlapping candidates. State the action for every active PR.\n\n## Maintainer Comments\n\nWrite drafts in English, in chat, as copy-paste-ready text for the user to send themselves. Per the repository owner's standing rules, do not post them to GitHub or any external service, and do not impersonate the user. Produce one when recommending closure, more evidence, focused changes, superseding, or choosing among competing PRs.\n\nKeep it polite, direct, complete, and usually 60-160 words in one to three short paragraphs:\n\n1. Acknowledge the contribution/report.\n2. State the decision with decisive technical evidence.\n3. Give the exact next action or reconsideration condition.\n\nDo not include internal severity labels, speculate about authorship/intent, repeat the full review, or soften the requested action until it is unclear.\n\n### Close\n\n```text\nThanks for taking the time to investigate this. I traced the reported case through <path or behavior>, and <decisive finding>. In the supported path, <practical result>, so the added complexity is not justified by the demonstrated impact.\n\nI am going to close this <issue/PR>. If you can provide <specific reproduction or evidence that would change the decision>, we can revisit the underlying problem with that narrower scope.\n```\n\n### Request Changes\n\n```text\nThanks for the contribution. The underlying issue is valid, and this approach is directionally reasonable. Before we can merge it, please address the following points: <bounded required changes, e.g., mirror the change in the async stack under redis/asyncio/, add RESP2/RESP3 coverage, add a regression test>.\n\nThese changes are needed because <contract, lifecycle, compatibility, sync/async parity, or test reason>. Once they are covered with a regression test that fails on the base and passes on the updated branch, the PR should be ready for another review.\n```\n\nAdapt these templates to evidence. Do not use them as filler.\n\n### Existing Capability or Better Alternative\n\n```text\nThanks for the contribution. I traced the underlying use case through <existing API or workflow>, which already supports <desired outcome and relevant limits>. The proposed change adds <new contract or complexity>, but the issue does not demonstrate a concrete supported case that the existing approach cannot handle.\n\nI am going to close this <issue/PR> for now. If you can provide <specific scenario showing the existing approach is insufficient>, we can revisit the unmet need and choose the narrowest appropriate design from that evidence.\n```\n\n## Compact Report Variants\n\nUse `Maintainer decision` for a concluded review. Use `Preliminary assessment` when a desk review is tentatively positive but a decision-relevant runtime concern remains. `Verdict` is intentionally avoided in the report headings because it does not communicate whether the result is provisional or final.\n\n### Runtime Approval Gate\n\n```markdown\n## Preliminary assessment\n\n<Tentative issue or PR assessment based on desk review only.>\n\n## Static evidence\n\n- <decisive code-path or test-inspection evidence>\n- <what remains uncertain at runtime>\n\n## Proposed runtime probe\n\n- Concern: <the uncertainty that could change the decision>\n- Probe: <smallest exact execution path; note protocol/topology and whether `invoke devenv` or external infra is required>\n- Control: <base, release, or known-good comparison when relevant>\n- Scope: <local-only or any live-service, Docker, cost, mutation, or cleanup implications>\n\n## Approval request\n\n<Ask whether to run this exact probe. Do not present a final positive recommendation yet.>\n```\n\n### Issue\n\n```markdown\n## Maintainer decision\n\n<Real/partial/unproven/contradicted, severity, and disposition.>\n\n## Evidence\n\n- <decisive evidence>\n- <scope or uncertainty (which stacks/protocols/topologies)>\n\n## Existing capability and alternatives\n\n<Closest supported path, why it is or is not sufficient, and the preferred design alternative.>\n\n## Recommendation\n\n<Prioritize, accept low priority, narrow, request evidence, or close.>\n\n## Maintainer comment draft\n\n<Include for closure or an evidence request.>\n```\n\n### Pull Request\n\n```markdown\n## Maintainer decision\n\n<Need, practical impact, and merge-worthiness.>\n\n- Need evidence: <Demonstrated / Plausible but unproven / Already covered / Unsupported>\n- Code recommendation: <code disposition>\n- Repository readiness: <one allowed status; only when useful for a merge-worthy recommendation>\n\n## Evidence\n\n- <runtime/code-path result>\n- <test/compatibility result>\n\n## Existing capability and alternatives\n\n<Closest supported path, why the demonstrated scenario cannot use it or remains defective, and why this patch is preferable to no code change or a narrower design.>\n\n## Issue impact\n\n- Validity: <claim validity>\n- Severity: <underlying issue severity>\n- Reach: <realistic reach: stacks, protocols, topologies, versions>\n\n## Patch risk\n\n<Only meaningful patch-induced risk, including sync/async parity and resource-ownership hazards.>\n\n## PR quality\n\n- Solution fit: <assessment>\n- Tests: <assessment, including protocol/topology coverage>\n- Remaining effort: <bounded/unbounded and why; note any missing sync or async mirror>\n\n## Recommendation\n\n<Merge, focused changes, simpler replacement, or close.>\n\n## Maintainer comment draft\n\n<Only when closure, evidence, or changes should be requested.>\n```\n\n### Competing Pull Requests\n\n```markdown\n## Maintainer decision\n\n<Issue validity, severity, and preferred implementation path.>\n\n## Open PR comparison\n\n| PR   | Approach | Correctness | Tests | Compatibility/complexity | Readiness |\n| ---- | -------- | ----------- | ----- | ------------------------ | --------- |\n| #... | ...      | ...         | ...   | ...                      | ...       |\n\n## Recommendation\n\n<Select one, request focused changes, combine exact pieces, replace all, or merge none.> <State the action for every other active candidate.>\n\n## Maintainer comment drafts\n\n<One draft for each PR that should be closed, changed, or superseded.>\n```\n","isInternal":false,"tokens":5253,"sizeBytes":26702},{"name":"SKILL.md","path":".agents/skills/maintainer-review/SKILL.md","rawUrl":"https://raw.githubusercontent.com/redis/redis-py/HEAD/.agents/skills/maintainer-review/SKILL.md","title":"maintainer-review","category":"anthropic-skill","format":"markdown","content":"---\nname: maintainer-review\ndescription: Review a GitHub issue or pull request URL as a redis-py maintainer, with a staged assessment of whether the claim is real, practically important, already solvable with supported functionality, correctly scoped, better served by another design, and worth maintainer and contributor effort. Use when assessing issue validity or severity, deciding whether an issue should be prioritized or closed, determining whether a requested feature represents an unmet need rather than a discoverability or usage gap, judging whether a PR is worth bringing to mergeable quality, comparing open PRs or alternative designs, separating code quality from repository readiness, or drafting a concise maintainer assessment. When closure, additional evidence, or code changes should be requested, also produce a polite, concise, complete, copy-paste-ready maintainer comment.\n---\n\n# Maintainer Review\n\n## Objective\n\nMake a maintainer decision, not a generic diff summary. Separate these questions:\n\n1. Is the claimed behavior real?\n2. What user outcome or constraint exists independently of the reporter's proposed API or fix?\n3. Can supported functionality already achieve that outcome with reasonable composition or configuration?\n4. If a gap remains, is the proposed solution the best design and implementation layer?\n5. Can supported users plausibly reach the gap, and what happens when they do?\n6. Is it important enough to act on now?\n7. If this PR did not already exist, would maintainers choose to open and implement the same work?\n8. For a PR, is this solution worth merging and maintaining?\n9. Can overlapping or stale operations corrupt shared state or clean up resources owned by surviving work?\n10. If competing PRs exist, which single implementation path should maintainers pursue?\n11. What concise maintainer message should communicate closure, an evidence request, or required changes?\n\nTreat an issue's requested keyword argument, callback, flag, class, or implementation strategy as a proposed mechanism, not as the accepted requirement. Do not begin by asking how to implement it. First establish either a concrete unmet user outcome or a violated supported contract — the public API surface, documented Redis command semantics, error/exception types, or wire-protocol behavior — then prove that the proposed mechanism is better than the available alternatives.\n\nLead with the current review state. Use `Preliminary assessment` while runtime approval or evidence is pending, and `Maintainer decision` only when the review can be concluded. Use the diff, issue narrative, and contributor effort as evidence, not as proxies for impact.\n\n## redis-py context to hold throughout\n\nThis library ships two parallel stacks (sync under `redis/`, async under `redis/asyncio/`) that must stay aligned, and exercises behavior across several orthogonal axes. Keep these as the runtime-variant matrix whenever you reason about reach, consistency, or \"the exact same scenario\":\n\n- **Sync vs async**: every behavior question has a sync (`redis/...`) and async (`redis/asyncio/...`) variant; tests mirror under `tests/` and `tests/test_asyncio/`. A defect or fix on one side usually has a counterpart on the other (`specs/sync_async_deduplication_analysis.md`).\n- **Wire protocol vs response shape**: `protocol=2|3` chooses RESP2/RESP3 on the wire; `legacy_responses=True|False` chooses the Python response shape independently. Both axes are exercised in CI (`invoke run-test-matrix`; see `specs/unified_responses_migration_guide.md`). A claim or fix may behave differently across these four combinations.\n- **Topology**: standalone, cluster (`redis/cluster.py`), and sentinel (`redis/sentinel.py`) have distinct routing, connection, and failure behavior. Cluster adds slot mapping, MOVED/ASK redirection, metadata-driven replica routing (`MetadataResolver.is_replica_safe`), and `RequestPolicy`/`ResponsePolicy` resolution.\n- **Parser backend**: pure-Python (`redis/_parsers/resp2.py`, `resp3.py`) vs optional C-accelerated `hiredis`. Behavior must match across both.\n- **Public API compatibility**: signatures, argument names, defaults, return types, error/exception types, and `response_callbacks` shaping are contracts. The compatibility boundary is the latest released version on PyPI, not unreleased branch churn.\n\n## Workflow\n\n### 1. Establish the exact remote target\n\n- Accept a GitHub issue or PR URL as the primary input. Resolve owner, repository, item type, and number before reviewing it.\n- For an issue, read the full report, comments, reproduction, environment (redis-py version, Python version, Redis server version, topology, protocol, hiredis present or not), linked material, and maintainer responses.\n- For a PR, inspect the current remote base and head, full patch, commit history when relevant, tests, linked issue, and review discussion. Do not substitute the current local checkout for the remote change.\n- State the claim in one falsifiable sentence. Separate the observed symptom from the proposed cause or fix.\n- Identify the latest released boundary (the current redis-py release on PyPI) when compatibility or regression claims matter.\n- Verify whether linked evidence matches the PR's exact runtime variant — sync or async, RESP2 or RESP3, `legacy_responses` setting, standalone/cluster/sentinel topology, parser backend — triggering condition, and user outcome. A generic issue title, conceptual similarity, or wording such as `Related to` does not transfer evidence of need to an adjacent variant. If the reported scenario has already been fixed, treat additional variants (the other stack, another protocol, another topology) as new needs requiring their own evidence.\n\nUse read-only GitHub access. Per the repository owner's standing rule, never run `gh` (or any GitHub API call) that writes — no comments, labels, reviews, approvals, branch changes, pushes, or merges — unless the user explicitly authorizes that specific action in the same turn. Read-only inspection (`gh ... list/view/diff/status`, GET-only `gh api`) is fine. A review never authorizes remote writes. Note: `gh` may fail with a TLS error inside the command sandbox; run it with the sandbox disabled when needed.\n\n### 2. Establish the unmet need and challenge the proposed solution\n\nComplete this pass before deeply evaluating a proposed implementation and before any positive issue or PR assessment.\n\nFirst assign one `Need evidence` status:\n\n- **Demonstrated**: The exact scope has a concrete supported scenario, a real-path reproduction, a released compatibility requirement, a violated supported contract (public API, Redis command semantics, error/exception type, or wire-protocol behavior), repeated demand, or a broad invariant with a meaningful consequence.\n- **Plausible but unproven**: The path can exist, but realistic Redis-server behavior, user reach, frequency, consequence, or demand is not established.\n- **Already covered**: A reasonable supported workflow already satisfies the outcome.\n- **Unsupported**: The outcome belongs outside redis-py's contract or at the Redis server, a caller-owned layer, or an application concern.\n\nOnly `Demonstrated` need may receive `Merge-worthy as-is` or `Merge-worthy after focused changes`. For `Plausible but unproven`, prefer `Needs evidence` or `Not worth completing`; for `Already covered` or `Unsupported`, prefer closure or the relevant simpler alternative.\n\n1. Restate the desired user outcome without naming the requested API, class, file, option, or implementation. Separate the actual constraint from the reporter's preferred mechanism.\n2. Trace the closest supported ways to achieve that outcome in the current release across the relevant stack. Inspect the owning code path, public API, tests, and relevant docs rather than assuming that an unfamiliar capability is missing. Consider configuration, composition, client/connection-pool options, callbacks, `response_callbacks`, retry/backoff strategies, existing command mixins, and caller-owned code.\n3. Determine whether the report shows a capability gap, an ergonomics or discoverability gap, an unsupported use case, or no demonstrated gap. A more convenient spelling is not automatically a missing capability.\n4. Compare the proposed solution against the strongest existing approach and at least one better-design candidate: no code change, clearer documentation or validation, a narrower fix, reuse of an existing abstraction or helper (`redis/commands/helpers.py`, existing parser/callback hooks), or enforcement at a more coherent shared boundary.\n5. For each viable approach, compare whether it satisfies the concrete scenario, what new public or internal contract it creates, cross-path consistency (sync/async, protocol, topology), compatibility, and permanent maintenance cost.\n\nDo not treat a test proving that new code can work as evidence that the feature is needed. A mock connection, a `FakeRedis`/monkeypatched parser response, a manually constructed RESP payload, a mock, or a new regression test can establish code-path reachability and implementation correctness; it does not by itself establish realistic Redis-server behavior, user reach, frequency, practical consequence, or demand.\n\nAPI symmetry, naming consistency, and parity with an adjacent command, topology, protocol, or the other stack are design arguments, not evidence of need. Parity may justify work when it removes existing complexity or enforces a broad demonstrated invariant (and sync/async parity is itself a repository invariant), but adding branches, tests, documentation, or public behavior requires independent practical justification.\n\nIf the need is not `Demonstrated`, inspect the patch only far enough to understand its contract, risk, and maintenance cost. Do not turn implementation defects, missing tests, or documentation gaps into a request-changes recommendation, because those questions become merge-blocking only after the need gate passes. If the report provides no concrete scenario, the existing functionality appears sufficient, or the requested mechanism solves only a hypothetical convenience problem, prefer `Needs evidence`, `Close`, `Supersede with a simpler alternative`, or `Not worth completing` over designing the requested feature on the reporter's behalf.\n\nAn existing workaround may change priority or solution shape, but it does not by itself erase a demonstrated correctness, security, compatibility, or lifecycle defect in supported behavior. Evaluate both the unmet outcome and the violated contract.\n\n### 3. Discover competing open PRs proportionally\n\nDo this before deeply evaluating a specified PR. A PR URL selects the starting point, not necessarily the entire comparison set.\n\n- Determine the primary issue from explicit closing keywords, linked issues, timeline/development links, PR body/comments, and the reproduced symptom. State when association is inferred.\n- When an issue is explicitly linked, enumerate all open PRs that address it through cross-references, closing keywords, and development links. Include drafts but label them.\n- When no issue is linked, run a bounded duplicate search using the strongest signals from the title, reproduction, violated invariant, and runtime path (command, topology, protocol, sync/async).\n- Exclude closed/merged PRs from the active comparison set while using them as history when relevant.\n- Require a shared issue, symptom, violated invariant, or materially overlapping path. A shared area label is not enough.\n- If repository access cannot establish completeness, say so instead of claiming every candidate was found.\n\nThe repository also has an `analyze-open-prs` skill for triaging the whole PR queue; use this skill for the focused single-target comparison and that one when the user wants a queue-wide sweep. Compare candidates on need coverage, runtime correctness, placement, tests, compatibility, complexity, readiness, remaining maintainer work, and reusable pieces. Prefer the best maintainable solution, not the first or smallest diff by default.\n\n### 4. Use a two-stage evidence flow\n\nAlways begin with a desk review. Inspect the real runtime path before judging a change as trivial or meaningful. Check callers, public exports, both stacks (sync and async), equivalent protocol/topology/parser paths, persistence and connection lifecycle, cleanup, and focused tests. Inspecting test code is part of the desk review; executing tests, imports, examples, reproductions, benchmarks, or live Redis calls is a runtime probe.\n\nTreat `references/evaluation-framework.md` and the repository's own design notes (`.agent/`, `specs/`, `CLAUDE.md`) as read-only background during review. Verify every current claim against the remote change, current source, tests, docs, release boundary, and runtime evidence. Do not infer issue status or PR correctness from a reference. Recommend a separate documentation/spec-maintenance change when review reveals a durable invariant unless the user explicitly includes that update.\n\nUse this evidence order across the two stages:\n\n1. Trace the closest existing supported capabilities and determine whether they already satisfy the underlying user outcome.\n2. Inspect existing tests and complete the code-path trace, including the mandatory interleaving and ownership pass when triggered, without executing code.\n3. With explicit user approval, run a focused local reproduction of the exact claim when the desk-review rules below require it.\n4. A comparison with the latest release, base branch, or another known-good control.\n5. A broader runtime matrix only when the maintainer decision remains uncertain and the user approves it.\n\n#### Stage 1: desk review\n\nProduce an initial result from static evidence before running code:\n\n##### Mandatory unmet-need and design pass\n\nBefore a positive assessment, complete the pass in step 2 and be able to state all of the following from concrete evidence:\n\n1. The user outcome that current supported behavior cannot achieve, or the supported contract that the reported defect violates.\n2. The closest existing API or composition path and the exact reason it is insufficient.\n3. Why the proposed behavior belongs at the chosen abstraction layer instead of a caller, application, the Redis server, a response callback, validation, documentation, or an existing extension point.\n4. Why the proposed permanent contract is better than no code change and the strongest narrower alternative.\n5. What real scenario, compatibility requirement, violated invariant, or repeated demand justifies the maintenance surface.\n6. Whether maintainers would choose to pursue the same work if no contributor had already supplied a patch.\n\nIf any answer is missing and could change whether code should exist at all, do not call the issue actionable or the PR merge-worthy. Request only the evidence needed to distinguish a genuine capability gap or contract violation from a usage, discoverability, or solution-design problem. This is a product and architecture evidence gap, not a runtime-probe trigger by itself.\n\n##### Mandatory sync/async parity pass\n\nredis-py requires the sync and async stacks to stay aligned. Before any positive assessment of a PR that changes runtime behavior, confirm:\n\n1. Whether the change touches `redis/` (sync) without a mirrored change under `redis/asyncio/` (or vice versa), and whether that asymmetry is intended.\n2. Whether tests were added or updated on both sides (`tests/` and `tests/test_asyncio/`) when both stacks are affected.\n3. Whether type hints, overloads, signatures, defaults, and return types remain identical across stacks (`.agent/sync_async_type_hints_overload_guide.md`).\n\nA behavior fix applied to only one stack is normally `Merge-worthy after focused changes` at best, with the missing mirror called out as required work — unless the divergence is deliberate and documented.\n\n##### Mandatory interleaving and ownership pass\n\nRun this pass before any positive PR assessment when a patch adds, removes, or reorders cleanup, retry, reconnect, cancellation, listeners, shared coroutines/tasks, sockets or connections, pool state, state flags, or mutable state across an `await`, callback, event, or deferred completion. In redis-py this most often means connection-pool acquire/release, async locks held across awaits, pub/sub reconnect, retry/backoff, cluster MOVED/ASK re-slotting, maintenance-notification handlers, and multidb health-check/failover.\n\n1. Name each shared resource or state value and the operation that owns it. Include pool connections, async locks, pub/sub state, in-flight commands, cluster slot maps, coroutines/tasks, sockets, caches, state flags, persistence, and telemetry/observability spans.\n2. Trace at least two overlapping operations, `A` and `B`, across every suspension or re-entry point. Check `A pending -> B starts -> A fails -> B succeeds`, `A pending -> B starts -> B fails -> A succeeds`, disconnect/cancellation between setup and completion, and a stale completion arriving after newer work.\n3. For every cleanup or rollback, identify the exact attempt and resource generation it is allowed to dispose. Treat unconditional cleanup after a suspension point as a regression candidate until the code proves it cannot tear down newer or surviving work. In particular, an async lock released across an `await` inside a pool mutation can let another task observe in-between state — hold the lock across the awaits.\n4. Compare base and head for the survivor invariant. Replacing duplicated work with a closed connection, a returned-then-reused pool slot, reverted slot-map state, or a rejected surviving coroutine is a regression, not a successful cleanup.\n5. Inspect tests for controlled interleavings using events, deferred futures, or callbacks. Require assertions about the surviving operation's observable behavior and final resource state (pool size, connection validity, lock state), not only counts or individual exception results.\n\nDo not mark a concurrency-sensitive patch `Merge-worthy as-is` merely because sequential reconnect, retry, failure, and disconnect tests pass. If the code trace proves an unsafe interleaving, conclude from static evidence and request a focused fix and regression test. If ownership remains ambiguous, keep the result preliminary and request approval for the smallest decisive runtime probe.\n\n- If the claim or PR is decisively negative from a complete reachable code-path trace, conclude the review without a runtime probe. Examples include an impossible or unsupported path, duplicated existing handling, a demonstrated no-op, a direct compatibility break, or a clearly wrong abstraction. Do not call an ambiguous result negative merely to avoid a probe.\n- If the initial result is positive and there is no unresolved runtime concern, and any triggered parity and interleaving passes are complete, the desk review may be sufficient for a final maintainer decision. Do not run a probe only to restate evidence that cannot plausibly change the decision.\n- If the initial result is positive but there is any unresolved runtime concern that could plausibly change claim validity, severity, merge-worthiness, required changes, or the preferred competing PR, stop before executing code. Report a `Preliminary assessment`, name the concern, propose the smallest decisive probe and control, and ask the user for approval to run it.\n- A purely stylistic, documentation, CI-status, or repository-readiness concern does not trigger a runtime probe unless it masks a runtime question.\n\nDo not issue a definitive positive maintainer decision while a decision-relevant runtime concern remains unresolved. If the user declines the probe, keep the result preliminary and state the exact confidence limitation.\n\n#### Stage 2: approved runtime probe\n\nAfter explicit approval, run only the smallest probe needed to resolve the stated concern. Exercise the real public or internal path and include a base, release, or known-good control when relevant. Do not stop at a happy-path smoke check when failure behavior determines the decision. Return to the user for separate approval before expanding materially beyond the approved probe.\n\nFor latency, timeout, buffering, backpressure, or cleanup, measure an observable elapsed-time or state transition where feasible. Do not assume that a mocked unit test exercises real scheduling or Redis-server behavior. Prefer a local probe first; use an approval-gated probe against a live or external Redis deployment only when local evidence cannot settle the decision.\n\nThe repository's local runtime tools are the test harness, not a separate probe skill: `invoke devenv` brings up the docker test environment; `invoke tests` / `invoke standalone-tests` / `invoke cluster-tests` run suites; targeted runs use `pytest ... --protocol 2|3` and topology markers (`onlycluster`, `onlynoncluster`). The repository also has `verify` and `run` skills for driving the library end-to-end. Honor any live-service, Docker, cost, cleanup, and reporting implications when proposing a probe, and call out when a probe needs `invoke devenv` or external infrastructure (scenario tests). Ordinary maintainer review must not depend on spinning up external infrastructure.\n\nFor validation, cleanup, retries, interruption, background work, or concurrency:\n\n- Identify the earliest correct decision point after dynamic inputs are available.\n- List resources acquired before and after it: pool connections, sockets, coroutines/tasks, locks, caches, state mutations, and telemetry.\n- Exercise failure during construction, connection, validation, command execution, and cleanup where applicable.\n- Verify explicit cleanup when failure occurs before normal teardown (connection returned to or removed from the pool, lock released, task cancelled).\n- Require a negative-path test when a connection, coroutine, lock, socket, or state can remain after failure.\n\nStop when additional evidence is unlikely to change validity, severity, or maintainer action.\n\n### 5. Calibrate validity and impact\n\nRead [the evaluation framework](references/evaluation-framework.md) when validity, severity, or merge value is not immediately clear.\n\nAssess claim validity, realistic reach (which stacks, protocols, topologies, and versions), consequence, breadth, frequency, recoverability, compatibility, and severity. Keep observed facts separate from inference and name missing evidence that could change the result.\n\nReport the `Need evidence` status before classifying the need as a capability gap, ergonomics or discoverability gap, unsupported use case, no demonstrated gap, or a defect in supported behavior. Do not assign practical impact to the absence of the requested mechanism when an existing supported workflow already produces the requested outcome. Do not infer practical importance merely from reachability, API asymmetry, or a technically successful patch.\n\nFor a PR, make `Severity` describe the underlying issue or user need. Report patch-induced regression, compatibility, lifecycle, or maintenance risk separately as `Patch risk`.\n\nDo not speculate about AI authorship or contributor intent. Identify weak reports through objective evidence: no reproduction, unsupported input, impossible path, duplicated handling, a test that does not exercise the claim, or a fix that is a runtime no-op.\n\n### 6. Apply the maintainer-effort test\n\nUse one code recommendation:\n\n- **Merge-worthy as-is**: real need, sound placement, proportionate scope, adequate tests, and sync/async parity maintained.\n- **Merge-worthy after focused changes**: real need and viable direction with bounded corrections (which may include adding the missing sync or async mirror, RESP2/RESP3 coverage, or a regression test).\n- **Supersede with a simpler alternative**: real need, but a smaller or more coherent fix is preferable.\n- **Not worth completing**: negligible/unsupported impact, no-op behavior, wrong abstraction, or excessive completion cost.\n\n`Merge-worthy as-is` and `Merge-worthy after focused changes` are invalid unless `Need evidence` is `Demonstrated`. A bounded set of implementation fixes cannot promote a `Plausible but unproven` need into a merge-worthy recommendation.\n\nFor merge-worthy recommendations, use one repository-readiness status when useful:\n\n- **Ready**\n- **CI or review pending**\n- **Rebase or conflict resolution required**\n- **Blocked**\n\nOmit readiness for supersede/not-worth-completing recommendations; CI does not change those code decisions. Do not downgrade sound code only because CI is pending, and do not call a PR ready when semantic changes remain (including a missing sync/async mirror or untested protocol/topology).\n\nFor competing PRs, make one portfolio recommendation: choose one, choose one after focused changes, combine exact pieces into one destination, replace all with a simpler approach, or merge none. State what should happen to every active candidate.\n\nAlways compare the proposed patch with the strongest existing supported approach and at least one alternative: no code change, validation or documentation, a narrower fix, reuse of an existing helper, or a different layer that enforces the invariant consistently. A review is incomplete if it establishes only that the patch works without establishing why the current product cannot meet the underlying need or violates a supported contract, and why this design is preferable.\n\n### 7. Report the decision and action\n\nChoose the assessment language from the current user request and governing repository instructions. Per the repository owner's standing rules, deliver review findings in chat, lead with findings ordered by severity with file/line references, and do not post comments, approvals, or change requests to GitHub or any external service. Maintainer comment drafts are produced in chat as copy-paste-ready text, in English, for the user to send themselves.\n\nUse the matching compact report in the evaluation framework. While runtime approval is pending, use its preliminary-assessment variant and end with the approval request instead of presenting a final recommendation. Keep the report decision-oriented, put unexpected/negative evidence first, and use no more than five evidence bullets by default.\n\nFor PRs, put `Need evidence` before the code recommendation. When the need is not `Demonstrated`, lead with that result, omit repository readiness, and avoid presenting patch fixes as the primary maintainer action.\n\nWhen existing functionality or a better alternative materially affects the decision, state it explicitly in the evidence and recommendation. Name the exact supported path, what it does and does not cover, and why it is preferable. Do not bury a `Not worth completing` or `Supersede with a simpler alternative` conclusion beneath praise for implementation quality.\n\nWhen recommending closure, more evidence, focused changes, or superseding a PR, append a polite, complete, copy-paste-ready English maintainer comment. Include only merge-blocking work in its required-action paragraph. Do not produce a line-by-line review unless requested, equate passing tests with merge-worthiness, or equate a logically correct patch with practical value.\n\n## Resource\n\n- `references/evaluation-framework.md` contains the severity rubric, evidence checks, lifecycle review, issue dispositions, PR value checks, documentation threshold, competing-PR framework, maintainer-comment guidance, and compact report variants.\n","frontmatter":{"name":"maintainer-review","description":"Review a GitHub issue or pull request URL as a redis-py maintainer, with a staged assessment of whether the claim is real, practically important, already solvable with supported functionality, correctly scoped, better served by another design, and worth maintainer and contributor effort. Use when assessing issue validity or severity, deciding whether an issue should be prioritized or closed, determining whether a requested feature represents an unmet need rather than a discoverability or usage gap, judging whether a PR is worth bringing to mergeable quality, comparing open PRs or alternative designs, separating code quality from repository readiness, or drafting a concise maintainer assessment. When closure, additional evidence, or code changes should be requested, also produce a polite, concise, complete, copy-paste-ready maintainer comment."},"isInternal":false,"tokens":5343,"sizeBytes":27533},{"name":"SKILL.md","path":".agents/skills/pr-draft-summary/SKILL.md","rawUrl":"https://raw.githubusercontent.com/redis/redis-py/HEAD/.agents/skills/pr-draft-summary/SKILL.md","title":"pr-draft-summary","category":"anthropic-skill","format":"markdown","content":"---\nname: pr-draft-summary\ndescription: Produce PR-ready output for redis-py — a one-line commit message and a copy-pasteable Markdown PR description (Change summary + Test coverage, 5–6 paragraphs) covering all staged changes plus the commits already on the current branch. Trigger whenever the user's message mentions any of these keywords or phrases: \"PR\", \"pr\", \"PR description\", \"PR draft\", \"pull request\", \"the PR block\", \"commit msg\", \"commit message\", or otherwise asks for a commit message or PR write-up for their local changes.\n---\n\n# PR Draft Summary\n\n## Purpose\n\nAfter eligible work is complete, produce exactly two things for redis-py:\n\n1. A **one-line commit message** (imperative, conventional prefix preferred).\n2. A **PR description** as a single Markdown block the user can copy-paste into\n   the GitHub PR UI with all formatting preserved. Keep it to **5–6 short\n   paragraphs** that explain what the changes do.\n\nBy default, emit both as text in the chat. Write them to a `.md` file **only**\nif the user explicitly asks for a file.\n\n## Scope of Changes to Evaluate\n\nBy default, cover **staged changes** and **commits already on the current\nbranch** (relative to the PR base) — that is what the commit/PR will actually\ncontain. Include **unstaged changes** and **untracked files** only when the user\nexplicitly asks for them; note that untracked files do not appear in\n`git diff --stat`, so pick them up from `git status -sb` (the `??` entries). Use\ncommit messages as supporting context, not as a substitute for reading the\ndiffs. If the in-scope set is empty, reply briefly that no changes were detected\nand skip emitting the blocks.\n\n## Inputs to Collect Automatically (do not ask the user)\n\n- Working tree overview — `git status -sb` (one shot: branch name, ahead/behind,\n  and staged/unstaged/untracked entries, with `??` marking untracked files).\n- What changed, with line counts — `git diff --stat --cached` for staged\n  changes (stats include file names, so a separate `--name-only` is not needed);\n  read the staged diff to write the summary. Run `git diff --stat` (unstaged)\n  only when the user asks to include unstaged/untracked work.\n- PR base — the branch this change will merge into. Accept it as an input: use\n  the base branch the user names (which may be a feature/integration branch, not\n  `master`). When none is given, default to `origin/master` (fall back to\n  `master`). Then set `BASE_REF` to that branch and\n  `BASE_COMMIT=$(git merge-base \"$BASE_REF\" HEAD)`.\n- Branch commits vs the base — `git diff --stat \"${BASE_COMMIT}..HEAD\"` and\n  `git log --oneline --no-merges \"${BASE_COMMIT}..HEAD\"`.\n\nClassify the change from the touched paths: runtime (`redis/`,\n`redis/asyncio/`), tests (`tests/`, `tests/test_asyncio/`), docs/examples\n(`docs/`, `doctests/`, `benchmarks/`), build/test config (`pyproject.toml`,\n`tasks.py`, `dockers/`, …), or repo-meta (`AGENTS.md`, `CLAUDE.md`, `.agents/`,\n`.github/`).\n\n## Workflow\n\n1. Determine the PR base first: use the base branch the user provided; otherwise\n   default to `origin/master` (fall back to `master`). Compute\n   `BASE_REF`/`BASE_COMMIT`, then run the commands above. The base may be a\n   feature or integration branch when the change targets one rather than\n   `master`.\n2. Build the in-scope diff: committed branch changes + staged changes by\n   default; add unstaged and untracked changes only when the user explicitly\n   asks. Use commit messages as supporting context, not as a substitute for\n   inspecting the diff itself.\n3. Infer change type from touched paths (feature, fix, refactor, perf, or\n   docs-with-impact). Pick the commit-message prefix accordingly: `feat:`,\n   `fix:`, `refactor:`, `perf:`, `docs:`, `test:`, `build:`, `chore:`.\n4. Flag **backward-compatibility risk** in the description only when the diff\n   changes the public API (signatures, argument names, defaults, return types),\n   command semantics, error/exception types, wire-protocol (RESP2/RESP3)\n   handling, or persisted/connection config. Judge that risk against the latest\n   released version, not unreleased branch-only churn.\n5. Flag **sync/async parity** explicitly: if the change touches sync code under\n   `redis/` without a mirrored change under `redis/asyncio/` (or vice versa, and\n   likewise `tests/` vs `tests/test_asyncio/`), call it out, since this repo\n   requires both stacks to stay aligned.\n6. If the branch name matches `issue-<number>` (digits only) or an issue number\n   is otherwise known, reference\n   `https://github.com/redis/redis-py/issues/<number>` and add an auto-closing\n   line such as `Resolves #<number>.` Do not block if the issue cannot be fetched.\n7. Draft the two blocks using the format below. The PR description always has a\n   \"Change summary\" and a \"Test coverage\" section. Keep it to 5–6 short\n   paragraphs total; do not pad. In \"Test coverage\", describe the tests added or\n   updated; if none were added, say so briefly and explain why.\n8. Output both blocks as chat text by default. If — and only if — the user\n   explicitly requested a file, also write them to a `.md` file and report its\n   path.\n\n## Output Format\n\nEmit the commit message first, then the PR description. The description must be\na single fenced Markdown block so it can be copied into the GitHub UI with\nformatting intact.\n\n### Commit message\n\n```\n<single-line imperative commit message; conventional prefix preferred, e.g. fix: release async pool lock only after pool mutation>\n```\n\n### PR description\n\n````\n```markdown\n## Change summary\n\n<3–4 paragraphs of prose, starting with \"This pull request resolves/updates/adds ...\"\nusing a verb that matches the change. Explain what the change does and the\nbackground — for bugs, the symptom/repro; for features, what is needed and why —\nthen the concrete changes: the main files/areas touched, the approach, and notable\nedge cases. Call out public-API or backward-compatibility impact and any sync/async\nparity considerations here. Bullets are allowed.>\n\n## Test coverage\n\n<1–2 paragraphs (bullets allowed) describing the tests added or updated and what\nbehavior they exercise (sync and async, edge/failure cases). If the change is not\ntestable or no tests were added, say so briefly and explain why.>\n\nResolves #<number>.   <!-- include only when an issue number is known -->\n```\n````\n\nKeep it tight — 5–6 paragraphs total across both sections, no redundant prose\naround the blocks, and no repeating the same detail in both sections.\n","isInternal":false,"tokens":1588,"sizeBytes":6509},{"name":"SKILL.md","path":".agents/skills/prepare-for-takeover/SKILL.md","rawUrl":"https://raw.githubusercontent.com/redis/redis-py/HEAD/.agents/skills/prepare-for-takeover/SKILL.md","title":"prepare-for-takeover","category":"anthropic-skill","format":"markdown","content":"---\nname: prepare-for-takeover\ndescription: Prepare copy-paste-ready maintainer comments that mark stalled but valuable redis-py PRs as `open-for-takeover`, inviting the original author or any other contributor to finish the work. Each comment summarizes what is left based on the maintainer's earlier review. Use when the user says \"prepare for takeover\", \"open for takeover\", \"mark as abandoned\", \"invite contributors to finish\", or gives one or more PR numbers/URLs that stalled and should be handed over.\n---\n\n# Prepare for Takeover\n\n## Purpose\n\nSome PRs are worth finishing, but the author has gone quiet and the maintainer has no time to complete them. This skill drafts one comment per PR that:\n\n1. Says the PR seems to have stalled but is still valuable for redis-py.\n2. Announces the `open-for-takeover` label.\n3. Lists what is left, taken from the maintainer's earlier review.\n4. Invites the original author to continue, or anyone else to take over while keeping the author's credit.\n\nFor now the skill only generates the comment texts. It does not apply labels, post comments, or edit workflows.\n\n## Hard rule: no GitHub writes\n\nUse read-only GitHub access only (`gh pr view`, `gh api` GET). Never comment, label, review, or edit anything on GitHub. The user posts the comments and applies the labels manually. `gh` may fail with a TLS error inside the command sandbox; run it with the sandbox disabled when needed.\n\n## Inputs\n\n- One or more PR numbers or URLs. Default repo: `redis/redis-py`. Invoking the skill on a PR makes it eligible; do not require any label (such as `waiting-for-response`), a minimum inactivity period, or other staleness signals.\n- The maintainer's GitHub login: resolve it with `gh api user --jq .login`. Do not guess.\n\n## Data to collect per PR (read-only)\n\n```\ngh pr view <n> -R <owner/repo> --json title,author,state,isDraft,labels,updatedAt,body,reviews,comments\ngh api graphql --paginate -F owner=<owner> -F repo=<repo> -F n=<n> -f query='\n  query($owner: String!, $repo: String!, $n: Int!, $endCursor: String) {\n    repository(owner: $owner, name: $repo) {\n      pullRequest(number: $n) {\n        reviewThreads(first: 100, after: $endCursor) {\n          pageInfo { hasNextPage endCursor }\n          nodes {\n            isResolved isOutdated path line subjectType\n            comments(first: 100) {\n              nodes { author { login } body createdAt pullRequestReview { state } }\n            }\n          }\n        }\n      }\n    }\n  }'\n```\n\nThe second command returns the inline review threads, which `gh pr view` does not include. A `CHANGES_REQUESTED` review often has an empty or short body with the details inline. Per thread:\n\n- `isResolved: true` means the thread was settled (often the author resolved it after the fix, with no reply); treat it as done unless a later maintainer comment reopens it.\n- `isOutdated: true` means the code moved under the comment; check whether it still applies.\n- `subjectType: FILE` is a file-level comment. Its `line` is null, but it is still current.\n\nIgnore pending (unsubmitted) feedback: reviews with `state: PENDING` and thread comments whose `pullRequestReview.state` is `PENDING`. They are drafts visible only to the maintainer, so they must not appear in a public comment or count as an earlier review.\n\nFrom the output, get:\n\n- `author.login`: the PR author.\n- Whether the maintainer has already commented or reviewed (a comment, submitted review or inline comment by the maintainer's login).\n- All maintainer feedback in chronological order: review bodies, inline comments and conversation comments. Read it in full; it is the source for \"What's left\". Drop items in resolved threads, items that a later maintainer comment withdrew or marked as done, and items that a later commit or reply clearly addressed. An empty review body does not mean there is no feedback.\n- Current labels (to point out `waiting-for-response` for removal) and `isDraft`.\n\nIf the PR is closed or merged, skip it and say so. If the maintainer left no feedback in any of these sources, write \"What's left\" from the open TODOs in the PR `body` or ask the user for a line, and do not invent requirements.\n\n## Writing \"What's left\"\n\n- 1 to 5 short bullets, each one concrete action, in the order the maintainer raised them.\n- Paraphrase the review. Do not add new requests that the maintainer did not make.\n- Keep scope notes from the review (for example \"change Fixes #N to Refs #N\", \"keep #N open, retitle the PR\").\n- Add \"Take the PR out of draft.\" when `isDraft` is true and the review asked for it.\n- Put file and symbol names in single backticks.\n\n## Comment template\n\nInclude the greeting line **only** if the maintainer has not commented on or reviewed this PR before. Otherwise start with `@<author>, this PR seems to have stalled...`.\n\n```markdown\nHey @<author>, thank you for your contribution!\n\nThis PR seems to have stalled, but the change is still valuable for redis-py and I would like to see it land. I don't have the bandwidth to finish it myself right now, so I'm marking it as `open-for-takeover`.\n\nWhat's left (details in my review above):\n- <action>\n- <action>\n\n@<author>, if you want to continue, you're very welcome to, just let me know here. Otherwise, anyone interested can take it over: leave a comment first so two people don't work on it at the same time, then open a new PR that builds on this branch (keep the original commits so @<author> keeps the credit) and links back here.\n\nAny help is very welcome!\n```\n\nWhen there is no earlier maintainer review or inline review comment, use `What's left:` without \"(details in my review above)\".\n\n## Writing rules\n\n- Use \"I\", never \"we/us/our\". The maintainer is one person.\n- Never use em dashes, en dashes, or `--` as punctuation; use a single hyphen.\n- No code fences inside the comment (it breaks the outer fence). Use single backticks only.\n- Keep it short; do not restate the full review.\n\n## Output format\n\nStart with one line naming the audience, for example `Written for: the PR authors and other contributors on GitHub.`\n\nThen for each PR, a bold heading with the number and author, followed by the comment in its own fenced ```markdown block:\n\n**#<n>** (@<author>)\n\n```markdown\n<comment>\n```\n\nAfter the blocks, add at most two short notes:\n\n- PRs that still carry `waiting-for-response` (remove it when adding `open-for-takeover`).\n- PRs that were skipped (closed or merged).\n\n## Label reference\n\nIf the user asks for the label details:\n\n- Name: `open-for-takeover`\n- Description: `Stalled but valuable work - anyone is welcome to pick it up and finish it`\n- Color: `0e8a16`\n- Stale bot: add `open-for-takeover` to `exempt-pr-labels` in `.github/workflows/stale-issues.yml`.\n","frontmatter":{"name":"prepare-for-takeover","description":"Prepare copy-paste-ready maintainer comments that mark stalled but valuable redis-py PRs as `open-for-takeover`, inviting the original author or any other contributor to finish the work. Each comment summarizes what is left based on the maintainer's earlier review. Use when the user says \"prepare for takeover\", \"open for takeover\", \"mark as abandoned\", \"invite contributors to finish\", or gives one or more PR numbers/URLs that stalled and should be handed over."},"isInternal":false,"tokens":1613,"sizeBytes":6724},{"name":"SKILL.md","path":".claude/skills/generate-release-notes/SKILL.md","rawUrl":"https://raw.githubusercontent.com/redis/redis-py/HEAD/.claude/skills/generate-release-notes/SKILL.md","title":"generate-release-notes","category":"anthropic-skill","format":"markdown","content":"---\nname: generate-release-notes\ndescription: Generate a redis-py GitHub release-notes draft and save it as a Markdown file. Collects the merged PRs/commits since the previous release, maps each commit to its PR labels, categorizes them into the project's release sections (Breaking Changes, Deprecations, Experimental Features, New Features, Bug Fixes, Maintenance) using the skill's pr-labels-guide, infers a category for unlabeled PRs, and assembles the notes with an optional Highlights section and a contributors footer. Trigger this skill whenever the user says \"release notes\", \"create release notes\", \"generate release notes\", \"draft release notes\", \"prepare release notes\", \"changelog\", or asks for the release notes/changelog of a specific version or branch (e.g. \"release notes for 8.0\", \"generate release notes for the 8.0 branch\", \"what changed since the last release\"). A phrase like \"release notes for 8.0\" means: use branch `8.0` as the release-branch input. Also use when asked to summarize what changed since the last tag.\n---\n\nThe full instructions for this skill live in\n`.agents/skills/generate-release-notes/SKILL.md` (relative to the redis-py repo root),\nwith its helper files under `.agents/skills/generate-release-notes/references/`.\n\n**Read `.agents/skills/generate-release-notes/SKILL.md` in full now and follow it\nexactly.** Treat its contents as the body of this skill.\n","isInternal":false,"tokens":319,"sizeBytes":1399},{"name":"SKILL.md","path":".claude/skills/maintainer-review/SKILL.md","rawUrl":"https://raw.githubusercontent.com/redis/redis-py/HEAD/.claude/skills/maintainer-review/SKILL.md","title":"maintainer-review","category":"anthropic-skill","format":"markdown","content":"---\nname: maintainer-review\ndescription: Review a GitHub issue or pull request URL as a redis-py maintainer, with a staged assessment of whether the claim is real, practically important, already solvable with supported functionality, correctly scoped, better served by another design, and worth maintainer and contributor effort. Use when assessing issue validity or severity, deciding whether an issue should be prioritized or closed, determining whether a requested feature represents an unmet need rather than a discoverability or usage gap, judging whether a PR is worth bringing to mergeable quality, comparing open PRs or alternative designs, separating code quality from repository readiness, or drafting a concise maintainer assessment. When closure, additional evidence, or code changes should be requested, also produce a polite, concise, complete, copy-paste-ready maintainer comment.\n---\n\nThe full instructions for this skill live in\n`.agents/skills/maintainer-review/SKILL.md` (relative to the redis-py repo root), with\nits helper files under `.agents/skills/maintainer-review/references/`.\n\n**Read `.agents/skills/maintainer-review/SKILL.md` in full now and follow it exactly.**\nTreat its contents as the body of this skill.\n","frontmatter":{"name":"maintainer-review","description":"Review a GitHub issue or pull request URL as a redis-py maintainer, with a staged assessment of whether the claim is real, practically important, already solvable with supported functionality, correctly scoped, better served by another design, and worth maintainer and contributor effort. Use when assessing issue validity or severity, deciding whether an issue should be prioritized or closed, determining whether a requested feature represents an unmet need rather than a discoverability or usage gap, judging whether a PR is worth bringing to mergeable quality, comparing open PRs or alternative designs, separating code quality from repository readiness, or drafting a concise maintainer assessment. When closure, additional evidence, or code changes should be requested, also produce a polite, concise, complete, copy-paste-ready maintainer comment."},"isInternal":false,"tokens":252,"sizeBytes":1239},{"name":"SKILL.md","path":".claude/skills/pr-draft-summary/SKILL.md","rawUrl":"https://raw.githubusercontent.com/redis/redis-py/HEAD/.claude/skills/pr-draft-summary/SKILL.md","title":"pr-draft-summary","category":"anthropic-skill","format":"markdown","content":"---\nname: pr-draft-summary\ndescription: Produce PR-ready output for redis-py — a one-line commit message and a copy-pasteable Markdown PR description (Change summary + Test coverage, 5–6 paragraphs) covering all staged changes plus the commits already on the current branch. Trigger whenever the user's message mentions any of these keywords or phrases: \"PR\", \"pr\", \"PR description\", \"PR draft\", \"pull request\", \"the PR block\", \"commit msg\", \"commit message\", or otherwise asks for a commit message or PR write-up for their local changes.\n---\n\nThe full instructions for this skill live in\n`.agents/skills/pr-draft-summary/SKILL.md` (relative to the redis-py repo root).\n\n**Read `.agents/skills/pr-draft-summary/SKILL.md` in full now and follow it exactly.**\nTreat its contents as the body of this skill.\n","isInternal":false,"tokens":188,"sizeBytes":808},{"name":"SKILL.md","path":".claude/skills/prepare-for-takeover/SKILL.md","rawUrl":"https://raw.githubusercontent.com/redis/redis-py/HEAD/.claude/skills/prepare-for-takeover/SKILL.md","title":"prepare-for-takeover","category":"anthropic-skill","format":"markdown","content":"---\nname: prepare-for-takeover\ndescription: Prepare copy-paste-ready maintainer comments that mark stalled but valuable redis-py PRs as `open-for-takeover`, inviting the original author or any other contributor to finish the work. Each comment summarizes what is left based on the maintainer's earlier review. Use when the user says \"prepare for takeover\", \"open for takeover\", \"mark as abandoned\", \"invite contributors to finish\", or gives one or more PR numbers/URLs that stalled and should be handed over.\n---\n\nThe full instructions for this skill live in\n`.agents/skills/prepare-for-takeover/SKILL.md` (relative to the redis-py repo root).\n\n**Read `.agents/skills/prepare-for-takeover/SKILL.md` in full now and follow it exactly.**\nTreat its contents as the body of this skill.\n","frontmatter":{"name":"prepare-for-takeover","description":"Prepare copy-paste-ready maintainer comments that mark stalled but valuable redis-py PRs as `open-for-takeover`, inviting the original author or any other contributor to finish the work. Each comment summarizes what is left based on the maintainer's earlier review. Use when the user says \"prepare for takeover\", \"open for takeover\", \"mark as abandoned\", \"invite contributors to finish\", or gives one or more PR numbers/URLs that stalled and should be handed over."},"isInternal":false,"tokens":181,"sizeBytes":782},{"name":"add-new-command.md","path":".agents/commands/add-new-command/add-new-command.md","rawUrl":"https://raw.githubusercontent.com/redis/redis-py/HEAD/.agents/commands/add-new-command/add-new-command.md","title":"Command: /add-new-command","category":"command-prompt","format":"markdown","content":"---\ndescription: Adds support for a new Redis command from a given specification. Check command-specification-template.md.\nargument-hint: [path-to-specification]\n---\n\n# Execute: Add new Redis command support\n\n## Plan to Execute\n\nRead specification file: `$ARGUMENTS`\n\n## Execution Instructions\n\n### 1. Preparations\n\n- Follow instructions from `.agent/instructions.md`\n- Go through the guide `specs/redis_commands_guide.md`\n\n### 2. Read and Understand\n\n- Read the ENTIRE specification carefully\n- Go through Command Description and identify command type (string, list, set, etc.)\n- Go through the Command API:\n  - Identify required and optional arguments\n  - Identify how to match Redis command arguments type to Python types\n  - Identify return value and possible response types\n- Check relevant Redis-Cli examples, if provided\n- Review the Test Plan\n\n### 3. Execute Tasks in Order\n\n#### a. Navigate to the task\n- Identify the files and action required\n- Read existing related files if modifying\n\n#### b. Implement the command\n- Add new command method within matching trait object (for example: `redis/commands/core.py` for core Redis commands)\n- Ensure overloading is implemented for sync and async API\n- Follow Arguments definition section from `specs/redis_commands_guide.md` before defining command arguments\n- Ensure arguments and response types consider bytes representation\n- Ensure response RESP2 and RESP3 compatibility via `response_callbacks`\n\n#### c. Record the command's metadata\n- Obtain the server's metadata for the command: use the `COMMAND INFO` output from the\n  specification's \"Command metadata\" section, or run `COMMAND INFO <name>` against a server\n  that ships the command. For a container command (`BLESS`, `MEMORY`, ...) the subcommands\n  are nested in the reply as `<container>|<sub>`; each one gets its own record.\n- Add a record for the command to `_STATIC_COMMAND_METADATA` in `redis/commands/metadata.py`,\n  keyed lowercase under its module (`\"core\"` for non-module commands, the lowercased prefix\n  such as `\"json\"` or `\"ft\"` for module commands). Key a container subcommand by its\n  space-joined name (`\"bless scan\"`), the form `execute_command` receives.\n- Map the reply onto the record fields and reuse an existing shape (`_CACHEABLE_KEYED`,\n  `_WRITE_KEYED`, `_NONDETERMINISTIC_KEYED`, ...) when one matches; otherwise spell out every\n  field of a `CommandMetadata` inline, as the table does for `touch` and `vrandmember`:\n  - `is_readonly` is the `readonly` command flag - not the `RO` key spec flag. A command the\n    server does not flag `readonly` is neither client-side cacheable nor replica-eligible.\n  - `is_blocking` is the `blocking` flag; `is_script_runner` the `script_runner` flag.\n  - `has_key_argument` is true when `first_key_pos > 0 and step_count > 0`, or when a\n    `movablekeys` command reports a key spec not flagged `not_key`.\n  - `has_nondeterministic_output` and `is_dont_cache` are the `nondeterministic_output` and\n    `dont_cache` tips, matched exactly (`nondeterministic_output_order` is a different tip).\n  - `has_complete_metadata` is true: the record was taken from a reply that carries flags and\n    tips.\n  - `request_policy` / `response_policy` are the keyed or keyless defaults unless a\n    `request_policy:` / `response_policy:` tip overrides them. Withhold both (set them to\n    `None`) when the cluster client must keep resolving the target itself: the command is in\n    `RedisCluster.COMMAND_FLAGS` (as `SCAN` and `BLESS SCAN` are), is `movablekeys`, or tips a\n    policy the client does not implement (`multi_shard`). A keyed command must never be\n    recorded `DEFAULT_KEYLESS`.\n- Document in a comment next to the record anything that diverges from the live reply and\n  why, and extend the provenance note above the table if the command needs a newer server\n  than the table was validated against.\n- Update the guards in `tests/test_command_metadata.py` (shared with its async mirror):\n  add a withheld record to `ALL_WITHHELD_ROUTING_COMMANDS`, a deliberate cacheability\n  divergence to `LIVE_CACHEABILITY_DIVERGENCE`, and raise `STATIC_TABLE_SERVER_VERSION` if\n  the command first ships in a newer release. Then run `tests/test_command_metadata.py`,\n  `tests/test_asyncio/test_command_metadata.py` and the static-table routing tests in\n  `tests/test_cluster.py` / `tests/test_asyncio/test_cluster.py`.\n\n#### d. Verify as you go\n- After each file change, check syntax\n- Ensure imports are correct\n- Verify types are properly defined\n- Verify that response schema is similar for RESP2 and RESP3\n\n### 4. Implement Testing Plan\n\nAfter completing implementation tasks:\n\n- Identify matching test file or create new one if needed\n- Implement all test cases as separate test methods\n- Ensure adding version constraint if specified in the specification\n- Ensure tests cover edge cases\n\n### 5. Run tests\n\n- Run newly added test cases using `pytest` with RESP2 and RESP3 protocol specified via `--protocol` option\n- Ensure that the same test cases passed with both protocols\n- Get back to the Implementation stage if any test failed\n\n### 6. Final Verification\n\nBefore completing:\n\n- ✅ All tasks from plan completed\n- ✅ All tests created and passing\n- ✅ Code follows project conventions\n- ✅ Documentation added/updated as needed\n\n## Output Report\n\nProvide summary:\n\n### Completed Tasks\n- List of all tasks completed\n- Files created (with paths)\n- Files modified (with paths)\n\n### Tests Added\n- Test files created\n- Test cases implemented\n- Test results\n\n\n","frontmatter":{"description":"Adds support for a new Redis command from a given specification. Check command-specification-template.md.","argument-hint":["path-to-specification"]},"isInternal":false,"tokens":1274,"sizeBytes":5530},{"name":"command-specification-template.md","path":".agents/commands/add-new-command/command-specification-template.md","rawUrl":"https://raw.githubusercontent.com/redis/redis-py/HEAD/.agents/commands/add-new-command/command-specification-template.md","title":"Command: /command-specification-template","category":"command-prompt","format":"markdown","content":"# $COMMAND_NAME command specification\n\n## Supported version\n\nAdd supported Redis version here. For example: Redis >= 6.2.0\n\n## Command description\n\nAdd a description of the command here.\n\n## Command API\n\nSpecify an API for the command in the format that official docs uses. For example:\n\n```\n$COMMAND_NAME $key $member [NX|XX] [CH] [INCR]\n```\n\n## Redis-CLI examples\n\nAdd relevant Redis-CLI examples here.\n\n## Command metadata\n\nPaste the output of `COMMAND INFO $COMMAND_NAME` from a server that ships the command. It is\nthe source for the command's record in `_STATIC_COMMAND_METADATA`\n(`redis/commands/metadata.py`): the command flags, the `request_policy:` / `response_policy:`\n/ `nondeterministic_output` / `dont_cache` tips and the key specifications. For a container\ncommand, include the nested subcommands.\n\n## Test plan\n\nSpecify how you want to test the command in terms of integration testing. For example:\n\n- Test only with required arguments, assert that single value returned\n- Test with required arguments and optional XX modifier, ensure that 1 returned\n- ...\n","isInternal":false,"tokens":245,"sizeBytes":1073},{"name":"sync-claude-md.md","path":".agents/commands/sync-claude-md/sync-claude-md.md","rawUrl":"https://raw.githubusercontent.com/redis/redis-py/HEAD/.agents/commands/sync-claude-md/sync-claude-md.md","title":"Command: /sync-claude-md","category":"command-prompt","format":"markdown","content":"---\ndescription: Audit AGENTS.md (the canonical agent guide; CLAUDE.md is only an `@AGENTS.md` include) against the current state of the repo and update it directly. Run when wrapping up a feature or after significant structural changes.\n---\n\n# Execute: Sync AGENTS.md with current repo state\n\nAudit `AGENTS.md` at the repo root and update it to reflect the current state of the codebase. This is the canonical guidance file; `CLAUDE.md` is only an `@AGENTS.md` include and must NOT be edited (editing it would drop the include and duplicate content). Edit `AGENTS.md` directly — do not ask for confirmation first.\n\n## Inputs\n\n- Current `AGENTS.md` (read it before doing anything else — it's the baseline you're updating against).\n- Recent git history. Read `git log --oneline -40` and `git log --diff-filter=A --name-only --since=\"3 months ago\"` to find recently added subsystems.\n- Current file/directory layout of the repo, especially `redis/`, `redis/asyncio/`, `redis/commands/`, `redis/_parsers/`, `tests/`, `.github/workflows/`, `.agents/`, `.claude/`.\n\n## What to verify\n\nWalk through each section of `AGENTS.md` and check:\n\n### 1. Commands section\n- Cross-reference every `invoke` task mentioned against `tasks.py`. Catch wrong defaults, renamed flags, removed/added tasks, changed conftest options.\n- Cross-reference pytest options against the `pytest_addoption` calls in `tests/conftest.py`.\n- Cross-reference the pytest marker list against `[tool.pytest.ini_options].markers` in `pyproject.toml`.\n\n### 2. Architecture section\n- Verify each subpackage list matches what's actually in the directory (e.g., the modules under `redis/commands/`, `redis/_parsers/`, `redis/multidb/`, `redis/asyncio/`, `redis/auth/`, `redis/observability/`, `redis/http/`).\n- Check whether any new top-level subsystem under `redis/` was added since AGENTS.md was last touched and is not mentioned. Use `git log --diff-filter=A --name-only` scoped to `redis/` to find newly added files.\n- Verify any specific symbols I reference (e.g., `READ_COMMANDS`, `RequestPolicy`, `ResponsePolicy`, `CommandsParser`, `ExponentialWithJitterBackoff`) still exist — grep for them.\n- Confirm sync/async mirror claim: spot-check that the async equivalents listed exist under `redis/asyncio/`.\n\n### 3. Python/dependency notes\n- Cross-reference `requires-python`, `[project.optional-dependencies]`, and ruff config in `pyproject.toml`.\n\n### 4. Pointers to other docs\n- Verify referenced files still exist.\n\n## What to add (only if genuinely missing)\n\nIf a new subsystem, workflow, or developer-facing convention was introduced and is non-obvious from reading the directory tree, add a short note. Examples of things worth adding:\n\n- A new top-level package under `redis/` with its own architectural role.\n- A new `invoke` task or a meaningful change to an existing one.\n- A new pytest marker or a new convention about how tests are organized.\n- A newly added `.agents/skills/` or `.agents/commands/` skill that future agents should know about.\n- A new spec file under `specs/` that documents a load-bearing design decision.\n\n## What NOT to add\n\n- Per-file descriptions or anything discoverable by `ls`. AGENTS.md is an orienting document, not a directory listing.\n- Generic Python or git advice.\n- Recent bugfixes or commit-level changes. AGENTS.md describes the repo, not its history.\n- Anything obvious from reading `README.md` or `CONTRIBUTING.md`.\n\n## Output\n\n1. Apply edits directly to `AGENTS.md` using the `Edit` tool. Prefer small, targeted edits over rewrites.\n2. After editing, print a short summary of what changed and why (under 200 words). Group by section: Commands / Architecture / Other.\n3. If nothing needs to change, say so explicitly — do not edit the file just to look productive.\n","frontmatter":{"description":"Audit AGENTS.md (the canonical agent guide; CLAUDE.md is only an `@AGENTS.md` include) against the current state of the repo and update it directly. Run when wrapping up a feature or after significant structural changes."},"isInternal":false,"tokens":901,"sizeBytes":3779},{"name":"add-new-command.md","path":".claude/commands/add-new-command.md","rawUrl":"https://raw.githubusercontent.com/redis/redis-py/HEAD/.claude/commands/add-new-command.md","title":"Command: /add-new-command","category":"command-prompt","format":"markdown","content":"---\ndescription: Adds support for a new Redis command from a given specification. Check command-specification-template.md.\nargument-hint: [path-to-specification]\n---\n\nThe full instructions for this command live in\n`.agents/commands/add-new-command/add-new-command.md` (relative to the redis-py repo\nroot), with the spec template at\n`.agents/commands/add-new-command/command-specification-template.md`.\n\n**Read `.agents/commands/add-new-command/add-new-command.md` in full now and follow it\nexactly.**\n\nThe specification file to use is: `$ARGUMENTS`\n","frontmatter":{"description":"Adds support for a new Redis command from a given specification. Check command-specification-template.md.","argument-hint":["path-to-specification"]},"isInternal":false,"tokens":121,"sizeBytes":549},{"name":"sync-claude-md.md","path":".claude/commands/sync-claude-md.md","rawUrl":"https://raw.githubusercontent.com/redis/redis-py/HEAD/.claude/commands/sync-claude-md.md","title":"Command: /sync-claude-md","category":"command-prompt","format":"markdown","content":"---\ndescription: Audit AGENTS.md (the canonical agent guide; CLAUDE.md is only an `@AGENTS.md` include) against the current state of the repo and update it directly. Run when wrapping up a feature or after significant structural changes.\n---\n\nThe full instructions for this command live in\n`.agents/commands/sync-claude-md/sync-claude-md.md` (relative to the redis-py repo root).\n\n**Read `.agents/commands/sync-claude-md/sync-claude-md.md` in full now and follow it\nexactly.**\n","frontmatter":{"description":"Audit AGENTS.md (the canonical agent guide; CLAUDE.md is only an `@AGENTS.md` include) against the current state of the repo and update it directly. Run when wrapping up a feature or after significant structural changes."},"isInternal":false,"tokens":121,"sizeBytes":477}],"systemPromptSnippet":"<yakaai_skills repo=\"redis/redis-py\">\n<!-- File: AGENTS.md (Tokens: ~4134 | Category: root-instruction) -->\n# ✅ Instructions for AI agents contributing to redis-py\n\nThis file is the single source of guidance for AI agents working in this repository. It combines project reference material (commands, architecture, dependency notes) with the contributor process rules you must follow.\n\n## Common commands\n\nDevelopment tasks are driven by `invoke` (see `tasks.py`). Activate `.venv` first.\n\n- `invoke devenv` — start the docker-compose test environment (standalone, replica, sentinel, cluster, redis-stack, stunnel). Profiles in `docker-compose.yml` control which containers come up; `--profile all` is the default.\n- `invoke clean` — stop all dockers and remove `build/` and `dist/`.\n- `invoke tests` — run `fixed_client`, `standalone`, then `cluster` test suites in sequence. Accepts `--uvloop`, `--protocol=2|3|\"\"`, `--legacy-responses=true|false`, `--profile`.\n- `invoke standalone-tests` / `invoke cluster-tests` — run just one suite. Cluster suite targets `redis://localhost:16379/0` (`rediss://localhost:27379/0` for TLS).\n- `invoke fixed-client-tests` — runs only tests marked `fixed_client` (tests that pin client config and must not be reconfigured by the global `--protocol` / `--legacy-responses` knobs).\n- `invoke run-test-matrix` — full `protocol` × `legacy_responses` matrix; what CI does.\n- `invoke linters` / `invoke linters-fix` — `ruff check`, `ruff format`, and `vulture redis whitelist.py --min-confidence 80` (use `whitelist.py` to suppress vulture false positives).\n- `invoke all-tests` — linters + tests.\n- `invoke build-docs` — sphinx HTML under `docs/`.\n\nRunning a single test directly with pytest:\n\n```\npytest tests/test_commands.py::TestRedisCommands::test_set -x\npytest tests/test_asyncio/test_commands.py -k \"test_set and not cluster\"\npytest --redis-url=redis://localhost:16379/0 -m onlycluster tests/test_cluster.py\n```\n\nThe pytest plugin in `tests/conftest.py` adds these options (also exposed via invoke wrappers): `--redis-url`, `--redis-ssl-url`, `--redis-mod-url`, `--protocol` (`\"\"` = library default, currently RESP3), `--legacy-responses` (`true|false|default`), `--redis-cluster-nodes`, `--uvloop`. It also adds `--tracemalloc` (off by default, not exposed via invoke), which traces allocations so `ResourceWarning`s show where the object was allocated; it makes cluster client setup several times slower.\n\n### pytest markers\n\nDefined in `pyproject.toml` and used to scope test runs:\n\n- `onlycluster` / `onlynoncluster` — limit a test to one topology.\n- `redismod` — requires Redis modules (Stack image); skipped against cluster.\n- `fixed_client` — test fixes its own client/config; excluded from the protocol/legacy matrix runs.\n- `experimental`, `replica`, `ssl`, `pipeline`, `cp_integration`, `no_mock_connections`.\n\n`tests/test_scenario/` and `tests/test_asyncio/test_scenario/` are scenario tests that require external infrastructure (Redis Enterprise, EntraID, etc.); they are excluded from the default `standalone-tests` / `cluster-tests` runs via `--ignore`.\n\n## Architecture\n\n### Sync and async mirrors\n\nThe library ships two parallel client stacks that must stay in lockstep:\n\n- Sync top-level: `redis/client.py`, `redis/cluster.py`, `redis/connection.py`, `redis/sentinel.py`, `redis/lock.py`, `redis/retry.py`.\n- Async mirrors under `redis/asyncio/`: `client.py`, `cluster.py`, `connection.py`, `sentinel.py`, etc.\n\nWhen changing behavior in one stack, the equivalent change almost always belongs in the other. `specs/sync_async_deduplication_analysis.md` documents the deliberate duplication. The process rules under \"Sync / Async Consistency\" below are mandatory whenever you touch either stack.\n\n### Commands layer (mixins)\n\nCommand surface is composed via mixin classes in `redis/commands/`:\n\n- `core.py` — `CoreCommands` / `AsyncCoreCommands` (every standard Redis command).\n- `cluster.py` — `RedisClusterCommands` / `AsyncRedisClusterCommands`; also exports `READ_COMMANDS`, which is **deprecated and unread**: replica eligibility is decided by the metadata resolver's `is_replica_safe`, so editing this set changes nothing. It is kept only so external importers keep working.\n- `sentinel.py` — sentinel-specific commands.\n- `redismodules.py` — aggregates module mixins (`bf/` Bloom, `json/`, `search/`, `timeseries/`, `vectorset/`). Each module subpackage owns its own command surface and response parsing.\n- `policies.py` — `PolicyResolver` / `StaticPolicyResolver`, the routing view the cluster client resolves per command. Also holds `STATIC_POLICIES`, which is **deprecated and unread**: a frozen verbatim copy of the 7.1.0 table, kept only for external importers and deliberately not derived from or synced with `_STATIC_COMMAND_METADATA`. Change routing in `metadata.py`, never there.\n- `metadata.py` — the command metadata record types: `RequestPolicy` / `ResponsePolicy`, `CommandPolicies` / `PolicyRecords`, and `CommandMetadata` / `CommandMetadataRecordsCache`, plus the metadata resolvers. `CommandMetadata` is the superset record; `_to_command_policies` projects one down to the routing view a `PolicyResolver` serves - projecting to `None` when the record withholds its routing policies, which is how a resolver tells the cluster client to resolve the target itself (the `movablekeys` reads need this: their keys are only in the key specs, so derived policies come out keyless). The two types are kept separate deliberately: `CommandPolicies` shipped in 7.1.0 as a mutable class, and `CommandMetadata` is frozen so one record can be shared across many commands. `redis/_parsers/commands.py` re-exports the policy types from here for backwards compatibility; import them from `redis.commands.metadata`. A resolver is a client-level constructor argument (`metadata_resolver=` on `Redis`, `ConnectionPool`, `RedisCluster`, `NodesManager` and `ClusterPipeline`; on the async side only on `redis.asyncio.RedisCluster`, because the async stack has no client-side cache to be the second consumer), built once and shared with every node client; it serves cluster routing through `resolve_policies`, replica safety through `is_replica_safe`, and client-side-cache eligibility through `is_cacheable`. When only `metadata_resolver` is given the routing resolver is derived from it; an explicit `policy_resolver` still wins for the routing view - which nodes a command targets - but not for anything that view cannot express: replica safety and cache eligibility keep resolving through `metadata_resolver` either way, because a `CommandPolicies` record carries no `is_readonly` flag to answer them with.\n- `helpers.py` — shared utilities (`list_or_args`, pubsub subscription partitioning, etc.).\n\n`Redis` (sync) and `redis.asyncio.Redis` are assembled by inheriting these mixins plus the connection/pool plumbing. Adding a new command means editing both the sync and async mixin classes and, for cluster routing, possibly the command metadata in `metadata.py`, which drives both the routing policies and replica eligibility. Not `READ_COMMANDS` - it is deprecated and unread. There is a `/add-new-command` skill (`.claude/commands/add-new-command.md`) that follows the project's expected workflow; use the spec template at `.agents/commands/add-new-command/command-specification-template.md`. Before adding commands, also read this file (AGENTS.md) and `.agents/sync_async_type_hints_overload_guide.md` — they document the sync/async type-overload convention that command additions must follow. The `/sync-claude-md` skill audits and refreshes this file when the project structure changes.\n\n### Wire protocol and response parsing\n\n`redis/_parsers/` handles RESP framing and response shaping:\n\n- `resp2.py`, `resp3.py` — pure-Python parsers.\n- `hiredis.py` — optional C-accelerated parser; auto-used if `hiredis>=3.2.0` is installed.\n- `response_callbacks.py` — per-command response post-processing (the place where the library reshapes raw protocol output into Python types).\n- `commands.py` — `CommandsParser`, used by the cluster client to learn command metadata via `COMMAND INFO`; the policy record types it returns live in `redis/commands/metadata.py`.\n- `encoders.py` — request encoding.\n\nRESP3 is now the default on the wire. Two knobs interact:\n\n- `protocol=2|3` chooses the wire protocol.\n- `legacy_responses=True|False` chooses the *Python* response shape. `True` (current default) preserves RESP2-era shapes regardless of wire protocol; `False` returns the new unified shapes. The test matrix exercises both axes; see `specs/unified_responses_migration_guide.md`.\n\n### Cluster client\n\n`redis/cluster.py` (`redis/asyncio/cluster.py`) implements topology discovery, slot mapping (`redis/crc.py`, `key_slot`), per-node connection pools, MOVED/ASK redirection, and routing using `RequestPolicy`/`ResponsePolicy` enums from `redis/commands/metadata.py` resolved via `redis/commands/policies.py`. Replica eligibility for read routing is decided by the metadata resolver's `is_replica_safe`, which reads the `is_readonly` flag on the command metadata record; `READ_COMMANDS` is deprecated and no longer consulted.\n\n### Multi-database (Active-Active) client\n\n`redis/multidb/` (and `redis/asyncio/multidb/`) implements a client that fronts multiple Redis deployments with health checks, failure detection, and failover. Key modules: `client.py`, `command_executor.py`, `database.py`, `failover.py`, `failure_detector.py`, `config.py`, plus `circuit.py` (pybreaker) and `exception.py` on the sync side and `healthcheck.py` on the async side. Mirror sync/async changes here too.\n\n### Cross-cutting subsystems\n\n- `redis/auth/` — credential providers and token-based auth (EntraID via `redis-entraid`; JWT support).\n- `redis/cache.py` — client-side caching configuration. `CacheConfig.is_allowed_to_cache` is the single eligibility decision point, and delegates to the `MetadataResolver` the config holds (`redis/commands/metadata.py`), defaulted to the static table and injected by `ConnectionPool.__init__` from the client-level `metadata_resolver=`. `DEFAULT_ALLOW_LIST` is deprecated and unread - change eligibility in `metadata.py`, never there. `CacheProxyConnection.send_command` asks that decision point and then requires the invocation to carry `keys`; a cacheable command without them bypasses the cache rather than raising, so plumbing `keys=` into a command method is a purely additive way to start caching it.\n- `redis/maint_notifications.py` — server-pushed maintenance notifications and the handler that reacts to them. The handlers report the relaxation they apply as `MaintenanceStartedEvent` / `MaintenanceCompletedEvent` (`redis/event.py`) to the optional `event_dispatcher` on `MaintNotificationsConfig`; the MultiDB health check policy (`redis/asyncio/multidb/healthcheck.py`) is the in-tree consumer, relaxing a database's `health_check_timeout` to `probes * (relaxed_timeout + delay)` while its probe client is under maintenance. With notifications enabled, a pooled connection that has readable data while idle is not reconnected outright by `ensure_connection`: `handle_pending_push_notifications` (both connection mixins) drains the buffered pushes with the handlers deferred, applies them only if the socket is still alive (the maintenance is still in progress), and discards them and raises for the pool to reconnect if the server has closed the socket or a stale reply was left on it.\n- `redis/keyspace_notifications.py` — keyspace/keyevent subscription helpers (sync and async variants live side by side).\n- `redis/observability/` (and `redis/asyncio/observability/`) — OpenTelemetry instrumentation. Modules: `attributes.py` (span/metric attribute keys), `config.py`, `metrics.py`, `providers.py`, `recorder.py` (the API the rest of the code calls — `record_operation_duration`, `record_error_count`, `record_pubsub_message`, `record_streaming_lag_from_response`), `registry.py`. Optional, gated by the `otel` extra. The async stack has only `recorder.py` and delegates the rest to the sync package.\n- `redis/event.py` — `EventDispatcher` used to notify subscribers on connection lifecycle events.\n- `redis/http/` — HTTP client used by some auth flows and scenario tests.\n- `redis/retry.py`, `redis/backoff.py` — retry/backoff strategy (`ExponentialWithJitterBackoff` is the default).\n\n### Docker test images\n\n`dockers/` contains config for standalone, cluster, sentinel, and redis-stack containers. The compose file pulls `redislabs/client-libs-test:<tag>` images parameterized by `CLIENT_LIBS_TEST_IMAGE_TAG` / `CLIENT_LIBS_TEST_STACK_IMAGE_TAG`. CI pins these via `CURRENT_REDIS_VERSION` in `.github/workflows/integration.yaml`.\n\n## Python and dependency notes\n\n- `requires-python = \">=3.10\"`; `pyproject.toml` lists the supported versions.\n- Optional extras: `hiredis`, `xxhash`, `ocsp`, `jwt`, `circuit_breaker`, `otel`.\n- Ruff is the only formatter/linter (`target-version = \"py310\"`, `line-length = 88`). `tests/*` has relaxed naming rules; module command packages (`bf`, `timeseries`, `json`, `search`) opt out of pep8 naming.\n- `whitelist.py` is the vulture allowlist — extend it instead of silencing vulture inline.\n\n---\n\n## MUST\n- Preserve public API compatibility (signatures, argument names, defaults, return types)\n- Keep sync and async implementations fully aligned (behavior, signatures, type hints) when possible\n- Run the full test suite and fix all failures\n- Run linting and fix all issues\n- Follow existing code patterns and conventions exactly\n- Maintain Redis command semantics and behavior precisely\n- Optimize for performance and memory efficiency\n\n## MUST NOT\n- Introduce breaking API changes without explicit need\n- Modify unrelated code\n- Change Redis behavior, edge cases, or error semantics\n- Introduce new dependencies without strong justification\n- Add unnecessary abstractions or new patterns\n- Weaken or remove existing tests to make them pass\n\n## General Principles\n- Make minimal, surgical changes\n- Prefer consistency over innovation\n- If unsure, follow existing implementations in the codebase\n- Prioritize correctness first, then performance\n\n## Type Hints & Overloads\n- Use PEP 604 (`X | Y`) syntax\n- Follow `.agents/sync_async_type_hints_overload_guide.md`\n- Keep type hints identical between sync and async APIs\n- Do not change public type signatures unnecessarily\n\n## Imports\n- Import all types at the top of the file\n- Avoid function-level imports unless absolutely necessary\n- Keep imports minimal and consistent\n\n## Sync / Async Consistency\n- Any change in sync must be mirrored in async (and vice versa)\n- Ensure identical signatures, return types, and behavior\n- Ensure overloads remain aligned\n\n## Performance & Memory\n- Prioritize correctness first, then optimize for performance and memory efficiency\n- Never trade correctness for performance\n- Avoid unnecessary allocations in hot paths\n- Avoid creating temporary objects when not needed\n- Reuse existing helpers/utilities instead of duplicating logic\n- Be careful with large data structures\n\n## Redis Semantics\n- Match Redis server behavior exactly\n- Preserve error types and messages\n- Do not introduce silent behavior changes\n- Handle edge cases explicitly and correctly\n\n## Testing\n- Add or update tests for every behavior change\n- Cover edge cases and failure scenarios\n- Keep tests deterministic and stable\n- Do not relax assertions to pass tests\n\n## Linting & Code Quality\n- Run all linters and fix issues\n- Ensure code is clean, readable, and consistent\n- Perform a full code review after changes\n- Refactor if needed to meet project standards\n\n## Documentation\n- Update docstrings when behavior changes\n- Keep docstrings consistent with type hints\n- Avoid redundant or outdated documentation\n\n## Dependencies\n- Do not introduce new dependencies unless absolutely necessary\n- Prefer existing utilities within the project\n\n---\n\n# 🔁 Self-Check Loop (MANDATORY)\n\nBefore finalizing changes, perform this loop:\n\n1. **Correctness Check**\n   - Does the change preserve Redis semantics exactly?\n   - Are edge cases handled correctly?\n\n2. **API Check**\n   - Did any public function signature change?\n   - If yes, revert or justify explicitly\n\n3. **Sync/Async Check**\n   - Are both implementations updated and identical in behavior?\n   - Are type hints and overloads aligned?\n\n4. **Performance Check**\n   - Did this introduce extra allocations or unnecessary work?\n   - Can this be simplified or reused?\n\n5. **Consistency Check**\n   - Does this match existing patterns in the codebase?\n   - Are naming and structure consistent?\n\n6. **Test Check**\n   - Are tests added/updated for the change?\n   - Do all tests pass without weakening assertions?\n\nRepeat this loop until all answers are satisfactory.\n\n---\n\n# 🔍 Diff Validation (MANDATORY)\n\nBefore submitting changes, review the full diff:\n\n- Ensure only relevant files are modified\n- Ensure no unrelated lines were changed\n- Ensure no accidental deletions of logic\n- Ensure no debug code or comments remain\n- Ensure imports were not unnecessarily altered\n- Ensure public APIs were not unintentionally modified\n\nIf any issue is found:\n- Fix it\n- Re-run validation\n\n---\n\n## 🔎 Code Review (MANDATORY)\n\nAfter implementing changes, perform a thorough code review of the final code.\n\n### Verify:\n\n- **Correctness**\n  - Logic is correct and complete\n  - Edge cases are handled properly\n  - Redis semantics are preserved exactly\n\n- **API Stability**\n  - No unintended changes to public APIs\n  - Function signatures, defaults, and return types are unchanged\n\n- **Sync/Async Parity**\n  - Sync and async implementations are fully aligned\n  - Type hints and overloads match\n\n- **Performance**\n  - No unnecessary allocations or inefficiencies introduced\n  - No redundant work or duplicate logic\n\n- **Consistency**\n  - Code follows existing patterns and conventions\n  - Naming and structure match the rest of the codebase\n\n- **Simplicity**\n  - No unnecessary abstractions added\n  - Implementation is as simple as possible\n\n- **Tests**\n  - Tests cover the change adequately\n  - No weakened assertions\n\n### If any issue is found:\n- Fix it\n- Re-run tests and linting\n- Repeat the review\n\n---\n\n# ✅ Final Checklist (must all be true)\n\n- Tests pass\n- Lint passes\n- Sync/async parity verified\n- No API breakage\n- No unnecessary allocations introduced\n- Behavior matches Redis exactly\n- Code follows existing patterns\n- Diff is minimal and clean\n\n<!-- File: CLAUDE.md (Tokens: ~5 | Category: claude-rule) -->\n@AGENTS.md\n\n<!-- File: .agents/skills/generate-release-notes/references/pr-labels-guide.md (Tokens: ~1631 | Category: anthropic-skill) -->\n# PR Labels Guide for Release Notes\n\nThis guide is the **authoritative, self-contained mapping** from a PR's labels (or, when there is no\nuseful label, its actual change) to a release-notes category. Use it to categorize every commit/PR in\nthe release range. We do **not** rely on any automated release-drafter output to decide what goes\nwhere or which PRs are included — categorize manually using the rules below.\n\n## Category labels (drive a release-notes section)\n\nThese are the only labels that place a commit into a section. When a PR carries labels from more than\none category, **list it under every matching section** — one line per section (e.g. a PR labeled both\n`deprecation` and `breakingchange` appears in both ⚠️ Deprecations and 🔥 Breaking Changes). The\npriority order in this table is used only to pick a single section when a PR has **no** category\nlabel and you must infer one (see \"Inferring a category when there is no label\" below).\n\n| Section | Mapped labels | Classify a PR here when it... |\n| --- | --- | --- |\n| 🚀 New Features | `feature`, `enhancement` | Adds a new Redis command, client capability, public option, or a user-visible enhancement to existing behavior. |\n| 🧪 Experimental Features | `experimental` | Adds a preview/experimental capability not yet covered by compatibility guarantees (often also tagged `experimental` in code/tests). |\n| 🔥 Breaking Changes | `breakingchange` | Changes or removes any public contract: a function/method signature, argument name, default value, return type, raised exception type, wire-protocol/response shape, or persisted/connection config — anything that can break a user upgrading from the latest released version. |\n| ⚠️ Deprecations | `deprecation` | Marks a public API, option, or behavior as deprecated while keeping it working this release (typically emitting a `DeprecationWarning`). If the same PR *also* removes/changes another public contract now, it additionally appears in 🔥 Breaking Changes (list it in both). |\n| 🐛 Bug Fixes | `fix`, `bugfix`, `bug`, `BUG` | Corrects incorrect behavior, a crash, a leak, or a regression in supported functionality. |\n| 🧰 Maintenance | `maintenance`, `dependencies`, `documentation`, `docs`, `testing` | CI/release tooling, dependency bumps, docs, tests, refactors, type-hint/lint cleanups — no user-facing behavior change. |\n\n## Repo labels that need an explicit decision\n\nThese labels exist in the redis-py repo but are **not** one of the category labels above, so a PR\nlabeled only with one of them does not name its own section. Route them as follows, and add the\nproper category label to the PR when possible so the labeling is correct for future reference:\n\n- `bug-fix` — treat as **🐛 Bug Fixes** (synonym of `bug`/`fix`; prefer also adding `bug`).\n- `security` — place under **🐛 Bug Fixes** by default; if the fix changes a public contract, it is\n  **🔥 Breaking Changes** instead. Strongly consider calling it out in `## ✨ Highlights` with the\n  advisory/CVE reference regardless of section.\n- `techdebt` — **🧰 Maintenance**.\n- `deprecation` — route to the **⚠️ Deprecations** section (see the category table above). If the PR\n  removes or changes a public contract *in this release* (not just warns), it belongs in **🔥 Breaking\n  Changes** instead.\n- Topic/area labels — `async`, `cluster`, `windows`, `redis-7`, `redis-py-5`, `RediSearch`,\n  `RedisJSON`, `RedisGraph` — describe *where* a change applies, not *what kind* it is. They never\n  select a section; a PR carrying only these still needs a category decision from the rules below.\n\n## Excluded and non-release labels\n\n- `skip-changelog` — **exclude the PR from the release notes entirely.**\n- Process/triage labels never appear in release notes and are ignored for categorization:\n  `triage`, `question`, `discussion`, `duplicate`, `stale`, `help-wanted`, `good first issue`,\n  `need more info`, `needs-information`, `ready-for-merge`, `changes-requested`,\n  `waiting-for-response`, `docs-review`, `update-docs`, `run-benchmark`, `hacktoberfest-accepted`,\n  `tracking-issue`.\n\n## Inferring a category when there is no label\n\nWhen a PR/commit has no category label, infer the section from the change itself. Apply **every** rule\nthat matches — a single change can land in more than one section (e.g. it both deprecates one API and\nbreaks another, so it appears in Deprecations and Breaking Changes):\n\n1. **New Features** — adds a new Redis command, client/connection capability, or public option, or\n   a clearly user-visible enhancement. Title often starts with `feat`/`feature`/`add`. Label:\n   `feature`.\n2. **Experimental Features** — the change is explicitly a preview/experimental feature (marked\n   experimental in code, tests under the `experimental` marker, or stated as preview). Label:\n   `experimental`.\n3. **Breaking Changes** — the diff changes/removes a public signature, argument name, default,\n   return type, raised exception type, or wire-protocol/response shape against the latest release, or\n   the PR title/body says \"breaking\", \"remove\", \"drop support\". Suggested label: `breakingchange`.\n4. **Deprecations** — the change marks a public API/option/behavior as deprecated but keeps it\n   working (e.g. adds a `DeprecationWarning`, a \"deprecated\" note in a docstring, or a\n   `@deprecated` marker) without removing it this release. Suggested label: `deprecation`.\n5. **Bug Fixes** — fixes incorrect behavior, a crash, a connection/resource leak, or a regression.\n   Title often starts with `fix`. Branch named `bug-*`. Label: `bug`.\n6. **Maintenance** — everything else with no user-facing behavior change: touches only `*.md`,\n   `.github/*`, `docs/`, `tests/`, `pyproject.toml`/`dev_requirements.txt`/`docker-compose.yml`/\n   `dockers/`/`tasks.py`, dependency bumps, refactors, type-hint/lint cleanups, or CI. Branch named\n   `maintenance-*`. Label: `maintenance`.\n\nBranch-naming hints that help inference: branch `bug-*` → Bug Fixes; branch `maintenance-*` →\nMaintenance; branch `feature-*` → New Features; changes touching only `*.md` or `.github/*` →\nMaintenance.\n\nWhen the right category is genuinely ambiguous, record the uncertainty in the draft (a `<!-- TODO:\nconfirm category for #N -->` comment) rather than silently guessing, and surface it to the user so\nthe PR can be labeled correctly.\n\n## sync/async note\n\nA single behavior change is frequently delivered as paired sync/async work, usually in a single PR.\nIf two separate PRs (one sync, one async) implement the same change, treat them as one change: group\nthem onto a single line under the same category, with both PR numbers in one bracket group\n(`- <title> (#3434 #3456)`), per the grouping rule in `release-notes-template.md`.\n\n<!-- File: .agents/skills/generate-release-notes/references/release-notes-template.md (Tokens: ~1185 | Category: anthropic-skill) -->\n# Release Notes Template\n\nThis is the canonical structure for a redis-py GitHub release: the label-driven category sections\nplus the hand-authored sections maintainers add before publishing. Use it as the skeleton when\nassembling a draft. The label→category rules live in `pr-labels-guide.md`; this file owns the section\norder, titles, line format, and full skeleton.\n\n## Section order\n\nThe canonical sections, in the order they must appear, and the PR labels that route a commit into\neach (see `pr-labels-guide.md` for the full classification rules):\n\n| Order | Section title | PR labels that route a commit here |\n| --- | --- | --- |\n| 1 | `## 🚀 New Features` | `feature`, `enhancement` |\n| 2 | `## 🧪 Experimental Features` | `experimental` |\n| 3 | `## 🔥 Breaking Changes` | `breakingchange` |\n| 4 | `## ⚠️ Deprecations` | `deprecation` |\n| 5 | `## 🐛 Bug Fixes` | `fix`, `bugfix`, `bug`, `BUG` |\n| 6 | `## 🧰 Maintenance` | `maintenance`, `dependencies`, `documentation`, `docs`, `testing` |\n\nRules:\n\n- **Omit empty sections.** A category with no commits is not rendered (see the patch releases below).\n- **Each line** uses the change template `- $TITLE (#$NUMBER)` — the PR title verbatim, then a space, then `(#<pr-number>)`.\n- **Group PRs that deliver the same feature/change onto a single line**, with every related PR number\n  in one space-separated bracket group: `- <title> (#3434 #3456 #3467)`. Use the clearest title for\n  the combined change (usually the primary PR's). This is common for paired sync/async PRs, a\n  feature split across follow-ups, or a fix plus its test/typing follow-up. List the PR numbers in\n  ascending order. Only group PRs that are genuinely the same change — do not merge unrelated work to\n  shorten the list. If the grouped PRs would land in different categories, place the combined line in\n  each matching section (same one-line-per-section rule below).\n- **Exclude** any PR labeled `skip-changelog`.\n- A commit whose PR carries labels from more than one category is listed under **every matching section** — one line per section (e.g. a PR labeled both `deprecation` and `breakingchange` appears in both ⚠️ Deprecations and 🔥 Breaking Changes). The priority order is used only to infer a single section for a PR that has no category label.\n- For any commit/PR with **no category label**, infer the section using `pr-labels-guide.md` before placing it.\n\n## Optional hand-authored sections\n\nThese are written by maintainers, not derived from labels. Include them only when the release warrants it.\n\n- `## ✨ Highlights` — include this **only when the release has something significant worth focusing\n  the reader on**: a notable new feature, or a breaking change that may affect a large share of users.\n  Use `###` subsections with prose describing each highlight, with code/API names and links to\n  docs/specs. Routine patch releases have no Highlights.\n- A lead prose paragraph immediately under `# Changes` (no heading) summarizing a headline change and\n  linking the migration guide. (See the 8.0.0b2 pre-release.)\n- Highlights/lead prose go **above** the generated category sections.\n\n## Contributors footer\n\nEnd with the thank-you line followed by the de-duplicated list of contributor GitHub handles\n(`@handle`), space-separated, for everyone whose PR is included:\n\n```\nWe'd like to thank all the contributors who worked on this release!\n@handle1 @handle2 @handle3\n```\n\n## Full skeleton\n\n```markdown\n# Changes\n\n## ✨ Highlights\n\n### <Notable feature name>\n\n<Prose: what it is, the public API/class/option names, and links to docs or specs. Omit this whole\nsection for routine patch releases.>\n\n## 🚀 New Features\n\n- <PR title> (#<n>)\n\n## 🧪 Experimental Features\n\n- <PR title> (#<n>)\n\n## 🔥 Breaking Changes\n\n- <PR title> (#<n>)\n\n## ⚠️ Deprecations\n\n- <PR title> (#<n>)\n\n## 🐛 Bug Fixes\n\n- <PR title> (#<n>)\n\n## 🧰 Maintenance\n\n- <PR title> (#<n>)\n\nWe'd like to thank all the contributors who worked on this release!\n@handle1 @handle2\n```\n\n## Worked examples from published releases\n\n### Minor/major release (8.0.0-style)\n\n`# Changes` → `## ✨ Highlights` (several `###` prose subsections covering async cluster pubsub,\nkeyspace notifications, RESP3-by-default, type-hint overloads, connection/retry defaults) →\ncategory sections → contributors footer.\n\n### Pre-release (8.0.0b2-style)\n\n`# Changes` → lead prose paragraph on the headline breaking change with a migration-guide link →\nNew Features → Breaking Changes → Maintenance → contributors footer.\n\n### Patch release (8.0.1-style)\n\n`# Changes` → `## 🐛 Bug Fixes` → `## 🧰 Maintenance` → contributors footer. No Highlights, and\nempty categories (Breaking, Experimental, New Features) are omitted entirely.\n\n<!-- File: .agents/skills/generate-release-notes/SKILL.md (Tokens: ~2095 | Category: anthropic-skill) -->\n---\nname: generate-release-notes\ndescription: Generate a redis-py GitHub release-notes draft and save it as a Markdown file. Collects the merged PRs/commits since the previous release, maps each commit to its PR labels, categorizes them into the project's release sections (Breaking Changes, Deprecations, Experimental Features, New Features, Bug Fixes, Maintenance) using the skill's pr-labels-guide, infers a category for unlabeled PRs, and assembles the notes with an optional Highlights section and a contributors footer. Trigger this skill whenever the user says \"release notes\", \"create release notes\", \"generate release notes\", \"draft release notes\", \"prepare release notes\", \"changelog\", or asks for the release notes/changelog of a specific version or branch (e.g. \"release notes for 8.0\", \"generate release notes for the 8.0 branch\", \"what changed since the last release\"). A phrase like \"release notes for 8.0\" means: use branch `8.0` as the release-branch input. Also use when asked to summarize what changed since the last tag.\n---\n\n# Generate Release Notes\n\n## Objective\n\nProduce a redis-py release-notes draft that matches the structure of the project's published GitHub\nreleases, derived from the merged PRs on a given **release branch** since the previous release and\ncategorized by PR label. The release branch is the skill's primary input (step 1). Save the result as a Markdown file. Never publish it — drafting only; the user publishes the release themselves.\n\n**Do not rely on any automated release-drafter output.** We do not use it to decide which PRs are\nincluded or how they are categorized — it misses PRs and miscategorizes them. Collect the PRs\nyourself from the git history (step 2) and categorize every one of them manually using this skill's\nreferences, which are the complete and authoritative rules:\n\n- `references/release-notes-template.md` — the section order, titles, line format, and full skeleton.\n- `references/pr-labels-guide.md` — the authoritative label→category mapping, what each label means,\n  and how to categorize a PR/commit that has no category label.\n\n## Constraints\n\n- **Read-only GitHub access only.** Per the repository owner's standing rule, never run `gh` (or any\n  GitHub API call) that writes — no creating/editing releases, comments, labels, or tags — unless the\n  user explicitly authorizes that specific action in the same turn. Use `gh ... view/list`,\n  `git log`, or GET-only `gh api`/`curl` to gather data. `gh` may fail with a TLS error inside the\n  command sandbox; run it with the sandbox disabled when needed.\n- This skill **only writes the draft Markdown file** in the working tree. It does not commit, tag,\n  push, or create a GitHub release.\n\n## Workflow\n\n### 1. Determine the release range and version\n\n1. **Read the release branch as input.** The branch the release is cut from is the primary input to\n   this skill — it defines which changes are included. Take it from the skill invocation/args if\n   provided; otherwise ask the user for it (e.g. `8.0`, `release/8.0.2`, `master`). Do not guess.\n   Verify it exists (`git rev-parse --verify <branch>`; fetch first if it is a remote branch,\n   `origin/<branch>`). Every change to include is a commit reachable from this branch and not from\n   the previous release tag.\n2. Identify the previous release tag: the most recent `v*` tag reachable from the release branch,\n   `git describe --tags --abbrev=0 <branch>` (or `gh release list -L 5`, read-only). Confirm with the\n   user if a non-default base is intended.\n3. Decide the new version. redis-py ships major, minor, and patch releases (e.g. `8.0.1`, `7.4.1`).\n   Infer minor vs patch from the change set (any Breaking/New Feature → at least a minor bump) and\n   confirm the exact version with the user if unclear. The tag form is `v<version>`.\n\n### 2. Collect the merged PRs / commits in range\n\n1. List the commits on the release branch since the previous tag:\n   `git log --oneline --no-merges <prev-tag>..<branch>`. Each squashed/merged commit title typically\n   ends with `(#<pr-number>)`; extract that number. For merge-commit workflows, use\n   `git log --merges` or the GitHub compare API instead.\n2. For each PR number, fetch its **title, labels, and author** (read-only):\n   `gh pr view <n> --json number,title,labels,author` or\n   `gh api repos/redis/redis-py/pulls/<n>`. Use the PR title for the change line (it is what the\n   published notes use), not the raw commit subject, when they differ.\n3. Drop any PR labeled `skip-changelog`.\n4. If a commit has no associated PR (direct push), use the commit subject and treat it as unlabeled.\n\n### 3. Categorize each commit\n\nFor each remaining commit, categorize it using `references/release-notes-template.md` and\n`references/pr-labels-guide.md`:\n\n- If the PR has one or more **category labels**, list it under **every** matching section — a PR with\n  two category labels appears in both (e.g. `deprecation` + `breakingchange` → both ⚠️ Deprecations\n  and 🔥 Breaking Changes).\n- If the PR has **no category label**, or only topic/process labels, or a non-category repo label\n  (e.g. `bug-fix`, `security`, `techdebt`), infer the section(s) using\n  `references/pr-labels-guide.md` — apply **every** inference rule that matches, so a change may land\n  in more than one section. Note the inferred labels you would suggest applying to the PR.\n- When a category is genuinely ambiguous, place your best guess and leave a\n  `<!-- TODO: confirm category for #<n> -->` marker, then surface it to the user.\n\n### 4. Assemble the draft\n\nFollow `references/release-notes-template.md`:\n\n1. Start with `# Changes`.\n2. Optionally add `## ✨ Highlights` (or a lead prose paragraph) — hand-authored `###` subsections\n   describing each highlight with API names and doc/spec links. Include it **only when the release has\n   something significant to focus the reader on**: a notable new feature, or a breaking change that\n   may affect a large share of users. Omit for routine patch releases. When the release contains\n   Breaking Changes or notable New Features, ask the user whether highlights are wanted.\n3. Emit the category sections **in the order from `release-notes-template.md`** (🚀 New Features,\n   🧪 Experimental, 🔥 Breaking Changes, ⚠️ Deprecations, 🐛 Bug Fixes, 🧰 Maintenance), omitting any\n   empty section. Each line is `- <PR title> (#<n>)`. **Combine PRs that deliver the same\n   feature/change onto one line** with all their numbers in a single bracket group in ascending order,\n   e.g. `- <title> (#3434 #3456 #3467)` (common for paired sync/async PRs or a feature split across\n   follow-ups) — see the grouping rule in `references/release-notes-template.md`.\n4. End with the contributors footer: the thank-you line and the de-duplicated, space-separated list\n   of `@<author>` handles for every included PR.\n\n### 5. Save the draft as a local Markdown file\n\nWrite the assembled notes to a **local Markdown file** so the maintainer can review it and copy the\ncontents into the GitHub release UI by hand. This file is a local review artifact only:\n\n- Default path: `release_notes/release_notes_<version>.md` at the repo root (e.g.\n  `release_notes/release_notes_8.0.2.md`). Create the `release_notes/` folder if it does not exist.\n  Confirm or accept an override path from the user.\n- **Do not commit, tag, push, or create/edit a GitHub release.** The skill only writes the file in\n  the working tree; the maintainer reviews it and publishes manually. (If the `release_notes/` folder\n  is not already git-ignored, mention that so it is not accidentally committed.)\n- The file content is exactly what should be pasted into the release body — no extra wrapper or\n  commentary inside the file.\n\nAfter writing, report the file path and a short summary: the version, the previous tag, the\nper-section commit counts, the count of PRs whose category was inferred (and which ones), and any\n`TODO` markers left in the draft.\n\n## Verification\n\n- Cross-check the commit count: the number of **distinct PR numbers** referenced across all change\n  lines (a grouped line contributes all of its numbers), plus any `skip-changelog`/excluded PRs,\n  should equal the commits in `git log <prev-tag>..<branch>` (minus merge commits). Grouping related\n  PRs reduces the number of lines but not the number of PR references, so reconcile on PR numbers, not\n  lines.\n- Confirm section order and titles match `references/release-notes-template.md` and no empty section\n  was emitted.\n- Confirm every included PR number resolves to a real PR and every contributor handle is included\n  once.\n- Spot-check against the previous published release's formatting (run `gh release view <prev-tag>`).\n\n<!-- File: .agents/skills/maintainer-review/references/evaluation-framework.md (Tokens: ~5253 | Category: anthropic-skill) -->\n# Maintainer Evaluation Framework\n\nUse this reference when a claim is ambiguous, severity is disputed, or a technically correct PR may not justify permanent maintenance.\n\n## Contents\n\n- [Decision model](#decision-model)\n- [Severity](#severity)\n- [Evidence strength](#evidence-strength)\n- [Unmet need and alternative design gate](#unmet-need-and-alternative-design-gate)\n- [Issue disposition](#issue-disposition)\n- [PR quality and value](#pr-quality-and-value)\n- [Documentation threshold](#documentation-threshold)\n- [Lifecycle and failure paths](#lifecycle-and-failure-paths)\n- [Concurrency and cleanup ownership](#concurrency-and-cleanup-ownership)\n- [Better-alternative prompts](#better-alternative-prompts)\n- [Competing pull requests](#competing-pull-requests)\n- [Maintainer comments](#maintainer-comments)\n- [Compact report variants](#compact-report-variants)\n\n## Decision Model\n\nTreat validity, severity, and merge-worthiness as separate outputs. Also distinguish a `Preliminary assessment`, which may still require approved runtime evidence, from a final `Maintainer decision`. Do not label a provisional positive result as a verdict or final decision.\n\n| Dimension | Question | Strong evidence |\n| --- | --- | --- |\n| Claim validity | Does the exact behavior occur, and is the proposed cause correct? | Reproduction, failing focused test, or complete reachable path |\n| Reachability | Can supported realistic inputs reach it? | Public API trace, real configuration, user report, or release comparison |\n| Consequence | What fails, and is it silent or recoverable? | Observed output/error/state and downstream effect |\n| Breadth | Which stacks (sync/async), protocols (RESP2/RESP3, `legacy_responses`), topologies (standalone/cluster/sentinel), parsers (pure-Python/hiredis), and versions are affected? | Explicit path and compatibility matrix |\n| Frequency | Is it normal, intermittent, or pathological? | Repeats, deterministic preconditions, reports, or telemetry |\n| Need evidence | Is the exact scope demonstrated, merely plausible, already covered, or unsupported? | Same-scope user scenario, real-path reproduction, released compatibility requirement, violated supported contract, repeated demand, or broad consequential invariant |\n| Unmet need | What user outcome cannot be achieved through supported behavior today, or what supported contract is violated? | Concrete scenario plus a trace showing why the closest existing path is insufficient or defective |\n| Existing capability | Can configuration, composition, client/pool options, callbacks, `response_callbacks`, retry strategies, an existing command mixin, or a caller-owned layer already satisfy the outcome? | Current release code, tests, docs, and an exact supported workflow |\n| Compatibility | Is released public API, signature, default, return type, error/exception type, wire-protocol handling, or connection/config behavior changed? | Latest released (PyPI) comparison and contract inspection |\n| Solution fit | Is the requested mechanism the best design and implementation layer? | Proposed solution compared with the strongest existing path and at least one narrower or more coherent alternative |\n| Resource ownership | Can stale, failed, cancelled, or overlapping work mutate or clean up resources owned by surviving work (pool connections, locks, slot maps, pub/sub state)? | Interleaving trace, attempt or generation ownership, and survivor assertions |\n| Maintenance cost | What permanent complexity and review burden is added? | New branches/configuration, changed surface, tests across stacks/protocols, and remaining work |\n\n## Severity\n\n- **Negligible**: no runtime difference, unreachable/unsupported input, cosmetic inconsistency, or harmless edge case. Usually close, document, or decline code complexity.\n- **Low**: real but narrow and recoverable behavior with a simple workaround and no data, security, or compatibility risk. Merge only when the fix is small and strengthens an invariant.\n- **Moderate**: supported use fails or produces incorrect behavior for a meaningful subset (one stack, one protocol, one topology). Prioritize a bounded fix and regression test.\n- **High**: common or important use is broken, released compatibility is seriously affected, sensitive data can leak, connections/pools leak or corrupt, or persistent corruption is possible. Require urgent strong validation.\n- **Critical**: broadly exploitable security impact, severe data loss, or systemic failure requiring coordinated action. Use only with concrete evidence.\n\nSeverity is consequence multiplied by realistic reach and frequency, reduced by recoverability. Do not raise it because prose is alarming or lower it because a diff is small.\n\n## Evidence Strength\n\nBefore calling a claim confirmed, answer:\n\n- Does the reproduction exercise the same public/internal path, stack (sync/async), protocol, topology, and parser?\n- Does failure occur on the relevant base, latest release, or target?\n- Does the regression test fail without the patch and pass with it?\n- Are stale build/`dist`, wrong virtualenv/imports, dependency drift (including hiredis presence/version), proxies, caches, runtime conditions, unavailable Docker/sandbox, authentication, quota, and Redis-server-version differences excluded?\n- Does an equivalent sync/async, RESP2/RESP3, `legacy_responses`, standalone/cluster/sentinel, or hiredis/pure-Python path differ?\n- Is behavior prohibited by a real contract (public API, documented Redis command semantics, error/exception type, wire protocol) or merely surprising?\n- For latency, timeout, buffering, backpressure, or cleanup, was observable time or state measured rather than inferred only from mocks?\n- For shared asynchronous state, do tests control completion order and prove that a stale failure or cleanup cannot affect the surviving operation?\n\nUse `partially confirmed` when the symptom is real but cause/reach/scope is wrong. Use `unproven` when decisive evidence is missing. Use `contradicted` only when evidence directly disproves the claim.\n\n## Unmet Need and Alternative Design Gate\n\nIssue reports often combine a desired outcome with a proposed API or implementation. Treat the proposed mechanism as a hypothesis. Confirm the unmet outcome or violated supported contract before evaluating how well the patch implements that mechanism.\n\n### Linked-evidence scope\n\nEvidence from a linked issue applies only when the issue and PR share the same runtime variant — stack (sync/async), protocol, `legacy_responses` setting, topology, command, parser — trigger, supported configuration, and user outcome. A broad title, ordinary reference, `Related to` statement, or conceptual similarity is not enough. If an earlier change already resolved the concrete reported scenario, an adjacent variant (the other stack, another protocol, another topology) starts with no inherited evidence of need.\n\n### Need evidence status\n\nAssign one status before deep implementation review:\n\n- **Demonstrated**: The exact scope has a concrete supported scenario, real-path reproduction, released compatibility requirement, violated supported contract, repeated demand, or broad invariant with meaningful consequence.\n- **Plausible but unproven**: The code path is possible, but realistic reach, frequency, consequence, Redis-server behavior, or demand is missing.\n- **Already covered**: A reasonable supported workflow already satisfies the outcome.\n- **Unsupported**: The outcome is outside redis-py's contract or belongs at the Redis server, a caller-owned, or an application layer.\n\nOnly `Demonstrated` need can support a merge-worthy code recommendation. `Plausible but unproven` maps to `Needs evidence` or `Not worth completing`, even when the patch is technically correct and its remaining fixes are bounded. `Already covered` and `Unsupported` normally map to closure or a simpler non-core alternative.\n\nBefore accepting an issue or recommending a PR, record:\n\n| Question | Required evidence |\n| --- | --- |\n| What outcome is needed? | A concrete supported scenario stated without the proposed API or fix |\n| What exists today? | The closest current-release API, configuration, composition, extension point, or caller-owned solution |\n| Why is it insufficient? | An exact behavioral, compatibility, lifecycle, or operational constraint, not preference alone |\n| What are the alternatives? | The proposed patch, the strongest existing path, and at least one no-code, narrower, or better-layer design |\n| Why add a contract? | Practical benefit sufficient to justify public surface, runtime branches, cross-stack/protocol tests, documentation, and long-term maintenance |\n\nClassify the result:\n\n- **Capability gap**: a supported, realistic outcome cannot be achieved with current functionality. Code may be warranted.\n- **Ergonomics or discoverability gap**: the outcome is already possible, but the supported route is confusing or unnecessarily difficult. Prefer documentation, validation, or a narrowly justified convenience improvement.\n- **Unsupported use case**: the desired outcome lies outside redis-py's contract or belongs at the Redis server, application, or other caller-owned layer. Do not expand the core API merely to make it possible.\n- **No demonstrated gap**: no concrete scenario proves that existing functionality is insufficient. Request evidence or close rather than designing from the proposed mechanism.\n- **Defect in supported behavior**: the existing path violates a documented or established correctness, security, compatibility, or lifecycle contract (including Redis command semantics, error/exception types, and sync/async parity). A workaround may affect priority or solution shape, but does not erase the defect.\n\nPassing tests for a new implementation establish feasibility and correctness, not need. A mock connection, `FakeRedis`/monkeypatched response, manually constructed RESP payload, mock, or synthetic fixture does not establish realistic Redis-server behavior, user reach, frequency, consequence, or demand. API symmetry and parity with an adjacent command, protocol, topology, or the other stack are design arguments, not need evidence. A technically coherent patch can still be `Not worth completing` when the motivating scenario is hypothetical, already supported, or better solved elsewhere.\n\nUse the counterfactual maintainer test: if the PR did not already exist, would maintainers choose to file and implement the same work from the available evidence? Contributor effort lowers implementation cost but does not create product need or remove permanent maintenance cost.\n\nWhen the need is not `Demonstrated`, inspect implementation only far enough to estimate contract, risk, and maintenance cost. Do not convert patch defects, missing tests, or documentation gaps into a request-changes disposition; those become merge blockers only after the need gate passes.\n\n## Issue Disposition\n\nChoose one:\n\n- **Prioritize**: confirmed moderate-or-higher impact or important invariant with no safe workaround.\n- **Accept, low priority**: confirmed low impact, existing supported functionality is insufficient or defective for the demonstrated scenario, and a proportionate fix is plausible.\n- **Narrow scope**: valid core, overstated paths or expected behavior.\n- **Needs evidence**: plausible but missing a supported reproduction, contract basis, or concrete scenario showing why existing functionality is insufficient.\n- **Close**: duplicate, unsupported, unreachable, contradicted, no-op, already addressed by a reasonable supported path, or not worth permanent complexity.\n\nAsk only for evidence that could change the disposition.\n\n## PR Quality and Value\n\nAssess independently:\n\n1. **Need**: same-scope evidence demonstrates a concrete unmet user outcome or defect in supported behavior, and the closest supported capability cannot reasonably satisfy the scenario as-is. Do not inherit evidence from an adjacent variant (other stack, protocol, topology) or already-fixed scenario.\n2. **Correctness**: the fix covers the claim and meaningful boundaries, and preserves Redis command semantics and error/exception types.\n3. **Placement**: the invariant is enforced once at the owning layer (connection, pool, parser, callback, command mixin) instead of duplicating existing functionality, patching locally, or moving server- or application-owned policy into the client.\n4. **Consistency**: equivalent sync/async, RESP2/RESP3, `legacy_responses`, standalone/cluster/sentinel, parser, and serialization paths stay aligned. Sync/async parity is a repository requirement, not optional polish.\n5. **Tests**: a regression test fails on base, passes on head, and asserts the non-happy-path value/state; protocol-sensitive changes run under both `--protocol 2` and `--protocol 3`. When shared state crosses an asynchronous boundary, tests control relevant completion orders and assert the surviving operation's behavior and final resource state.\n6. **Compatibility**: released public API (signatures, argument names, defaults, return types), error/exception types, wire-protocol handling, packaging extras, and connection/config behavior are preserved or intentionally migrated against the latest release.\n7. **Proportionality**: public surface and complexity match impact.\n8. **Completion cost**: remaining code, tests, docs, design, the missing sync or async mirror, and conflict work is bounded enough to justify attention.\n\nA PR can be correct but not merge-worthy because the need is negligible, the outcome is already supported through a reasonable existing mechanism, the real path is unchanged, equivalent paths remain inconsistent, the abstraction costs more than the benefit, or a simpler design exists at another layer.\n\nDo not use implementation correctness, bounded remaining work, CI status, or contributor effort to upgrade a need that is only `Plausible but unproven`. Merge-worthiness is gated by demonstrated need, not by how close the patch is to completion.\n\nKeep issue severity separate from `Patch risk`. A patch-induced regression, compatibility break, connection/resource leak, lock-release-across-await hazard, or maintenance hazard does not make the underlying issue more severe.\n\n## Documentation Threshold\n\nMake docs merge-blocking only when:\n\n- Existing docs or docstrings become materially false, unsafe, or misleading.\n- Safe/correct use depends on a non-obvious constraint, migration, compatibility boundary, or operational warning.\n- Repository policy, accepted scope, or an explicit maintainer decision requires docs in the same PR.\n- The feature is practically unusable or undiscoverable without a user-facing entrypoint and generated/API discovery is insufficient.\n\nKeep optional discoverability/completeness non-blocking. Do not downgrade a code recommendation solely for optional docs or include optional docs in a required-action paragraph.\n\n## Lifecycle and Failure Paths\n\nApply this section when a change adds validation, fail-fast behavior, cleanup, retry, interruption, background work, connection handling, or concurrency.\n\n- Identify the earliest point where all dynamic inputs required for a correct decision exist.\n- List side effects before and after that point: pool connections, sockets, coroutines/tasks, locks, caches, state, persistence, and telemetry/observability spans.\n- Exercise failure during construction, connection, validation, command execution, persistence, and teardown where those phases exist.\n- Confirm normal teardown is actually entered. If construction/connect fails, verify explicit cleanup (connection returned to or evicted from the pool, lock released, task cancelled).\n- Prefer validation after dynamic configuration is resolved but before avoidable side effects begin.\n- Require a regression test for any connection, coroutine, lock, socket, or state that can remain after failure.\n\n## Concurrency and Cleanup Ownership\n\nApply this section before a positive assessment whenever lifecycle work crosses an `await`, callback, event, deferred completion, retry, reconnect, cancellation, or shared resource boundary. Sequential correctness is insufficient because the patch can improve isolated cleanup while introducing cross-attempt teardown. In redis-py this most often involves connection-pool acquire/release, async locks held across awaits, pub/sub reconnect, cluster slot-map updates, and multidb failover.\n\nUse a two-operation interleaving matrix during desk review:\n\n| Ordering | Required question |\n| --- | --- |\n| `A pending -> B starts -> A fails -> B succeeds` | Can A's cleanup remove or revert anything B needs (a pool connection, a slot map, a lock)? |\n| `A pending -> B starts -> B fails -> A succeeds` | Can B's cleanup leave A successful but non-functional? |\n| `A succeeds -> B starts -> stale A completion` | Can stale A overwrite B's newer state or generation (slot map, reconnect, config)? |\n| setup -> close/cancel -> late completion | Can late work resurrect connections, listeners, state, or tasks after teardown? |\n\nFor each ordering:\n\n- Identify the resource owner before and after every suspension point.\n- Distinguish per-attempt resources from shared pool, connection, slot-map, cache, or pub/sub state.\n- Require cleanup to carry an ownership token, generation, identity check, serialization guarantee, or another invariant that prevents cross-attempt disposal. Keep async locks held across the awaits inside a pool mutation so other tasks cannot observe in-between state.\n- Compare base and head on the survivor invariant. Fewer duplicates do not justify losing the only active connection, task, or state update.\n- Require a controlled interleaving test when the ordering is reachable. The test must assert both the failing operation and the surviving operation's observable behavior after all completions settle.\n\nAn unscoped `finally`, `except`, close handler, cancellation callback, or rollback that mutates shared state after a suspension point is merge-blocking when another operation can still own or use that state.\n\n## Better-Alternative Prompts\n\nStart with the strongest existing supported path, then test at least one additional alternative against the proposed patch. Do not complete a positive review without this comparison.\n\n- Can the requested outcome already be achieved through configuration, composition, client/pool options, callbacks, `response_callbacks`, a retry/backoff strategy, an existing command mixin, or caller-owned code?\n- If the existing route is awkward, is the problem discoverability or ergonomics rather than missing capability?\n- What happens with no code change?\n- Can input validation or an existing helper (`redis/commands/helpers.py`) enforce the invariant earlier?\n- Can the fix be limited to the supported failing path?\n- Would a clearer error/exception or documentation prevent misuse without runtime complexity?\n- Can a failing test reveal a smaller correct change?\n- Is a new public option compensating for an internal ownership problem (pool, lock, connection lifecycle)?\n- Can the same result be achieved in the parser, response callback, or state owner instead of every caller?\n- Is the proposed core behavior actually Redis-server- or application-specific policy that belongs at another layer?\n\n## Competing Pull Requests\n\nRequire an explicit issue link, same reproduction, same violated invariant, or materially overlapping runtime path (command, stack, protocol, topology) before grouping candidates.\n\n| Criterion | Question |\n| --- | --- |\n| Need | Does a concrete user outcome remain unmet, or supported behavior remain defective, after tracing existing functionality? |\n| Existing capability | Could every candidate be avoided by configuration, composition, an extension point, or a better caller-owned solution? |\n| Coverage | Whole confirmed issue, useful subset, or adjacent problem? Both stacks or only one? |\n| Correctness | Real path and meaningful boundaries? |\n| Placement | Owning shared layer? |\n| Tests | Base failure reproduced, protocols/topologies covered, and approaches distinguished? |\n| Compatibility | Released APIs, signatures, error types, protocols, packaging, connection/config? |\n| Complexity | Permanent branches, abstractions, configuration, coupling? |\n| Readiness | Mergeable now or bounded focused work (including missing sync/async mirror)? |\n| Reuse | Exact tests or ideas worth transferring? |\n\nChoose one portfolio action:\n\n- **Prefer one PR**\n- **Prefer one after focused changes**\n- **Combine selectively** into a named destination PR\n- **Replace all** with a simpler/coherent implementation\n- **Merge none**\n\nDo not issue independent approvals for overlapping candidates. State the action for every active PR.\n\n## Maintainer Comments\n\nWrite drafts in English, in chat, as copy-paste-ready text for the user to send themselves. Per the repository owner's standing rules, do not post them to GitHub or any external service, and do not impersonate the user. Produce one when recommending closure, more evidence, focused changes, superseding, or choosing among competing PRs.\n\nKeep it polite, direct, complete, and usually 60-160 words in one to three short paragraphs:\n\n1. Acknowledge the contribution/report.\n2. State the decision with decisive technical evidence.\n3. Give the exact next action or reconsideration condition.\n\nDo not include internal severity labels, speculate about authorship/intent, repeat the full review, or soften the requested action until it is unclear.\n\n### Close\n\n```text\nThanks for taking the time to investigate this. I traced the reported case through <path or behavior>, and <decisive finding>. In the supported path, <practical result>, so the added complexity is not justified by the demonstrated impact.\n\nI am going to close this <issue/PR>. If you can provide <specific reproduction or evidence that would change the decision>, we can revisit the underlying problem with that narrower scope.\n```\n\n### Request Changes\n\n```text\nThanks for the contribution. The underlying issue is valid, and this approach is directionally reasonable. Before we can merge it, please address the following points: <bounded required changes, e.g., mirror the change in the async stack under redis/asyncio/, add RESP2/RESP3 coverage, add a regression test>.\n\nThese changes are needed because <contract, lifecycle, compatibility, sync/async parity, or test reason>. Once they are covered with a regression test that fails on the base and passes on the updated branch, the PR should be ready for another review.\n```\n\nAdapt these templates to evidence. Do not use them as filler.\n\n### Existing Capability or Better Alternative\n\n```text\nThanks for the contribution. I traced the underlying use case through <existing API or workflow>, which already supports <desired outcome and relevant limits>. The proposed change adds <new contract or complexity>, but the issue does not demonstrate a concrete supported case that the existing approach cannot handle.\n\nI am going to close this <issue/PR> for now. If you can provide <specific scenario showing the existing approach is insufficient>, we can revisit the unmet need and choose the narrowest appropriate design from that evidence.\n```\n\n## Compact Report Variants\n\nUse `Maintainer decision` for a concluded review. Use `Preliminary assessment` when a desk review is tentatively positive but a decision-relevant runtime concern remains. `Verdict` is intentionally avoided in the report headings because it does not communicate whether the result is provisional or final.\n\n### Runtime Approval Gate\n\n```markdown\n## Preliminary assessment\n\n<Tentative issue or PR assessment based on desk review only.>\n\n## Static evidence\n\n- <decisive code-path or test-inspection evidence>\n- <what remains uncertain at runtime>\n\n## Proposed runtime probe\n\n- Concern: <the uncertainty that could change the decision>\n- Probe: <smallest exact execution path; note protocol/topology and whether `invoke devenv` or external infra is required>\n- Control: <base, release, or known-good comparison when relevant>\n- Scope: <local-only or any live-service, Docker, cost, mutation, or cleanup implications>\n\n## Approval request\n\n<Ask whether to run this exact probe. Do not present a final positive recommendation yet.>\n```\n\n### Issue\n\n```markdown\n## Maintainer decision\n\n<Real/partial/unproven/contradicted, severity, and disposition.>\n\n## Evidence\n\n- <decisive evidence>\n- <scope or uncertainty (which stacks/protocols/topologies)>\n\n## Existing capability and alternatives\n\n<Closest supported path, why it is or is not sufficient, and the preferred design alternative.>\n\n## Recommendation\n\n<Prioritize, accept low priority, narrow, request evidence, or close.>\n\n## Maintainer comment draft\n\n<Include for closure or an evidence request.>\n```\n\n### Pull Request\n\n```markdown\n## Maintainer decision\n\n<Need, practical impact, and merge-worthiness.>\n\n- Need evidence: <Demonstrated / Plausible but unproven / Already covered / Unsupported>\n- Code recommendation: <code disposition>\n- Repository readiness: <one allowed status; only when useful for a merge-worthy recommendation>\n\n## Evidence\n\n- <runtime/code-path result>\n- <test/compatibility result>\n\n## Existing capability and alternatives\n\n<Closest supported path, why the demonstrated scenario cannot use it or remains defective, and why this patch is preferable to no code change or a narrower design.>\n\n## Issue impact\n\n- Validity: <claim validity>\n- Severity: <underlying issue severity>\n- Reach: <realistic reach: stacks, protocols, topologies, versions>\n\n## Patch risk\n\n<Only meaningful patch-induced risk, including sync/async parity and resource-ownership hazards.>\n\n## PR quality\n\n- Solution fit: <assessment>\n- Tests: <assessment, including protocol/topology coverage>\n- Remaining effort: <bounded/unbounded and why; note any missing sync or async mirror>\n\n## Recommendation\n\n<Merge, focused changes, simpler replacement, or close.>\n\n## Maintainer comment draft\n\n<Only when closure, evidence, or changes should be requested.>\n```\n\n### Competing Pull Requests\n\n```markdown\n## Maintainer decision\n\n<Issue validity, severity, and preferred implementation path.>\n\n## Open PR comparison\n\n| PR   | Approach | Correctness | Tests | Compatibility/complexity | Readiness |\n| ---- | -------- | ----------- | ----- | ------------------------ | --------- |\n| #... | ...      | ...         | ...   | ...                      | ...       |\n\n## Recommendation\n\n<Select one, request focused changes, combine exact pieces, replace all, or merge none.> <State the action for every other active candidate.>\n\n## Maintainer comment drafts\n\n<One draft for each PR that should be closed, changed, or superseded.>\n```\n\n<!-- File: .agents/skills/maintainer-review/SKILL.md (Tokens: ~5343 | Category: anthropic-skill) -->\n---\nname: maintainer-review\ndescription: Review a GitHub issue or pull request URL as a redis-py maintainer, with a staged assessment of whether the claim is real, practically important, already solvable with supported functionality, correctly scoped, better served by another design, and worth maintainer and contributor effort. Use when assessing issue validity or severity, deciding whether an issue should be prioritized or closed, determining whether a requested feature represents an unmet need rather than a discoverability or usage gap, judging whether a PR is worth bringing to mergeable quality, comparing open PRs or alternative designs, separating code quality from repository readiness, or drafting a concise maintainer assessment. When closure, additional evidence, or code changes should be requested, also produce a polite, concise, complete, copy-paste-ready maintainer comment.\n---\n\n# Maintainer Review\n\n## Objective\n\nMake a maintainer decision, not a generic diff summary. Separate these questions:\n\n1. Is the claimed behavior real?\n2. What user outcome or constraint exists independently of the reporter's proposed API or fix?\n3. Can supported functionality already achieve that outcome with reasonable composition or configuration?\n4. If a gap remains, is the proposed solution the best design and implementation layer?\n5. Can supported users plausibly reach the gap, and what happens when they do?\n6. Is it important enough to act on now?\n7. If this PR did not already exist, would maintainers choose to open and implement the same work?\n8. For a PR, is this solution worth merging and maintaining?\n9. Can overlapping or stale operations corrupt shared state or clean up resources owned by surviving work?\n10. If competing PRs exist, which single implementation path should maintainers pursue?\n11. What concise maintainer message should communicate closure, an evidence request, or required changes?\n\nTreat an issue's requested keyword argument, callback, flag, class, or implementation strategy as a proposed mechanism, not as the accepted requirement. Do not begin by asking how to implement it. First establish either a concrete unmet user outcome or a violated supported contract — the public API surface, documented Redis command semantics, error/exception types, or wire-protocol behavior — then prove that the proposed mechanism is better than the available alternatives.\n\nLead with the current review state. Use `Preliminary assessment` while runtime approval or evidence is pending, and `Maintainer decision` only when the review can be concluded. Use the diff, issue narrative, and contributor effort as evidence, not as proxies for impact.\n\n## redis-py context to hold throughout\n\nThis library ships two parallel stacks (sync under `redis/`, async under `redis/asyncio/`) that must stay aligned, and exercises behavior across several orthogonal axes. Keep these as the runtime-variant matrix whenever you reason about reach, consistency, or \"the exact same scenario\":\n\n- **Sync vs async**: every behavior question has a sync (`redis/...`) and async (`redis/asyncio/...`) variant; tests mirror under `tests/` and `tests/test_asyncio/`. A defect or fix on one side usually has a counterpart on the other (`specs/sync_async_deduplication_analysis.md`).\n- **Wire protocol vs response shape**: `protocol=2|3` chooses RESP2/RESP3 on the wire; `legacy_responses=True|False` chooses the Python response shape independently. Both axes are exercised in CI (`invoke run-test-matrix`; see `specs/unified_responses_migration_guide.md`). A claim or fix may behave differently across these four combinations.\n- **Topology**: standalone, cluster (`redis/cluster.py`), and sentinel (`redis/sentinel.py`) have distinct routing, connection, and failure behavior. Cluster adds slot mapping, MOVED/ASK redirection, metadata-driven replica routing (`MetadataResolver.is_replica_safe`), and `RequestPolicy`/`ResponsePolicy` resolution.\n- **Parser backend**: pure-Python (`redis/_parsers/resp2.py`, `resp3.py`) vs optional C-accelerated `hiredis`. Behavior must match across both.\n- **Public API compatibility**: signatures, argument names, defaults, return types, error/exception types, and `response_callbacks` shaping are contracts. The compatibility boundary is the latest released version on PyPI, not unreleased branch churn.\n\n## Workflow\n\n### 1. Establish the exact remote target\n\n- Accept a GitHub issue or PR URL as the primary input. Resolve owner, repository, item type, and number before reviewing it.\n- For an issue, read the full report, comments, reproduction, environment (redis-py version, Python version, Redis server version, topology, protocol, hiredis present or not), linked material, and maintainer responses.\n- For a PR, inspect the current remote base and head, full patch, commit history when relevant, tests, linked issue, and review discussion. Do not substitute the current local checkout for the remote change.\n- State the claim in one falsifiable sentence. Separate the observed symptom from the proposed cause or fix.\n- Identify the latest released boundary (the current redis-py release on PyPI) when compatibility or regression claims matter.\n- Verify whether linked evidence matches the PR's exact runtime variant — sync or async, RESP2 or RESP3, `legacy_responses` setting, standalone/cluster/sentinel topology, parser backend — triggering condition, and user outcome. A generic issue title, conceptual similarity, or wording such as `Related to` does not transfer evidence of need to an adjacent variant. If the reported scenario has already been fixed, treat additional variants (the other stack, another protocol, another topology) as new needs requiring their own evidence.\n\nUse read-only GitHub access. Per the repository owner's standing rule, never run `gh` (or any GitHub API call) that writes — no comments, labels, reviews, approvals, branch changes, pushes, or merges — unless the user explicitly authorizes that specific action in the same turn. Read-only inspection (`gh ... list/view/diff/status`, GET-only `gh api`) is fine. A review never authorizes remote writes. Note: `gh` may fail with a TLS error inside the command sandbox; run it with the sandbox disabled when needed.\n\n### 2. Establish the unmet need and challenge the proposed solution\n\nComplete this pass before deeply evaluating a proposed implementation and before any positive issue or PR assessment.\n\nFirst assign one `Need evidence` status:\n\n- **Demonstrated**: The exact scope has a concrete supported scenario, a real-path reproduction, a released compatibility requirement, a violated supported contract (public API, Redis command semantics, error/exception type, or wire-protocol behavior), repeated demand, or a broad invariant with a meaningful consequence.\n- **Plausible but unproven**: The path can exist, but realistic Redis-server behavior, user reach, frequency, consequence, or demand is not established.\n- **Already covered**: A reasonable supported workflow already satisfies the outcome.\n- **Unsupported**: The outcome belongs outside redis-py's contract or at the Redis server, a caller-owned layer, or an application concern.\n\nOnly `Demonstrated` need may receive `Merge-worthy as-is` or `Merge-worthy after focused changes`. For `Plausible but unproven`, prefer `Needs evidence` or `Not worth completing`; for `Already covered` or `Unsupported`, prefer closure or the relevant simpler alternative.\n\n1. Restate the desired user outcome without naming the requested API, class, file, option, or implementation. Separate the actual constraint from the reporter's preferred mechanism.\n2. Trace the closest supported ways to achieve that outcome in the current release across the relevant stack. Inspect the owning code path, public API, tests, and relevant docs rather than assuming that an unfamiliar capability is missing. Consider configuration, composition, client/connection-pool options, callbacks, `response_callbacks`, retry/backoff strategies, existing command mixins, and caller-owned code.\n3. Determine whether the report shows a capability gap, an ergonomics or discoverability gap, an unsupported use case, or no demonstrated gap. A more convenient spelling is not automatically a missing capability.\n4. Compare the proposed solution against the strongest existing approach and at least one better-design candidate: no code change, clearer documentation or validation, a narrower fix, reuse of an existing abstraction or helper (`redis/commands/helpers.py`, existing parser/callback hooks), or enforcement at a more coherent shared boundary.\n5. For each viable approach, compare whether it satisfies the concrete scenario, what new public or internal contract it creates, cross-path consistency (sync/async, protocol, topology), compatibility, and permanent maintenance cost.\n\nDo not treat a test proving that new code can work as evidence that the feature is needed. A mock connection, a `FakeRedis`/monkeypatched parser response, a manually constructed RESP payload, a mock, or a new regression test can establish code-path reachability and implementation correctness; it does not by itself establish realistic Redis-server behavior, user reach, frequency, practical consequence, or demand.\n\nAPI symmetry, naming consistency, and parity with an adjacent command, topology, protocol, or the other stack are design arguments, not evidence of need. Parity may justify work when it removes existing complexity or enforces a broad demonstrated invariant (and sync/async parity is itself a repository invariant), but adding branches, tests, documentation, or public behavior requires independent practical justification.\n\nIf the need is not `Demonstrated`, inspect the patch only far enough to understand its contract, risk, and maintenance cost. Do not turn implementation defects, missing tests, or documentation gaps into a request-changes recommendation, because those questions become merge-blocking only after the need gate passes. If the report provides no concrete scenario, the existing functionality appears sufficient, or the requested mechanism solves only a hypothetical convenience problem, prefer `Needs evidence`, `Close`, `Supersede with a simpler alternative`, or `Not worth completing` over designing the requested feature on the reporter's behalf.\n\nAn existing workaround may change priority or solution shape, but it does not by itself erase a demonstrated correctness, security, compatibility, or lifecycle defect in supported behavior. Evaluate both the unmet outcome and the violated contract.\n\n### 3. Discover competing open PRs proportionally\n\nDo this before deeply evaluating a specified PR. A PR URL selects the starting point, not necessarily the entire comparison set.\n\n- Determine the primary issue from explicit closing keywords, linked issues, timeline/development links, PR body/comments, and the reproduced symptom. State when association is inferred.\n- When an issue is explicitly linked, enumerate all open PRs that address it through cross-references, closing keywords, and development links. Include drafts but label them.\n- When no issue is linked, run a bounded duplicate search using the strongest signals from the title, reproduction, violated invariant, and runtime path (command, topology, protocol, sync/async).\n- Exclude closed/merged PRs from the active comparison set while using them as history when relevant.\n- Require a shared issue, symptom, violated invariant, or materially overlapping path. A shared area label is not enough.\n- If repository access cannot establish completeness, say so instead of claiming every candidate was found.\n\nThe repository also has an `analyze-open-prs` skill for triaging the whole PR queue; use this skill for the focused single-target comparison and that one when the user wants a queue-wide sweep. Compare candidates on need coverage, runtime correctness, placement, tests, compatibility, complexity, readiness, remaining maintainer work, and reusable pieces. Prefer the best maintainable solution, not the first or smallest diff by default.\n\n### 4. Use a two-stage evidence flow\n\nAlways begin with a desk review. Inspect the real runtime path before judging a change as trivial or meaningful. Check callers, public exports, both stacks (sync and async), equivalent protocol/topology/parser paths, persistence and connection lifecycle, cleanup, and focused tests. Inspecting test code is part of the desk review; executing tests, imports, examples, reproductions, benchmarks, or live Redis calls is a runtime probe.\n\nTreat `references/evaluation-framework.md` and the repository's own design notes (`.agent/`, `specs/`, `CLAUDE.md`) as read-only background during review. Verify every current claim against the remote change, current source, tests, docs, release boundary, and runtime evidence. Do not infer issue status or PR correctness from a reference. Recommend a separate documentation/spec-maintenance change when review reveals a durable invariant unless the user explicitly includes that update.\n\nUse this evidence order across the two stages:\n\n1. Trace the closest existing supported capabilities and determine whether they already satisfy the underlying user outcome.\n2. Inspect existing tests and complete the code-path trace, including the mandatory interleaving and ownership pass when triggered, without executing code.\n3. With explicit user approval, run a focused local reproduction of the exact claim when the desk-review rules below require it.\n4. A comparison with the latest release, base branch, or another known-good control.\n5. A broader runtime matrix only when the maintainer decision remains uncertain and the user approves it.\n\n#### Stage 1: desk review\n\nProduce an initial result from static evidence before running code:\n\n##### Mandatory unmet-need and design pass\n\nBefore a positive assessment, complete the pass in step 2 and be able to state all of the following from concrete evidence:\n\n1. The user outcome that current supported behavior cannot achieve, or the supported contract that the reported defect violates.\n2. The closest existing API or composition path and the exact reason it is insufficient.\n3. Why the proposed behavior belongs at the chosen abstraction layer instead of a caller, application, the Redis server, a response callback, validation, documentation, or an existing extension point.\n4. Why the proposed permanent contract is better than no code change and the strongest narrower alternative.\n5. What real scenario, compatibility requirement, violated invariant, or repeated demand justifies the maintenance surface.\n6. Whether maintainers would choose to pursue the same work if no contributor had already supplied a patch.\n\nIf any answer is missing and could change whether code should exist at all, do not call the issue actionable or the PR merge-worthy. Request only the evidence needed to distinguish a genuine capability gap or contract violation from a usage, discoverability, or solution-design problem. This is a product and architecture evidence gap, not a runtime-probe trigger by itself.\n\n##### Mandatory sync/async parity pass\n\nredis-py requires the sync and async stacks to stay aligned. Before any positive assessment of a PR that changes runtime behavior, confirm:\n\n1. Whether the change touches `redis/` (sync) without a mirrored change under `redis/asyncio/` (or vice versa), and whether that asymmetry is intended.\n2. Whether tests were added or updated on both sides (`tests/` and `tests/test_asyncio/`) when both stacks are affected.\n3. Whether type hints, overloads, signatures, defaults, and return types remain identical across stacks (`.agent/sync_async_type_hints_overload_guide.md`).\n\nA behavior fix applied to only one stack is normally `Merge-worthy after focused changes` at best, with the missing mirror called out as required work — unless the divergence is deliberate and documented.\n\n##### Mandatory interleaving and ownership pass\n\nRun this pass before any positive PR assessment when a patch adds, removes, or reorders cleanup, retry, reconnect, cancellation, listeners, shared coroutines/tasks, sockets or connections, pool state, state flags, or mutable state across an `await`, callback, event, or deferred completion. In redis-py this most often means connection-pool acquire/release, async locks held across awaits, pub/sub reconnect, retry/backoff, cluster MOVED/ASK re-slotting, maintenance-notification handlers, and multidb health-check/failover.\n\n1. Name each shared resource or state value and the operation that owns it. Include pool connections, async locks, pub/sub state, in-flight commands, cluster slot maps, coroutines/tasks, sockets, caches, state flags, persistence, and telemetry/observability spans.\n2. Trace at least two overlapping operations, `A` and `B`, across every suspension or re-entry point. Check `A pending -> B starts -> A fails -> B succeeds`, `A pending -> B starts -> B fails -> A succeeds`, disconnect/cancellation between setup and completion, and a stale completion arriving after newer work.\n3. For every cleanup or rollback, identify the exact attempt and resource generation it is allowed to dispose. Treat unconditional cleanup after a suspension point as a regression candidate until the code proves it cannot tear down newer or surviving work. In particular, an async lock released across an `await` inside a pool mutation can let another task observe in-between state — hold the lock across the awaits.\n4. Compare base and head for the survivor invariant. Replacing duplicated work with a closed connection, a returned-then-reused pool slot, reverted slot-map state, or a rejected surviving coroutine is a regression, not a successful cleanup.\n5. Inspect tests for controlled interleavings using events, deferred futures, or callbacks. Require assertions about the surviving operation's observable behavior and final resource state (pool size, connection validity, lock state), not only counts or individual exception results.\n\nDo not mark a concurrency-sensitive patch `Merge-worthy as-is` merely because sequential reconnect, retry, failure, and disconnect tests pass. If the code trace proves an unsafe interleaving, conclude from static evidence and request a focused fix and regression test. If ownership remains ambiguous, keep the result preliminary and request approval for the smallest decisive runtime probe.\n\n- If the claim or PR is decisively negative from a complete reachable code-path trace, conclude the review without a runtime probe. Examples include an impossible or unsupported path, duplicated existing handling, a demonstrated no-op, a direct compatibility break, or a clearly wrong abstraction. Do not call an ambiguous result negative merely to avoid a probe.\n- If the initial result is positive and there is no unresolved runtime concern, and any triggered parity and interleaving passes are complete, the desk review may be sufficient for a final maintainer decision. Do not run a probe only to restate evidence that cannot plausibly change the decision.\n- If the initial result is positive but there is any unresolved runtime concern that could plausibly change claim validity, severity, merge-worthiness, required changes, or the preferred competing PR, stop before executing code. Report a `Preliminary assessment`, name the concern, propose the smallest decisive probe and control, and ask the user for approval to run it.\n- A purely stylistic, documentation, CI-status, or repository-readiness concern does not trigger a runtime probe unless it masks a runtime question.\n\nDo not issue a definitive positive maintainer decision while a decision-relevant runtime concern remains unresolved. If the user declines the probe, keep the result preliminary and state the exact confidence limitation.\n\n#### Stage 2: approved runtime probe\n\nAfter explicit approval, run only the smallest probe needed to resolve the stated concern. Exercise the real public or internal path and include a base, release, or known-good control when relevant. Do not stop at a happy-path smoke check when failure behavior determines the decision. Return to the user for separate approval before expanding materially beyond the approved probe.\n\nFor latency, timeout, buffering, backpressure, or cleanup, measure an observable elapsed-time or state transition where feasible. Do not assume that a mocked unit test exercises real scheduling or Redis-server behavior. Prefer a local probe first; use an approval-gated probe against a live or external Redis deployment only when local evidence cannot settle the decision.\n\nThe repository's local runtime tools are the test harness, not a separate probe skill: `invoke devenv` brings up the docker test environment; `invoke tests` / `invoke standalone-tests` / `invoke cluster-tests` run suites; targeted runs use `pytest ... --protocol 2|3` and topology markers (`onlycluster`, `onlynoncluster`). The repository also has `verify` and `run` skills for driving the library end-to-end. Honor any live-service, Docker, cost, cleanup, and reporting implications when proposing a probe, and call out when a probe needs `invoke devenv` or external infrastructure (scenario tests). Ordinary maintainer review must not depend on spinning up external infrastructure.\n\nFor validation, cleanup, retries, interruption, background work, or concurrency:\n\n- Identify the earliest correct decision point after dynamic inputs are available.\n- List resources acquired before and after it: pool connections, sockets, coroutines/tasks, locks, caches, state mutations, and telemetry.\n- Exercise failure during construction, connection, validation, command execution, and cleanup where applicable.\n- Verify explicit cleanup when failure occurs before normal teardown (connection returned to or removed from the pool, lock released, task cancelled).\n- Require a negative-path test when a connection, coroutine, lock, socket, or state can remain after failure.\n\nStop when additional evidence is unlikely to change validity, severity, or maintainer action.\n\n### 5. Calibrate validity and impact\n\nRead [the evaluation framework](references/evaluation-framework.md) when validity, severity, or merge value is not immediately clear.\n\nAssess claim validity, realistic reach (which stacks, protocols, topologies, and versions), consequence, breadth, frequency, recoverability, compatibility, and severity. Keep observed facts separate from inference and name missing evidence that could change the result.\n\nReport the `Need evidence` status before classifying the need as a capability gap, ergonomics or discoverability gap, unsupported use case, no demonstrated gap, or a defect in supported behavior. Do not assign practical impact to the absence of the requested mechanism when an existing supported workflow already produces the requested outcome. Do not infer practical importance merely from reachability, API asymmetry, or a technically successful patch.\n\nFor a PR, make `Severity` describe the underlying issue or user need. Report patch-induced regression, compatibility, lifecycle, or maintenance risk separately as `Patch risk`.\n\nDo not speculate about AI authorship or contributor intent. Identify weak reports through objective evidence: no reproduction, unsupported input, impossible path, duplicated handling, a test that does not exercise the claim, or a fix that is a runtime no-op.\n\n### 6. Apply the maintainer-effort test\n\nUse one code recommendation:\n\n- **Merge-worthy as-is**: real need, sound placement, proportionate scope, adequate tests, and sync/async parity maintained.\n- **Merge-worthy after focused changes**: real need and viable direction with bounded corrections (which may include adding the missing sync or async mirror, RESP2/RESP3 coverage, or a regression test).\n- **Supersede with a simpler alternative**: real need, but a smaller or more coherent fix is preferable.\n- **Not worth completing**: negligible/unsupported impact, no-op behavior, wrong abstraction, or excessive completion cost.\n\n`Merge-worthy as-is` and `Merge-worthy after focused changes` are invalid unless `Need evidence` is `Demonstrated`. A bounded set of implementation fixes cannot promote a `Plausible but unproven` need into a merge-worthy recommendation.\n\nFor merge-worthy recommendations, use one repository-readiness status when useful:\n\n- **Ready**\n- **CI or review pending**\n- **Rebase or conflict resolution required**\n- **Blocked**\n\nOmit readiness for supersede/not-worth-completing recommendations; CI does not change those code decisions. Do not downgrade sound code only because CI is pending, and do not call a PR ready when semantic changes remain (including a missing sync/async mirror or untested protocol/topology).\n\nFor competing PRs, make one portfolio recommendation: choose one, choose one after focused changes, combine exact pieces into one destination, replace all with a simpler approach, or merge none. State what should happen to every active candidate.\n\nAlways compare the proposed patch with the strongest existing supported approach and at least one alternative: no code change, validation or documentation, a narrower fix, reuse of an existing helper, or a different layer that enforces the invariant consistently. A review is incomplete if it establishes only that the patch works without establishing why the current product cannot meet the underlying need or violates a supported contract, and why this design is preferable.\n\n### 7. Report the decision and action\n\nChoose the assessment language from the current user request and governing repository instructions. Per the repository owner's standing rules, deliver review findings in chat, lead with findings ordered by severity with file/line references, and do not post comments, approvals, or change requests to GitHub or any external service. Maintainer comment drafts are produced in chat as copy-paste-ready text, in English, for the user to send themselves.\n\nUse the matching compact report in the evaluation framework. While runtime approval is pending, use its preliminary-assessment variant and end with the approval request instead of presenting a final recommendation. Keep the report decision-oriented, put unexpected/negative evidence first, and use no more than five evidence bullets by default.\n\nFor PRs, put `Need evidence` before the code recommendation. When the need is not `Demonstrated`, lead with that result, omit repository readiness, and avoid presenting patch fixes as the primary maintainer action.\n\nWhen existing functionality or a better alternative materially affects the decision, state it explicitly in the evidence and recommendation. Name the exact supported path, what it does and does not cover, and why it is preferable. Do not bury a `Not worth completing` or `Supersede with a simpler alternative` conclusion beneath praise for implementation quality.\n\nWhen recommending closure, more evidence, focused changes, or superseding a PR, append a polite, complete, copy-paste-ready English maintainer comment. Include only merge-blocking work in its required-action paragraph. Do not produce a line-by-line review unless requested, equate passing tests with merge-worthiness, or equate a logically correct patch with practical value.\n\n## Resource\n\n- `references/evaluation-framework.md` contains the severity rubric, evidence checks, lifecycle review, issue dispositions, PR value checks, documentation threshold, competing-PR framework, maintainer-comment guidance, and compact report variants.\n\n<!-- File: .agents/skills/pr-draft-summary/SKILL.md (Tokens: ~1588 | Category: anthropic-skill) -->\n---\nname: pr-draft-summary\ndescription: Produce PR-ready output for redis-py — a one-line commit message and a copy-pasteable Markdown PR description (Change summary + Test coverage, 5–6 paragraphs) covering all staged changes plus the commits already on the current branch. Trigger whenever the user's message mentions any of these keywords or phrases: \"PR\", \"pr\", \"PR description\", \"PR draft\", \"pull request\", \"the PR block\", \"commit msg\", \"commit message\", or otherwise asks for a commit message or PR write-up for their local changes.\n---\n\n# PR Draft Summary\n\n## Purpose\n\nAfter eligible work is complete, produce exactly two things for redis-py:\n\n1. A **one-line commit message** (imperative, conventional prefix preferred).\n2. A **PR description** as a single Markdown block the user can copy-paste into\n   the GitHub PR UI with all formatting preserved. Keep it to **5–6 short\n   paragraphs** that explain what the changes do.\n\nBy default, emit both as text in the chat. Write them to a `.md` file **only**\nif the user explicitly asks for a file.\n\n## Scope of Changes to Evaluate\n\nBy default, cover **staged changes** and **commits already on the current\nbranch** (relative to the PR base) — that is what the commit/PR will actually\ncontain. Include **unstaged changes** and **untracked files** only when the user\nexplicitly asks for them; note that untracked files do not appear in\n`git diff --stat`, so pick them up from `git status -sb` (the `??` entries). Use\ncommit messages as supporting context, not as a substitute for reading the\ndiffs. If the in-scope set is empty, reply briefly that no changes were detected\nand skip emitting the blocks.\n\n## Inputs to Collect Automatically (do not ask the user)\n\n- Working tree overview — `git status -sb` (one shot: branch name, ahead/behind,\n  and staged/unstaged/untracked entries, with `??` marking untracked files).\n- What changed, with line counts — `git diff --stat --cached` for staged\n  changes (stats include file names, so a separate `--name-only` is not needed);\n  read the staged diff to write the summary. Run `git diff --stat` (unstaged)\n  only when the user asks to include unstaged/untracked work.\n- PR base — the branch this change will merge into. Accept it as an input: use\n  the base branch the user names (which may be a feature/integration branch, not\n  `master`). When none is given, default to `origin/master` (fall back to\n  `master`). Then set `BASE_REF` to that branch and\n  `BASE_COMMIT=$(git merge-base \"$BASE_REF\" HEAD)`.\n- Branch commits vs the base — `git diff --stat \"${BASE_COMMIT}..HEAD\"` and\n  `git log --oneline --no-merges \"${BASE_COMMIT}..HEAD\"`.\n\nClassify the change from the touched paths: runtime (`redis/`,\n`redis/asyncio/`), tests (`tests/`, `tests/test_asyncio/`), docs/examples\n(`docs/`, `doctests/`, `benchmarks/`), build/test config (`pyproject.toml`,\n`tasks.py`, `dockers/`, …), or repo-meta (`AGENTS.md`, `CLAUDE.md`, `.agents/`,\n`.github/`).\n\n## Workflow\n\n1. Determine the PR base first: use the base branch the user provided; otherwise\n   default to `origin/master` (fall back to `master`). Compute\n   `BASE_REF`/`BASE_COMMIT`, then run the commands above. The base may be a\n   feature or integration branch when the change targets one rather than\n   `master`.\n2. Build the in-scope diff: committed branch changes + staged changes by\n   default; add unstaged and untracked changes only when the user explicitly\n   asks. Use commit messages as supporting context, not as a substitute for\n   inspecting the diff itself.\n3. Infer change type from touched paths (feature, fix, refactor, perf, or\n   docs-with-impact). Pick the commit-message prefix accordingly: `feat:`,\n   `fix:`, `refactor:`, `perf:`, `docs:`, `test:`, `build:`, `chore:`.\n4. Flag **backward-compatibility risk** in the description only when the diff\n   changes the public API (signatures, argument names, defaults, return types),\n   command semantics, error/exception types, wire-protocol (RESP2/RESP3)\n   handling, or persisted/connection config. Judge that risk against the latest\n   released version, not unreleased branch-only churn.\n5. Flag **sync/async parity** explicitly: if the change touches sync code under\n   `redis/` without a mirrored change under `redis/asyncio/` (or vice versa, and\n   likewise `tests/` vs `tests/test_asyncio/`), call it out, since this repo\n   requires both stacks to stay aligned.\n6. If the branch name matches `issue-<number>` (digits only) or an issue number\n   is otherwise known, reference\n   `https://github.com/redis/redis-py/issues/<number>` and add an auto-closing\n   line such as `Resolves #<number>.` Do not block if the issue cannot be fetched.\n7. Draft the two blocks using the format below. The PR description always has a\n   \"Change summary\" and a \"Test coverage\" section. Keep it to 5–6 short\n   paragraphs total; do not pad. In \"Test coverage\", describe the tests added or\n   updated; if none were added, say so briefly and explain why.\n8. Output both blocks as chat text by default. If — and only if — the user\n   explicitly requested a file, also write them to a `.md` file and report its\n   path.\n\n## Output Format\n\nEmit the commit message first, then the PR description. The description must be\na single fenced Markdown block so it can be copied into the GitHub UI with\nformatting intact.\n\n### Commit message\n\n```\n<single-line imperative commit message; conventional prefix preferred, e.g. fix: release async pool lock only after pool mutation>\n```\n\n### PR description\n\n````\n```markdown\n## Change summary\n\n<3–4 paragraphs of prose, starting with \"This pull request resolves/updates/adds ...\"\nusing a verb that matches the change. Explain what the change does and the\nbackground — for bugs, the symptom/repro; for features, what is needed and why —\nthen the concrete changes: the main files/areas touched, the approach, and notable\nedge cases. Call out public-API or backward-compatibility impact and any sync/async\nparity considerations here. Bullets are allowed.>\n\n## Test coverage\n\n<1–2 paragraphs (bullets allowed) describing the tests added or updated and what\nbehavior they exercise (sync and async, edge/failure cases). If the change is not\ntestable or no tests were added, say so briefly and explain why.>\n\nResolves #<number>.   <!-- include only when an issue number is known -->\n```\n````\n\nKeep it tight — 5–6 paragraphs total across both sections, no redundant prose\naround the blocks, and no repeating the same detail in both sections.\n\n<!-- File: .agents/skills/prepare-for-takeover/SKILL.md (Tokens: ~1613 | Category: anthropic-skill) -->\n---\nname: prepare-for-takeover\ndescription: Prepare copy-paste-ready maintainer comments that mark stalled but valuable redis-py PRs as `open-for-takeover`, inviting the original author or any other contributor to finish the work. Each comment summarizes what is left based on the maintainer's earlier review. Use when the user says \"prepare for takeover\", \"open for takeover\", \"mark as abandoned\", \"invite contributors to finish\", or gives one or more PR numbers/URLs that stalled and should be handed over.\n---\n\n# Prepare for Takeover\n\n## Purpose\n\nSome PRs are worth finishing, but the author has gone quiet and the maintainer has no time to complete them. This skill drafts one comment per PR that:\n\n1. Says the PR seems to have stalled but is still valuable for redis-py.\n2. Announces the `open-for-takeover` label.\n3. Lists what is left, taken from the maintainer's earlier review.\n4. Invites the original author to continue, or anyone else to take over while keeping the author's credit.\n\nFor now the skill only generates the comment texts. It does not apply labels, post comments, or edit workflows.\n\n## Hard rule: no GitHub writes\n\nUse read-only GitHub access only (`gh pr view`, `gh api` GET). Never comment, label, review, or edit anything on GitHub. The user posts the comments and applies the labels manually. `gh` may fail with a TLS error inside the command sandbox; run it with the sandbox disabled when needed.\n\n## Inputs\n\n- One or more PR numbers or URLs. Default repo: `redis/redis-py`. Invoking the skill on a PR makes it eligible; do not require any label (such as `waiting-for-response`), a minimum inactivity period, or other staleness signals.\n- The maintainer's GitHub login: resolve it with `gh api user --jq .login`. Do not guess.\n\n## Data to collect per PR (read-only)\n\n```\ngh pr view <n> -R <owner/repo> --json title,author,state,isDraft,labels,updatedAt,body,reviews,comments\ngh api graphql --paginate -F owner=<owner> -F repo=<repo> -F n=<n> -f query='\n  query($owner: String!, $repo: String!, $n: Int!, $endCursor: String) {\n    repository(owner: $owner, name: $repo) {\n      pullRequest(number: $n) {\n        reviewThreads(first: 100, after: $endCursor) {\n          pageInfo { hasNextPage endCursor }\n          nodes {\n            isResolved isOutdated path line subjectType\n            comments(first: 100) {\n              nodes { author { login } body createdAt pullRequestReview { state } }\n            }\n          }\n        }\n      }\n    }\n  }'\n```\n\nThe second command returns the inline review threads, which `gh pr view` does not include. A `CHANGES_REQUESTED` review often has an empty or short body with the details inline. Per thread:\n\n- `isResolved: true` means the thread was settled (often the author resolved it after the fix, with no reply); treat it as done unless a later maintainer comment reopens it.\n- `isOutdated: true` means the code moved under the comment; check whether it still applies.\n- `subjectType: FILE` is a file-level comment. Its `line` is null, but it is still current.\n\nIgnore pending (unsubmitted) feedback: reviews with `state: PENDING` and thread comments whose `pullRequestReview.state` is `PENDING`. They are drafts visible only to the maintainer, so they must not appear in a public comment or count as an earlier review.\n\nFrom the output, get:\n\n- `author.login`: the PR author.\n- Whether the maintainer has already commented or reviewed (a comment, submitted review or inline comment by the maintainer's login).\n- All maintainer feedback in chronological order: review bodies, inline comments and conversation comments. Read it in full; it is the source for \"What's left\". Drop items in resolved threads, items that a later maintainer comment withdrew or marked as done, and items that a later commit or reply clearly addressed. An empty review body does not mean there is no feedback.\n- Current labels (to point out `waiting-for-response` for removal) and `isDraft`.\n\nIf the PR is closed or merged, skip it and say so. If the maintainer left no feedback in any of these sources, write \"What's left\" from the open TODOs in the PR `body` or ask the user for a line, and do not invent requirements.\n\n## Writing \"What's left\"\n\n- 1 to 5 short bullets, each one concrete action, in the order the maintainer raised them.\n- Paraphrase the review. Do not add new requests that the maintainer did not make.\n- Keep scope notes from the review (for example \"change Fixes #N to Refs #N\", \"keep #N open, retitle the PR\").\n- Add \"Take the PR out of draft.\" when `isDraft` is true and the review asked for it.\n- Put file and symbol names in single backticks.\n\n## Comment template\n\nInclude the greeting line **only** if the maintainer has not commented on or reviewed this PR before. Otherwise start with `@<author>, this PR seems to have stalled...`.\n\n```markdown\nHey @<author>, thank you for your contribution!\n\nThis PR seems to have stalled, but the change is still valuable for redis-py and I would like to see it land. I don't have the bandwidth to finish it myself right now, so I'm marking it as `open-for-takeover`.\n\nWhat's left (details in my review above):\n- <action>\n- <action>\n\n@<author>, if you want to continue, you're very welcome to, just let me know here. Otherwise, anyone interested can take it over: leave a comment first so two people don't work on it at the same time, then open a new PR that builds on this branch (keep the original commits so @<author> keeps the credit) and links back here.\n\nAny help is very welcome!\n```\n\nWhen there is no earlier maintainer review or inline review comment, use `What's left:` without \"(details in my review above)\".\n\n## Writing rules\n\n- Use \"I\", never \"we/us/our\". The maintainer is one person.\n- Never use em dashes, en dashes, or `--` as punctuation; use a single hyphen.\n- No code fences inside the comment (it breaks the outer fence). Use single backticks only.\n- Keep it short; do not restate the full review.\n\n## Output format\n\nStart with one line naming the audience, for example `Written for: the PR authors and other contributors on GitHub.`\n\nThen for each PR, a bold heading with the number and author, followed by the comment in its own fenced ```markdown block:\n\n**#<n>** (@<author>)\n\n```markdown\n<comment>\n```\n\nAfter the blocks, add at most two short notes:\n\n- PRs that still carry `waiting-for-response` (remove it when adding `open-for-takeover`).\n- PRs that were skipped (closed or merged).\n\n## Label reference\n\nIf the user asks for the label details:\n\n- Name: `open-for-takeover`\n- Description: `Stalled but valuable work - anyone is welcome to pick it up and finish it`\n- Color: `0e8a16`\n- Stale bot: add `open-for-takeover` to `exempt-pr-labels` in `.github/workflows/stale-issues.yml`.\n\n<!-- File: .claude/skills/generate-release-notes/SKILL.md (Tokens: ~319 | Category: anthropic-skill) -->\n---\nname: generate-release-notes\ndescription: Generate a redis-py GitHub release-notes draft and save it as a Markdown file. Collects the merged PRs/commits since the previous release, maps each commit to its PR labels, categorizes them into the project's release sections (Breaking Changes, Deprecations, Experimental Features, New Features, Bug Fixes, Maintenance) using the skill's pr-labels-guide, infers a category for unlabeled PRs, and assembles the notes with an optional Highlights section and a contributors footer. Trigger this skill whenever the user says \"release notes\", \"create release notes\", \"generate release notes\", \"draft release notes\", \"prepare release notes\", \"changelog\", or asks for the release notes/changelog of a specific version or branch (e.g. \"release notes for 8.0\", \"generate release notes for the 8.0 branch\", \"what changed since the last release\"). A phrase like \"release notes for 8.0\" means: use branch `8.0` as the release-branch input. Also use when asked to summarize what changed since the last tag.\n---\n\nThe full instructions for this skill live in\n`.agents/skills/generate-release-notes/SKILL.md` (relative to the redis-py repo root),\nwith its helper files under `.agents/skills/generate-release-notes/references/`.\n\n**Read `.agents/skills/generate-release-notes/SKILL.md` in full now and follow it\nexactly.** Treat its contents as the body of this skill.\n\n<!-- File: .claude/skills/maintainer-review/SKILL.md (Tokens: ~252 | Category: anthropic-skill) -->\n---\nname: maintainer-review\ndescription: Review a GitHub issue or pull request URL as a redis-py maintainer, with a staged assessment of whether the claim is real, practically important, already solvable with supported functionality, correctly scoped, better served by another design, and worth maintainer and contributor effort. Use when assessing issue validity or severity, deciding whether an issue should be prioritized or closed, determining whether a requested feature represents an unmet need rather than a discoverability or usage gap, judging whether a PR is worth bringing to mergeable quality, comparing open PRs or alternative designs, separating code quality from repository readiness, or drafting a concise maintainer assessment. When closure, additional evidence, or code changes should be requested, also produce a polite, concise, complete, copy-paste-ready maintainer comment.\n---\n\nThe full instructions for this skill live in\n`.agents/skills/maintainer-review/SKILL.md` (relative to the redis-py repo root), with\nits helper files under `.agents/skills/maintainer-review/references/`.\n\n**Read `.agents/skills/maintainer-review/SKILL.md` in full now and follow it exactly.**\nTreat its contents as the body of this skill.\n\n<!-- File: .claude/skills/pr-draft-summary/SKILL.md (Tokens: ~188 | Category: anthropic-skill) -->\n---\nname: pr-draft-summary\ndescription: Produce PR-ready output for redis-py — a one-line commit message and a copy-pasteable Markdown PR description (Change summary + Test coverage, 5–6 paragraphs) covering all staged changes plus the commits already on the current branch. Trigger whenever the user's message mentions any of these keywords or phrases: \"PR\", \"pr\", \"PR description\", \"PR draft\", \"pull request\", \"the PR block\", \"commit msg\", \"commit message\", or otherwise asks for a commit message or PR write-up for their local changes.\n---\n\nThe full instructions for this skill live in\n`.agents/skills/pr-draft-summary/SKILL.md` (relative to the redis-py repo root).\n\n**Read `.agents/skills/pr-draft-summary/SKILL.md` in full now and follow it exactly.**\nTreat its contents as the body of this skill.\n\n<!-- File: .claude/skills/prepare-for-takeover/SKILL.md (Tokens: ~181 | Category: anthropic-skill) -->\n---\nname: prepare-for-takeover\ndescription: Prepare copy-paste-ready maintainer comments that mark stalled but valuable redis-py PRs as `open-for-takeover`, inviting the original author or any other contributor to finish the work. Each comment summarizes what is left based on the maintainer's earlier review. Use when the user says \"prepare for takeover\", \"open for takeover\", \"mark as abandoned\", \"invite contributors to finish\", or gives one or more PR numbers/URLs that stalled and should be handed over.\n---\n\nThe full instructions for this skill live in\n`.agents/skills/prepare-for-takeover/SKILL.md` (relative to the redis-py repo root).\n\n**Read `.agents/skills/prepare-for-takeover/SKILL.md` in full now and follow it exactly.**\nTreat its contents as the body of this skill.\n\n<!-- File: .agents/commands/add-new-command/add-new-command.md (Tokens: ~1274 | Category: command-prompt) -->\n---\ndescription: Adds support for a new Redis command from a given specification. Check command-specification-template.md.\nargument-hint: [path-to-specification]\n---\n\n# Execute: Add new Redis command support\n\n## Plan to Execute\n\nRead specification file: `$ARGUMENTS`\n\n## Execution Instructions\n\n### 1. Preparations\n\n- Follow instructions from `.agent/instructions.md`\n- Go through the guide `specs/redis_commands_guide.md`\n\n### 2. Read and Understand\n\n- Read the ENTIRE specification carefully\n- Go through Command Description and identify command type (string, list, set, etc.)\n- Go through the Command API:\n  - Identify required and optional arguments\n  - Identify how to match Redis command arguments type to Python types\n  - Identify return value and possible response types\n- Check relevant Redis-Cli examples, if provided\n- Review the Test Plan\n\n### 3. Execute Tasks in Order\n\n#### a. Navigate to the task\n- Identify the files and action required\n- Read existing related files if modifying\n\n#### b. Implement the command\n- Add new command method within matching trait object (for example: `redis/commands/core.py` for core Redis commands)\n- Ensure overloading is implemented for sync and async API\n- Follow Arguments definition section from `specs/redis_commands_guide.md` before defining command arguments\n- Ensure arguments and response types consider bytes representation\n- Ensure response RESP2 and RESP3 compatibility via `response_callbacks`\n\n#### c. Record the command's metadata\n- Obtain the server's metadata for the command: use the `COMMAND INFO` output from the\n  specification's \"Command metadata\" section, or run `COMMAND INFO <name>` against a server\n  that ships the command. For a container command (`BLESS`, `MEMORY`, ...) the subcommands\n  are nested in the reply as `<container>|<sub>`; each one gets its own record.\n- Add a record for the command to `_STATIC_COMMAND_METADATA` in `redis/commands/metadata.py`,\n  keyed lowercase under its module (`\"core\"` for non-module commands, the lowercased prefix\n  such as `\"json\"` or `\"ft\"` for module commands). Key a container subcommand by its\n  space-joined name (`\"bless scan\"`), the form `execute_command` receives.\n- Map the reply onto the record fields and reuse an existing shape (`_CACHEABLE_KEYED`,\n  `_WRITE_KEYED`, `_NONDETERMINISTIC_KEYED`, ...) when one matches; otherwise spell out every\n  field of a `CommandMetadata` inline, as the table does for `touch` and `vrandmember`:\n  - `is_readonly` is the `readonly` command flag - not the `RO` key spec flag. A command the\n    server does not flag `readonly` is neither client-side cacheable nor replica-eligible.\n  - `is_blocking` is the `blocking` flag; `is_script_runner` the `script_runner` flag.\n  - `has_key_argument` is true when `first_key_pos > 0 and step_count > 0`, or when a\n    `movablekeys` command reports a key spec not flagged `not_key`.\n  - `has_nondeterministic_output` and `is_dont_cache` are the `nondeterministic_output` and\n    `dont_cache` tips, matched exactly (`nondeterministic_output_order` is a different tip).\n  - `has_complete_metadata` is true: the record was taken from a reply that carries flags and\n    tips.\n  - `request_policy` / `response_policy` are the keyed or keyless defaults unless a\n    `request_policy:` / `response_policy:` tip overrides them. Withhold both (set them to\n    `None`) when the cluster client must keep resolving the target itself: the command is in\n    `RedisCluster.COMMAND_FLAGS` (as `SCAN` and `BLESS SCAN` are), is `movablekeys`, or tips a\n    policy the client does not implement (`multi_shard`). A keyed command must never be\n    recorded `DEFAULT_KEYLESS`.\n- Document in a comment next to the record anything that diverges from the live reply and\n  why, and extend the provenance note above the table if the command needs a newer server\n  than the table was validated against.\n- Update the guards in `tests/test_command_metadata.py` (shared with its async mirror):\n  add a withheld record to `ALL_WITHHELD_ROUTING_COMMANDS`, a deliberate cacheability\n  divergence to `LIVE_CACHEABILITY_DIVERGENCE`, and raise `STATIC_TABLE_SERVER_VERSION` if\n  the command first ships in a newer release. Then run `tests/test_command_metadata.py`,\n  `tests/test_asyncio/test_command_metadata.py` and the static-table routing tests in\n  `tests/test_cluster.py` / `tests/test_asyncio/test_cluster.py`.\n\n#### d. Verify as you go\n- After each file change, check syntax\n- Ensure imports are correct\n- Verify types are properly defined\n- Verify that response schema is similar for RESP2 and RESP3\n\n### 4. Implement Testing Plan\n\nAfter completing implementation tasks:\n\n- Identify matching test file or create new one if needed\n- Implement all test cases as separate test methods\n- Ensure adding version constraint if specified in the specification\n- Ensure tests cover edge cases\n\n### 5. Run tests\n\n- Run newly added test cases using `pytest` with RESP2 and RESP3 protocol specified via `--protocol` option\n- Ensure that the same test cases passed with both protocols\n- Get back to the Implementation stage if any test failed\n\n### 6. Final Verification\n\nBefore completing:\n\n- ✅ All tasks from plan completed\n- ✅ All tests created and passing\n- ✅ Code follows project conventions\n- ✅ Documentation added/updated as needed\n\n## Output Report\n\nProvide summary:\n\n### Completed Tasks\n- List of all tasks completed\n- Files created (with paths)\n- Files modified (with paths)\n\n### Tests Added\n- Test files created\n- Test cases implemented\n- Test results\n\n<!-- File: .agents/commands/add-new-command/command-specification-template.md (Tokens: ~245 | Category: command-prompt) -->\n# $COMMAND_NAME command specification\n\n## Supported version\n\nAdd supported Redis version here. For example: Redis >= 6.2.0\n\n## Command description\n\nAdd a description of the command here.\n\n## Command API\n\nSpecify an API for the command in the format that official docs uses. For example:\n\n```\n$COMMAND_NAME $key $member [NX|XX] [CH] [INCR]\n```\n\n## Redis-CLI examples\n\nAdd relevant Redis-CLI examples here.\n\n## Command metadata\n\nPaste the output of `COMMAND INFO $COMMAND_NAME` from a server that ships the command. It is\nthe source for the command's record in `_STATIC_COMMAND_METADATA`\n(`redis/commands/metadata.py`): the command flags, the `request_policy:` / `response_policy:`\n/ `nondeterministic_output` / `dont_cache` tips and the key specifications. For a container\ncommand, include the nested subcommands.\n\n## Test plan\n\nSpecify how you want to test the command in terms of integration testing. For example:\n\n- Test only with required arguments, assert that single value returned\n- Test with required arguments and optional XX modifier, ensure that 1 returned\n- ...\n\n<!-- File: .agents/commands/sync-claude-md/sync-claude-md.md (Tokens: ~901 | Category: command-prompt) -->\n---\ndescription: Audit AGENTS.md (the canonical agent guide; CLAUDE.md is only an `@AGENTS.md` include) against the current state of the repo and update it directly. Run when wrapping up a feature or after significant structural changes.\n---\n\n# Execute: Sync AGENTS.md with current repo state\n\nAudit `AGENTS.md` at the repo root and update it to reflect the current state of the codebase. This is the canonical guidance file; `CLAUDE.md` is only an `@AGENTS.md` include and must NOT be edited (editing it would drop the include and duplicate content). Edit `AGENTS.md` directly — do not ask for confirmation first.\n\n## Inputs\n\n- Current `AGENTS.md` (read it before doing anything else — it's the baseline you're updating against).\n- Recent git history. Read `git log --oneline -40` and `git log --diff-filter=A --name-only --since=\"3 months ago\"` to find recently added subsystems.\n- Current file/directory layout of the repo, especially `redis/`, `redis/asyncio/`, `redis/commands/`, `redis/_parsers/`, `tests/`, `.github/workflows/`, `.agents/`, `.claude/`.\n\n## What to verify\n\nWalk through each section of `AGENTS.md` and check:\n\n### 1. Commands section\n- Cross-reference every `invoke` task mentioned against `tasks.py`. Catch wrong defaults, renamed flags, removed/added tasks, changed conftest options.\n- Cross-reference pytest options against the `pytest_addoption` calls in `tests/conftest.py`.\n- Cross-reference the pytest marker list against `[tool.pytest.ini_options].markers` in `pyproject.toml`.\n\n### 2. Architecture section\n- Verify each subpackage list matches what's actually in the directory (e.g., the modules under `redis/commands/`, `redis/_parsers/`, `redis/multidb/`, `redis/asyncio/`, `redis/auth/`, `redis/observability/`, `redis/http/`).\n- Check whether any new top-level subsystem under `redis/` was added since AGENTS.md was last touched and is not mentioned. Use `git log --diff-filter=A --name-only` scoped to `redis/` to find newly added files.\n- Verify any specific symbols I reference (e.g., `READ_COMMANDS`, `RequestPolicy`, `ResponsePolicy`, `CommandsParser`, `ExponentialWithJitterBackoff`) still exist — grep for them.\n- Confirm sync/async mirror claim: spot-check that the async equivalents listed exist under `redis/asyncio/`.\n\n### 3. Python/dependency notes\n- Cross-reference `requires-python`, `[project.optional-dependencies]`, and ruff config in `pyproject.toml`.\n\n### 4. Pointers to other docs\n- Verify referenced files still exist.\n\n## What to add (only if genuinely missing)\n\nIf a new subsystem, workflow, or developer-facing convention was introduced and is non-obvious from reading the directory tree, add a short note. Examples of things worth adding:\n\n- A new top-level package under `redis/` with its own architectural role.\n- A new `invoke` task or a meaningful change to an existing one.\n- A new pytest marker or a new convention about how tests are organized.\n- A newly added `.agents/skills/` or `.agents/commands/` skill that future agents should know about.\n- A new spec file under `specs/` that documents a load-bearing design decision.\n\n## What NOT to add\n\n- Per-file descriptions or anything discoverable by `ls`. AGENTS.md is an orienting document, not a directory listing.\n- Generic Python or git advice.\n- Recent bugfixes or commit-level changes. AGENTS.md describes the repo, not its history.\n- Anything obvious from reading `README.md` or `CONTRIBUTING.md`.\n\n## Output\n\n1. Apply edits directly to `AGENTS.md` using the `Edit` tool. Prefer small, targeted edits over rewrites.\n2. After editing, print a short summary of what changed and why (under 200 words). Group by section: Commands / Architecture / Other.\n3. If nothing needs to change, say so explicitly — do not edit the file just to look productive.\n\n<!-- File: .claude/commands/add-new-command.md (Tokens: ~121 | Category: command-prompt) -->\n---\ndescription: Adds support for a new Redis command from a given specification. Check command-specification-template.md.\nargument-hint: [path-to-specification]\n---\n\nThe full instructions for this command live in\n`.agents/commands/add-new-command/add-new-command.md` (relative to the redis-py repo\nroot), with the spec template at\n`.agents/commands/add-new-command/command-specification-template.md`.\n\n**Read `.agents/commands/add-new-command/add-new-command.md` in full now and follow it\nexactly.**\n\nThe specification file to use is: `$ARGUMENTS`\n\n<!-- File: .claude/commands/sync-claude-md.md (Tokens: ~121 | Category: command-prompt) -->\n---\ndescription: Audit AGENTS.md (the canonical agent guide; CLAUDE.md is only an `@AGENTS.md` include) against the current state of the repo and update it directly. Run when wrapping up a feature or after significant structural changes.\n---\n\nThe full instructions for this command live in\n`.agents/commands/sync-claude-md/sync-claude-md.md` (relative to the redis-py repo root).\n\n**Read `.agents/commands/sync-claude-md/sync-claude-md.md` in full now and follow it\nexactly.**\n</yakaai_skills>"}