{"owner":"redpanda-data","repo":"redpanda","hasSkills":true,"totalSkillsCount":14,"totalTokensCount":15511,"categories":["anthropic-skill","copilot-instructions","claude-rule","plugin-manifest"],"hasMcp":false,"mcpConfig":null,"found":[".claude/skills/add-admin-v2-endpoint/SKILL.md",".claude/skills/create-backport-branch/SKILL.md",".claude/skills/create-backport-branch/evals/evals.json",".claude/skills/improve-coverage/SKILL.md",".github/copilot-instructions.md","CLAUDE.md","bazel/clang_tidy/plugins/README.md","tools/docker/README.md","tools/go-comment-pbgen/README.md","tools/metrics-mock/README.md","tools/offline_log_viewer/README.md","tools/p/README.md","tools/rp_storage_tool/README.md","tools/type-checking/README.md"],"skills":{".claude/skills/add-admin-v2-endpoint/SKILL.md":"---\nname: add-admin-v2-endpoint\ndescription: Use when adding or modifying a Redpanda admin v2 ConnectRPC endpoint — authoring the protobuf under proto/redpanda/core/admin/v2/, wiring it into the Bazel build, implementing the C++ service handler under src/v/redpanda/admin/services/, registering it on the admin server, and regenerating Python bindings for ducktape integration tests. Covers both new RPCs on an existing service and brand-new services.\n---\n\n# Add an Admin v2 ConnectRPC Endpoint\n\nThis skill walks through every layer touched by a new admin v2 RPC: the `.proto`, the Bazel build, the C++ service implementation, registration on the admin server, and the Python bindings consumed by ducktape tests.\n\nFor generic protobuf authoring conventions (field options, message design, well-known types), the canonical reference is `proto/redpanda/README.md`. This skill assumes you've read that and focuses on what's specific to admin v2.\n\n## Reference example\n\nCommit `04d3160c3a` (`cluster: add manual upgrade finalization with admin v2 RPC`) is the cleanest minimal example — a new service with a single RPC. Skim these when unsure:\n\n- `proto/redpanda/core/admin/v2/features.proto` — the proto\n- `proto/redpanda/core/admin/v2/BUILD` — the BUILD entries (look at the `features_proto` / `features_redpanda_proto` pair)\n- `src/v/redpanda/admin/services/features.h` and `features.cc` — the C++ impl\n- `src/v/redpanda/admin/services/BUILD` — the `features` cc_library target\n- `src/v/redpanda/application_admin.cc` — the `add_service` registration\n\n`git show 04d3160c3a` will print the full diff.\n\n## Procedure\n\n### 1. Author the protobuf\n\nCreate or extend a `.proto` under `proto/redpanda/core/admin/v2/`. One file per service domain; the filename matches the dominant service (`features.proto` → `FeaturesService`).\n\nRequired header for a new file:\n\n```proto\n// (BSL copyright header — copy from features.proto:1-13)\nsyntax = \"proto3\";\n\npackage redpanda.core.admin.v2;\n\nimport \"proto/redpanda/core/pbgen/options.proto\";\nimport \"proto/redpanda/core/pbgen/rpc.proto\";\n\noption (pbgen.cpp_namespace) = \"proto::admin\";\n```\n\nNotes:\n\n- `package` is always `redpanda.core.admin.v2` for admin v2.\n- `option (pbgen.cpp_namespace) = \"proto::admin\";` is mandatory and always exactly this value for admin v2 — the short form (`pbgen.cpp_namespace`, not the FQN `redpanda.core.pbgen.cpp_namespace`) is what's used in the tree.\n- Import `rpc.proto` only when the file defines a `service`.\n\nService / RPC block:\n\n```proto\nservice MyService {\n    // One short paragraph explaining what the RPC does and any\n    // async/poll behavior the caller needs to know.\n    rpc MyMethod(MyMethodRequest) returns (MyMethodResponse) {\n        option (pbgen.rpc) = {\n            authz: SUPERUSER\n        };\n    }\n}\n\nmessage MyMethodRequest {}\nmessage MyMethodResponse {}\n```\n\n- Every RPC must carry `option (pbgen.rpc) = { authz: ... }`. Default to `SUPERUSER` for admin v2 — anything else (`USER`, `PUBLIC`) needs core-team approval (see `proto/redpanda/core/pbgen/rpc.proto`).\n- Optional `http_route: \"/v2/...\"` adds an alias HTTP path. The auto-generated `/<package>.<Service>/<Method>` route always works without it.\n- Naming: messages are `<RpcName>Request` / `<RpcName>Response`, one pair per RPC. Empty messages are fine.\n\nField-level options worth knowing (full reference in `proto/redpanda/core/pbgen/options.proto`):\n\n- `[(pbgen.iobuf) = true]` on `string` fields that may exceed 128 KiB — generates `iobuf` instead of `ss::sstring`. `bytes` is already iobuf-backed.\n- `[(pbgen.ptr) = true]` on message fields for indirection (recursive types or when you need to distinguish unset).\n- `[debug_redact = true]` on sensitive fields — suppresses them from `fmt::formatter` output (passwords, tokens, etc.).\n\nWell-known types (`google.protobuf.Duration`, `Timestamp`, `FieldMask`) are supported; importing them requires the corresponding BUILD deps in step 2.\n\n### 2. Wire up the Bazel build\n\nEach `.proto` needs **two stacked targets** in `proto/redpanda/core/admin/v2/BUILD`. Append (alphabetical order in the file):\n\n```python\nproto_library(\n    name = \"myservice_proto\",\n    srcs = [\"myservice.proto\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//proto/redpanda/core/pbgen:options_proto\",\n        \"//proto/redpanda/core/pbgen:rpc_proto\",\n        # Add per-import:\n        # \"@protobuf//:timestamp_proto\",\n        # \"@protobuf//:duration_proto\",\n        # \"@protobuf//:field_mask_proto\",\n        # \"@googleapis//google/api:field_info_proto\",\n        # \"@googleapis//google/api:field_behavior_proto\",\n        # \"@googleapis//google/api:resource_proto\",\n        # Cross-proto imports: e.g. \":kafka_connections_proto\"\n    ],\n)\n\nredpanda_proto_library(\n    name = \"myservice_redpanda_proto\",\n    protos = [\":myservice_proto\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        # Add when the proto uses Timestamp or Duration:\n        # \"@abseil-cpp//absl/time:time\",\n        # Add when the proto uses FieldMask:\n        # \"//src/v/serde/protobuf:field_mask\",\n        # For cross-proto deps the *_redpanda_proto sibling goes here:\n        # \":kafka_connections_redpanda_proto\"\n    ],\n)\n```\n\nRules:\n\n- `proto_library.deps` mirrors `import` statements in the `.proto` file.\n- `redpanda_proto_library.deps` carries C++ deps the *generated* code needs (e.g. abseil for timestamps), plus any sibling `_redpanda_proto` targets for cross-proto imports.\n- The pbgen plugin lives at `bazel/pbgen/`; it generates `<name>.proto.h` and `<name>.proto.cc` with the message types, formatters, and service stubs.\n\n### 3. Implement the C++ service\n\nIf extending an existing service (just adding a new RPC), open the matching `src/v/redpanda/admin/services/<name>.{h,cc}` and add the new handler. Skip to step 4 after.\n\nFor a brand-new service, create `src/v/redpanda/admin/services/<name>.h`:\n\n```cpp\n/*\n * Copyright 2026 Redpanda Data, Inc.\n * (BSL header — copy from features.h:1-10)\n */\n#pragma once\n\n#include \"cluster/fwd.h\"\n#include \"proto/redpanda/core/admin/v2/<name>.proto.h\"\n#include \"redpanda/admin/proxy/client.h\"\n\nnamespace admin {\n\nclass <name>_service_impl : public proto::admin::<name>_service {\npublic:\n    <name>_service_impl(\n      admin::proxy::client,\n      /* collaborators — e.g. cluster::controller*, ss::sharded<X>& */);\n\n    ss::future<proto::admin::<rpc>_response> <rpc>(\n      serde::pb::rpc::context, proto::admin::<rpc>_request) override;\n\nprivate:\n    admin::proxy::client _proxy_client;\n    /* members for collaborators */\n};\n\n} // namespace admin\n```\n\nNaming conventions (CamelCase → snake_case translation):\n\n- Service base class: `proto::admin::<service>_service` (e.g. `FeaturesService` → `features_service`)\n- Generated client (for inter-node proxying): `proto::admin::<service>_service_client`\n- Request / response types: `proto::admin::<rpc_name>_request` / `_response`\n- Handler method name: snake_case of the RPC name (e.g. `FinalizeUpgrade` → `finalize_upgrade`)\n- Implementation class: `admin::<service>_service_impl`, in namespace `admin` (NOT `proto::admin`)\n\nHandler signature is fixed: `ss::future<...response>` return type, `serde::pb::rpc::context` first arg, request second, `override`.\n\nThen `src/v/redpanda/admin/services/<name>.cc`:\n\n```cpp\n#include \"redpanda/admin/services/<name>.h\"\n#include \"redpanda/admin/services/utils.h\"\n#include \"serde/protobuf/rpc.h\"\n#include <seastar/core/coroutine.hh>\n\nnamespace admin {\nnamespace {\nss::logger <name>log{\"admin_api_server/<name>_service\"};\n} // namespace\n\n<name>_service_impl::<name>_service_impl(/* args */) : _proxy_client(...) {}\n\nss::future<proto::admin::<rpc>_response>\n<name>_service_impl::<rpc>(\n  serde::pb::rpc::context ctx, proto::admin::<rpc>_request req) {\n    vlog(<name>log.trace, \"<rpc>: {}\", req);\n    // ... validate, optionally redirect-to-leader, do work ...\n    co_return proto::admin::<rpc>_response{};\n}\n\n} // namespace admin\n```\n\nPatterns to know — read the existing service files for live examples:\n\n- **Validation failures throw typed exceptions** declared in `src/v/serde/protobuf/rpc.h:241-287`: `invalid_argument_exception`, `failed_precondition_exception`, `not_found_exception`, `already_exists_exception`, `permission_denied_exception`, `unavailable_exception`, `unauthenticated_exception`, etc. Each maps to a ConnectRPC/gRPC status code. Example: `features.cc:46`.\n- **Leader redirect**: `admin::utils::redirect_to_leader(...)` from `services/utils.h` returns an optional `node_id`; if set, proxy the call to that node using the generated `_service_client`. Pattern in `features.cc:51-64`.\n- **Proxying to peers**: `_proxy_client.make_client_for_node<proto::admin::<service>_service_client>(node)` for one target, or `.make_clients_for_other_nodes<...>()` for scatter-gather. Defined in `redpanda/admin/proxy/client.h`.\n- **License gating**: `admin::utils::check_license(_feature_table.local())` if the RPC is enterprise-gated. See `cluster.cc:51`.\n- **Avoid proxy loops** in scatter-gather handlers: gate with `proxy::is_proxied(ctx)` (example in `cluster.cc:56`).\n\nAdd the BUILD entry to `src/v/redpanda/admin/services/BUILD`:\n\n```python\nredpanda_cc_library(\n    name = \"<name>\",\n    srcs = [\"<name>.cc\"],\n    hdrs = [\"<name>.h\"],\n    implementation_deps = [\n        \":utils\",\n        # .cc-only collaborators\n    ],\n    deps = [\n        \"//proto/redpanda/core/admin/v2:<name>_redpanda_proto\",\n        \"//src/v/cluster:fwd\",\n        \"//src/v/redpanda/admin/proxy:client\",\n        \"//src/v/serde/protobuf:rpc\",\n        \"@seastar\",\n        # types that appear in <name>.h\n    ],\n)\n```\n\nRule: types that appear in the `.h` belong in `deps`; collaborators only used in the `.cc` belong in `implementation_deps`.\n\n### 4. Register the service\n\nTwo edits in `src/v/redpanda/`:\n\n**4a. `src/v/redpanda/BUILD`** — add the new services library to the top-level `redpanda` library's `deps` list (kept sorted):\n\n```python\n\"//src/v/redpanda/admin/services:<name>\",\n```\n\n**4b. `src/v/redpanda/application_admin.cc`** — two changes:\n\n1. Include the header (alphabetical with the other service includes):\n   ```cpp\n   #include \"redpanda/admin/services/<name>.h\"\n   ```\n2. Inside `_admin.invoke_on_all([this, node_id](admin_server& s) { ... })`, append an `add_service` call. `create_client()` is the local lambda defined at the top of the block; pass other collaborators via `controller.get()` or `std::ref(...)`:\n   ```cpp\n   s.add_service(\n     std::make_unique<admin::<name>_service_impl>(\n       create_client(), /* collaborators */));\n   ```\n\nThat's it for registration. `add_service` walks the service's routes and registers an HTTP POST handler per route at `route.path` with the auth wrapper keyed on `route.authz_level`.\n\n### 5. Build and format\n\n```bash\nbazel build //proto/redpanda/core/admin/v2:<name>_redpanda_proto\nbazel build //src/v/redpanda/admin/services:<name>\nbazel build //src/v/redpanda:application\nbazel run //tools:clang_format\n```\n\nFix compilation errors before moving on. The pbgen plugin's error messages are usually self-explanatory; common causes are missing BUILD deps from step 2 or imports in the `.proto` without matching `proto_library.deps`.\n\n### 6. Regenerate ducktape Python bindings\n\nDucktape tests consume admin v2 via committed-to-repo Python bindings under `tests/rptest/clients/admin/proto/`. They are regenerated by a script — CI (`.github/workflows/check-ducktape-protos.yml`) fails the PR if they're stale, so this step is mandatory whenever a `.proto` under `proto/redpanda/core/admin/**` changes.\n\nPrerequisite: `uv` installed (https://docs.astral.sh/uv/getting-started/installation/). The script aborts otherwise.\n\n```bash\nbazel fetch //proto/...          # one-time; populates the googleapis external dir\ntools/regenerate_ducktape_protos.sh\n```\n\nThe script wipes `tests/rptest/clients/admin/proto/**/*.py{,i}` and regenerates from the proto glob using `grpc_tools.protoc` + `mypy-protobuf` + `connect-python`, then runs `protoletariat` to rewrite imports to relative form. The glob picks up new `.proto` files automatically — **no manifest to update**.\n\nCommit the generated files. `.gitignore` does not exclude them.\n\n### 7. Add a ducktape client accessor (only for new services)\n\nIf you created a brand-new service, expose it from the `Admin` wrapper at `tests/rptest/clients/admin/v2.py`. Two edits:\n\n1. Add imports near the top (line ~9), alongside the existing service imports:\n   ```python\n   from rptest.clients.admin.proto.redpanda.core.admin.v2 import (\n       <name>_pb2,\n       <name>_pb2_connect,\n       # ... existing\n   )\n   ```\n   And add a convenience re-export (`<name>_pb = <name>_pb2`) in the re-export block (around line ~52).\n\n2. Add an accessor method on `Admin` matching the existing pattern (line ~133):\n   ```python\n   def <name>(self, **kwargs: Any) -> <name>_pb2_connect.<Name>ServiceClient:\n       return self._make_service(<name>_pb2_connect.<Name>ServiceClient, **kwargs)\n   ```\n\nExisting services (`broker`, `cluster`, `features`, `security`, `shadow_link`, `datalake`, `debug`, `breakglass`, `metastore`, `l0`, `internal_shadow_link`) show the shape. If you only added a new RPC to an existing service, no edit to `v2.py` is needed — `_make_service` returns the generated client which exposes every RPC as a method.\n\nA ducktape test uses the new RPC like this:\n\n```python\nfrom rptest.clients.admin.v2 import Admin as AdminV2, <name>_pb\n\nadmin_v2 = AdminV2(self.redpanda,\n                   auth=(self.superuser.username, self.superuser.password))\nreq = <name>_pb.<Rpc>Request(...)\nresp = admin_v2.<name>().<rpc>(req)\n```\n\n## Verification\n\nAfter all edits:\n\n```bash\nbazel build //...\nbazel run //tools:clang_format\ntools/regenerate_ducktape_protos.sh   # idempotent — confirms generated tree matches\ngit status                            # should show all expected files staged\n```\n\nManual smoke (optional, but the canonical way to confirm wiring):\n\n1. `bazel build //src/v/redpanda:application` or a debug-binary target.\n2. Start a local cluster; invoke the RPC via the ducktape `AdminV2` client, or `curl` against `/redpanda.core.admin.v2.<Service>/<Method>` with a protobuf-encoded body.\n\n## Out of scope\n\n**Go bindings for `rpk` consumption.** Admin v2 Go bindings are not generated in this repo — they're pulled from the Buf Schema Registry as a Go module (`buf.build/gen/go/redpandadata/core/...`). CI publishes to BSR automatically on merge to `dev` or a `v*` branch (see `.github/workflows/buf.yml`). Consuming a new RPC from `rpk` is a follow-up PR after the proto change has merged and the BSR push has run — out of scope for this skill.\n\nIf the user mentions needing `rpk` to consume the new RPC, surface this as a separate follow-up task rather than attempting it in the same change.\n\n## Rules and pitfalls\n\n- **Auth level defaults to `SUPERUSER`.** Don't use `USER` or `PUBLIC` without explicit approval — the proto README is clear on this.\n- **The proto's `option (pbgen.cpp_namespace)` is always `\"proto::admin\"` for admin v2.** Don't invent a per-service namespace.\n- **`add_service` registration order matters only for readability.** The existing block in `application_admin.cc` is not strictly sorted; match its grouping (regular services, then `if (cloud_topics_app)` services, then internal/security at the end).\n- **`bazel fetch //proto/...` is a prerequisite for `regenerate_ducktape_protos.sh`** — the script needs the googleapis external dir to exist. The script's error message is clear, but the failure mode (script aborts with \"googleapis directory not found\") is easy to misdiagnose if you skip it.\n- **CI enforces generated-file freshness.** `check-ducktape-protos` will fail if you forget step 6. The fix is always: re-run the script and commit the diff.\n- **Don't add comments that restate the code.** RPC docstrings in the `.proto` are valuable (they become the public contract); duplicating them in the C++ handler isn't.\n- **The handler signature `ss::future<R> name(serde::pb::rpc::context, Request)` is fixed** — context first, request second, both by value, return future. Deviating produces inscrutable pbgen-template errors.\n- **Validation errors must throw the typed exceptions from `src/v/serde/protobuf/rpc.h`.** Returning a default response, returning an `errc`, or throwing `std::runtime_error` will not surface a proper ConnectRPC status to the caller.\n",".claude/skills/create-backport-branch/SKILL.md":"---\nname: create-backport-branch\ndescription: \"Create a new backport branch with prefix `ai-backport-` by cherry-picking all commits from a GitHub PR onto a target release branch or commit. Use when the user wants to backport a PR, create a backport branch, cherry-pick PR commits to a release branch, or mentions backporting changes from dev/main to a version branch (e.g. v25.1.x, v25.2.x). Requires two arguments: TARGET (the release branch or commit hash to backport onto) and PR_NUM (the GitHub PR number to backport).\"\nargument-hint: [target-branch-or-commit] [pr-number]\nallowed-tools: Bash(git *), Bash(gh *), Bash(date *), Bash(grep *), Bash(wc *), Read, Write, Edit, Glob, Grep, Agent\nmodel: opus[1m]\neffort: high\n---\n\n# Create Backport Branch\n\nCherry-pick all commits from a GitHub PR onto a target release branch, resolving conflicts along the way.\n\nArguments:\n- `$0` is target — a release branch (e.g. `v25.1.x`) or a commit hash (e.g. `81d9e1af33`)\n- `$1` is pr-number (e.g. `12345`)\n\n## Procedure\n\n### 1. Validate inputs and set up\n\n1. Both arguments must be provided. If either target-branch or pr-number is empty, abort with: \"Usage: /create-backport-branch <target-branch> <pr-number>\"\n2. Generate the backport-branch-name using the current Unix timestamp:\n   ```bash\n   date +%s\n   ```\n   backport-branch-name format: `ai-backport-pr-${pr_number}-${target_branch}-${unix_timestamp}`\n\n### 2. Create the backport branch\n\n```bash\ngit checkout -b $backport_branch_name $target_branch\n```\n\n### 3. Fetch PR commits\n\nGet the list of commit SHAs from the PR:\n\n```bash\ngh api \"repos/{owner}/{repo}/pulls/$pr_number/commits\" --paginate --jq '[.[].sha[0:10]]|join(\" \")'\n```\n\n`gh` resolves the `{owner}` and `{repo}` placeholders from the git remote of the current directory (see [`gh api` docs](https://cli.github.com/manual/gh_api)). Run this from a checkout of the repo that owns the PR.\n\nSave the full list of commits as `git_commits` and report how many commits were found.\n\n### 4. Cherry-pick commits\n\nRun the cherry-pick for all commits at once:\n\n```bash\ngit cherry-pick -x $git_commits\n```\n\nTrack these counters as you go:\n- **total_commits**: number of commits from the PR\n- **conflicts_resolved**: number of commits that needed conflict resolution\n- **commits_skipped**: number of commits skipped because they were already present on the target branch\n- **generated_files_touched**: list of generated files that were part of the cherry-picked commits (whether conflicted or not)\n\nIf the cherry-pick succeeds cleanly, check if any generated files were touched (see step 4a), then skip to step 6.\n\n### 4a. Check for generated files in cherry-picked commits\n\nAfter each successful cherry-pick (clean or after conflict resolution), check if any generated files were touched. Use the same generated file patterns from step 5.3. For clean cherry-picks, check the files changed in the commit(s) just applied:\n\n```bash\ngit diff --name-only HEAD~1 HEAD\n```\n\nMatch the output against the generated file patterns. Add any matches to `generated_files_touched`.\n\n### 5. Resolve conflicts\n\nIf the cherry-pick fails with merge conflicts, attempt resolution. For each conflicted state:\n\n1. Identify the commit that caused the conflict (the one currently being cherry-picked).\n2. List conflicted files: `git diff --name-only --diff-filter=U`\n3. Categorize each file:\n   - **Generated files** (accept theirs and move on): `*.pb.go`, `*.pb.h`, `*.pb.cc`, `*.pb.rs`, `*_pb2.py`, `go.sum`, `*.lock`, `MODULE.bazel.lock`, `package-lock.json`, `Cargo.lock`, `MODULE.bazel`\n     - For generated files: `git checkout --theirs -- $FILE && git add -- $FILE`\n     - Add each generated file to the `generated_files_touched` list\n   - **Modify/delete conflicts**: file deleted on one side but modified on the other. These require human judgment — **abort immediately** (see abort procedure below).\n   - **Content conflicts**: files with `<<<<<<<` / `=======` / `>>>>>>>` markers. Attempt resolution.\n\n4. For each content conflict:\n   a. Read the full conflicted file\n   b. Run `git log -p -1 $FAILING_COMMIT -- $FILE` to understand what the cherry-picked commit intended\n   c. Understand both sides:\n      - **HEAD side** (`<<<<<<<` to `=======`): what the target release branch has\n      - **Incoming side** (`=======` to `>>>>>>>`): what the cherry-picked commit brings\n   d. Read surrounding code if needed to understand context (imports, types, function signatures)\n   e. Determine the correct resolution:\n      - The goal is to apply the *intent* of the cherry-picked commit to the target branch\n      - Adapt to the target branch's patterns, names, and APIs where they diverge\n   f. Edit the file to remove all conflict markers and write the correct merged code\n   g. Stage: `git add -- $FILE`\n\n5. Verify the resolution is clean:\n   ```bash\n   grep -rn '<<<<<<< \\|=======$\\|>>>>>>> ' -- $RESOLVED_FILES\n   git diff --name-only --diff-filter=U\n   ```\n   Both commands should produce no output. If unmerged paths remain, something was missed.\n\n6. Continue the cherry-pick:\n   ```bash\n   git cherry-pick --continue --no-edit\n   ```\n   If `--continue` reports \"The previous cherry-pick is now empty\", the commit's changes are already on the target branch. Run `git cherry-pick --skip` to move on, and increment `commits_skipped`.\n\n7. Increment `conflicts_resolved` counter.\n\n8. If the next commit also conflicts, repeat from step 5.1.\n\n### 5a. Empty cherry-picks (no conflict)\n\nA cherry-pick can also be empty without any conflict — git will report \"The previous cherry-pick is now empty\" immediately. This happens when the commit's changes already exist on the target branch (e.g., from a prior backport or parallel fix). Run `git cherry-pick --skip` to move on and increment `commits_skipped`. This is normal and not an error.\n\n**Abort procedure** — If at any point you are uncertain how to resolve a conflict (ambiguous intent, modify/delete conflicts, architectural changes you don't understand, or anything that feels risky):\n\n1. Run `git cherry-pick --abort`\n2. Report an error with:\n   - The exact `git cherry-pick -x ...` command that was being attempted\n   - The specific commit SHA that caused the uncertain conflict\n   - Which files were conflicted and why you couldn't resolve them\n3. Stop immediately. Do NOT attempt further cherry-picks.\n\n### 6. Final report\n\nAfter all commits are cherry-picked successfully, resolve the source-PR URL dynamically:\n\n```bash\npr_url=$(gh pr view $pr_number --json url --jq .url)\n```\n\nThen emit this report to stdout:\n\n```\nBackport of PR ${pr_url}\n\n- Command: git cherry-pick -x ${git_commits}\n- Commits backported: ${total_commits}\n- Conflicts resolved: ${conflicts_resolved}\n- Commits skipped (already on target): ${commits_skipped}\n- Backport branch: ${backport_branch_name}\n\n## Conflict details\n\n- ${commit_sha} (${file}): one-line explanation of what conflicted and how it was resolved\n```\n\nFor each conflict resolved, include a one-line explanation covering what conflicted and how you resolved it. This helps PR reviewers understand the backport without reading every diff.\n\nIf `generated_files_touched` is non-empty, append this warning section:\n\n```\n## ⚠️ Generated files\n\nThe following files were cherry-picked and may need regeneration:\n\n- ${file_1}\n- ${file_2}\n\nThese files were accepted as-is from the source branch. Before merging,\nregenerate them on the target branch to ensure they're correct. For example:\n- MODULE.bazel.lock: run `bazel mod deps --lockfile_mode=update`\n- *.pb.go / *.pb.cc / *.pb.h: rebuild protobuf targets\n- go.sum: run `go mod tidy`\n```\n\n**Optional: machine-readable report.** If the `SKILL_REPORT_FILE` env var is set (callers like GitHub Actions workflows use this), write the exact same report text to that file path. The file content is designed to be usable verbatim as the body of the backport PR. Humans invoking the skill directly do not set this variable and simply read the report on stdout.\n\n## Rules\n\n- NEVER guess at conflict resolutions. If uncertain, abort immediately using the abort procedure.\n- NEVER silently drop code. Incoming changes that add new functionality must be preserved (adapted to the target branch's conventions).\n- Do NOT add comments to resolved code unless the original commit had them.\n- Preserve the formatting style of the target branch.\n- For generated files (protobuf, lockfiles, MODULE.bazel), accept theirs during the cherry-pick to unblock it. Two follow-ups are then required:\n  - List each touched generated file under the \"Generated files\" warning section in the PR body so the human reviewer regenerates and commits the correct version before merge.\n  - Do not describe these files as \"regenerated by the build\" in conflict-detail lines. Bazel/protoc/etc. update them in the local workspace only; CI does not push a corrected version back to the PR, so any regeneration must be committed manually.\n",".claude/skills/create-backport-branch/evals/evals.json":"{\n  \"skill_name\": \"create-backport-branch\",\n  \"evals\": [\n    {\n      \"id\": 0,\n      \"name\": \"clean-cherry-pick\",\n      \"prompt\": \"/create-backport-branch 81d9e1af33 30160\",\n      \"expected_output\": \"Creates a backport branch from commit 81d9e1af33 (v25.3.x before PR 30160 was backported). The single commit from PR #30160 should cherry-pick cleanly with no conflicts and no empty result. Produces a structured report.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Branch was created with naming pattern ai-backport-pr-30160-81d9e1af33-{timestamp}\",\n        \"Cherry-pick command used the -x flag to record the source commit\",\n        \"Cherry-pick succeeded cleanly with no conflicts\",\n        \"Final report includes total commits count (1), conflicts resolved count (0), and commits skipped count (0)\",\n        \"Final report includes the backport branch name\",\n        \"The backport branch has exactly 1 new commit beyond the base\"\n      ]\n    },\n    {\n      \"id\": 1,\n      \"name\": \"multi-commit-conflict\",\n      \"prompt\": \"/create-backport-branch 5cdd00f4ca 30089\",\n      \"expected_output\": \"Creates a backport branch from commit 5cdd00f4ca (v26.1.x). Cherry-picks 8 commits from PR #30089. Should encounter conflict in feature_table.h and resolve it by adapting to the target branch's release_version values.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Branch was created with naming pattern ai-backport-pr-30089-5cdd00f4ca-{timestamp}\",\n        \"All 8 commits were fetched from the PR via gh api\",\n        \"Cherry-pick command used the -x flag\",\n        \"Either all conflicts were resolved and continued, OR agent aborted cleanly listing the specific commit SHA and conflicted files\",\n        \"If conflicts were resolved, the final report includes a per-conflict one-line explanation\",\n        \"No conflict markers (<<<<<<< or ======= or >>>>>>>) remain in any resolved files\"\n      ]\n    },\n    {\n      \"id\": 2,\n      \"name\": \"generated-files-modify-delete\",\n      \"prompt\": \"/create-backport-branch d0a6248434 30115\",\n      \"expected_output\": \"Creates a backport branch from commit d0a6248434 (v25.1.x). Cherry-picks 3 commits from PR #30115. Should encounter modify/delete conflict on buf.gen.yaml and abort cleanly per skill rules.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Branch was created with naming pattern ai-backport-pr-30115-d0a6248434-{timestamp}\",\n        \"Generated files (go.sum, MODULE.bazel, or similar) were identified\",\n        \"Cherry-pick command used the -x flag\",\n        \"Agent aborted cleanly on modify/delete conflict, listing the specific commit SHA and conflicted files\",\n        \"Abort report includes the exact cherry-pick command that was being attempted\",\n        \"Final report includes total commits count, conflicts resolved count, and commits skipped count\"\n      ]\n    },\n    {\n      \"id\": 3,\n      \"name\": \"already-present-empty\",\n      \"prompt\": \"/create-backport-branch ea6e2533c7 30160\",\n      \"expected_output\": \"Creates a backport branch from commit ea6e2533c7 (v25.3.x after PR 30160 was already backported). The cherry-pick should result in an empty commit since the change is already present. Should handle with git cherry-pick --skip and report as skipped.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Branch was created with naming pattern ai-backport-pr-30160-ea6e2533c7-{timestamp}\",\n        \"Cherry-pick command used the -x flag\",\n        \"Empty cherry-pick was handled with git cherry-pick --skip (not --abort)\",\n        \"Final report includes commits skipped count of 1\",\n        \"Final report correctly indicates the commit was already on the target branch\",\n        \"The backport branch was preserved (not deleted)\"\n      ]\n    }\n  ]\n}\n",".claude/skills/improve-coverage/SKILL.md":"---\nname: improve-coverage\ndescription: Improve code coverage for a Bazel C++ test target or source file\nuser-invocable: true\n---\n\n# Improve Code Coverage\n\nImprove test coverage for a Bazel C++ test target or source file path.\n\n## Input Resolution\n\n1. **Bazel test target** (starts with `//`, e.g. `//src/v/bytes/tests:iobuf_test`):\n   use it directly.\n\n2. **Source file path** (e.g. `src/v/bytes/iobuf.cc`): find the test target:\n   ```bash\n   bazel query \"kind('.*_test', rdeps(//src/v/..., //$(dirname $FILE):$(basename $FILE .cc), 1))\" 2>/dev/null\n   ```\n   If multiple targets match, pick the one in the closest `tests/` directory.\n   If none match, tell the user and stop.\n\n## Workflow\n\n### Step 1: Baseline Coverage\n\nRun coverage and read the LLM report:\n\n```bash\ntools/run-cov <target> -r llm\n```\n\nNote the **total line coverage percentage** — this is the baseline to beat.\n\nRead the report and identify:\n- **Uncovered functions** — highest impact. One test can cover many lines.\n- **Partially covered functions** — uncovered branches indicate missing\n  error-path or edge-case tests.\n- **Zero-coverage files in scope** — may need new test cases entirely.\n\n### Step 2: Understand the Code\n\nRead the source files named in the report. Focus on:\n- Uncovered functions: what they do, inputs, preconditions.\n- The existing test file: patterns, fixtures, helpers.\n\nKey codebase patterns:\n- `redpanda_cc_gtest` targets use GTest (`TEST()`, `TEST_F()`).\n  `redpanda_cc_btest` targets use Seastar Boost test\n  (`SEASTAR_THREAD_TEST_CASE`). Match whichever the test file uses.\n- Many functions are `ss::future<>` coroutines — test with\n  `SEASTAR_THREAD_TEST_CASE` or the Seastar GTest runner.\n- Use `ss` namespace prefix for Seastar types.\n- Use `EXPECT_*` for most checks, `ASSERT_*` only when continuing would crash.\n\n### Step 3: Write Tests\n\nPrioritize by impact:\n1. **Uncovered functions** — call each one. A 20-line function = 20 lines covered.\n2. **Uncovered error paths** — trigger the uncovered branches in partially\n   covered functions.\n3. **Zero-coverage files** — add smoke tests if practical.\n\nRules:\n- Add tests to the **existing test file**. Don't create new files or BUILD\n  targets unless necessary.\n- Follow existing naming conventions in the file.\n- One logical behavior per test case.\n- Prefer deterministic tests — construct error conditions directly.\n- Do NOT add comments that restate the code.\n\n### Step 4: Verify Build\n\n```bash\nbazel build <target>\n```\n\nFix compilation errors before proceeding.\n\n### Step 5: Verify Coverage\n\nRe-run coverage:\n\n```bash\ntools/run-cov <target> -r llm\n```\n\nCompare to the baseline. Report:\n\n```\nCoverage: X.Y% → A.B% (+N.N%)\nNew lines covered: <count>\n```\n\nIf coverage did not improve, investigate:\n- Build errors preventing the new tests from running?\n- Code compiled out by preprocessor guards?\n- Template instantiations not triggered by test types?\n\n### Step 6: Summary\n\nReport:\n- Which functions/paths are now covered\n- What test cases were added\n- Before/after coverage numbers\n- Remaining significant gaps and suggestions\n",".github/copilot-instructions.md":"CLAUDE.md","CLAUDE.md":"# Copilot Coding Agent Onboarding Guide for `redpanda-data/redpanda`\n\n## High-Level Overview\n\n**What is Redpanda?**\n\nRedpanda is a high-performance, Apache Kafka®-compatible streaming data platform. It is written primarily in C++ for the core, with Go for CLI tooling (`rpk`), and some Python for auxiliary scripts and tests. Redpanda is designed to be lightweight, fast, and simple to operate, omitting ZooKeeper and the JVM.\n\nIt uses extensively the thread-per-core model and asynchronous (coroutines, futures) programming model.\n\n**Repository Characteristics:**\n- **Large multi-language codebase:** C++ (core), Go (CLI/tools), Python (testing/scripts), Bash and Bazel for builds.\n- **Build System:** Bazel (with Bazelisk) for core.\n- **Target Platforms:** Linux (primary), some support for macOS and Windows.\n- **Key Directories:**\n  - `src/v/`: Core C++ source code\n  - `src/go/`: Go CLI and tools\n  - `bazel/`, `BUILD`, `MODULE.bazel`: Bazel scripts and definitions\n  - `tools/`: Development and helper scripts\n  - `tests/`: Test suites\n  - `conf/`: Configuration files\n  - `proto/`: Protobuf definitions for Redpanda services and APIs\n  - `.github/`, `.buildkite/`: CI/workflow automation\n- **Documentation:** [Docs site](https://redpanda.com/documentation) and `docs/`.\n\n## Build, Test, and Validation Instructions\n\n### Prerequisites (Always Perform)\n\n1. **Install Bazelisk** (the required Bazel wrapper):\n   ```bash\n   wget -O ~/bin/bazel https://github.com/bazelbuild/bazelisk/releases/latest/download/bazelisk-linux-amd64\n   chmod +x ~/bin/bazel\n   export PATH=\"$HOME/bin:$PATH\"\n   ```\n2. **Install system dependencies**:\n   ```bash\n   sudo ./bazel/install-deps.sh\n   ```\n   _This must always be run before any Bazel build, especially on a fresh system or after dependency updates._\n\n### Core Build Steps\n\n- **Build all (fastbuild):**\n  ```bash\n  bazel build //...\n  ```\n- **Test all:**\n  ```bash\n  bazel test //...\n  ```\n- **Lint (C++):**\n  - Formatting and linting are enforced. Use:\n    ```bash\n    bazel run //tools:clang_format\n    ```\n  - Configs: `.clang-format`, `.clang-tidy`, etc.\n\n- **Go CLI (`rpk`) Build:**\n  ```bash\n  bazel build //:rpk\n  ```\n\nSee `.bazelrc` for more details on build settings and config modes\n\n### Validation and CI\n\n- **Pre-push/merge:** All changes are validated by CI (GitHub Actions, Buildkite) for build, test, and lint.\n- **Formatting and lint checks are enforced; run locally before PRs.**\n- **Target branch:** Always open PRs against `dev`.\n\n## Project Layout & Architectural Notes\n\n- **Main C++ source:** `src/v/`\n- **Go CLI:** `src/go/rpk/`\n- **Build configuration:** `.bazelrc`, `.bazelversion`, `BUILD`, `MODULE.bazel`, and `bazel/`\n- **CI configuration:** `.github/workflows/`, `.buildkite/`\n- **Testing:** `tests/`\n- **Config:** `conf/`\n- **Docker:** `tools/docker/`\n\n### Lint/Formatting Configs:\n- `.clang-format`, `.clang-tidy*`: C++ style\n- `tools/ruff`, `.ruff.toml`: Python formatting\n\n### CI/CD Checks\n\n- **Build, test, and lint are enforced by CI.** Use the same steps as above locally before PRs.\n\n### File Index (Root Level)\n- `.bazelignore`, `.bazelrc`, `.bazelversion`, `BUILD`, `MODULE.bazel`, `README.md`, `CONTRIBUTING.md`, `SECURITY.md`, `CODE_OF_CONDUCT.md`, `LICENSES/`, `bazel/`, `src/`, `tests/`, `conf/`, `tools/`, `.github/`, `.buildkite/`, etc.\n\n---\n\n## Protobuf-Specific Instructions\n\n### Protobuf Coding Guidelines\n\nFollow the guidelines provided in `proto/redpanda/README.md` for basic Protobuf standards. For admin v2 ConnectRPC endpoints (proto → C++ service impl → registration → ducktape Python bindings), use the `add-admin-v2-endpoint` skill.\n\n### Protobuf Build & Environment\n- **Primary Protobuf code lives in `proto/`.**\n- **Formatting:** `.clang-format` in the root directory is enforced. Always run `clang-format` before committing changes or submitting a PR:\n  ```bash\n  bazel run //tools:clang_format\n  ```\n\n## C++-Specific Instructions\n\n### C++ Build & Environment\n\n- **Primary C++ code lives in `src/v/`.**\n- **C++ build is managed by Bazel.** All dependencies and toolchains are configured via Bazel rules and the `MODULE.bazel` file. Do not manually install C++ dependencies unless explicitly instructed in documentation.\n- **Compiler Standard:** C++23 is required. Some SDK components (e.g., `src/transform-sdk/cpp/`) use C++23 and specific flags like `-Wall`, `-fno-exceptions`, and for some targets, `-stdlib=libc++`.\n- **Sanitizers:** Some components and test builds use sanitizers (address, leak, undefined) via `-fsanitize=address,leak,undefined` for both compile and link.\n- **Suppression Files:** Leak, undefined, and other sanitizer suppressions can be found in the root as `lsan_suppressions.txt`, `ubsan_suppressions.txt`.\n- **C++ Linting:** `.clang-format` and `.clang-tidy` in the root directory are enforced. Always run `clang-format` before committing changes or submitting a PR:\n  ```bash\n  bazel run //tools:clang_format\n  ```\n- **C++ Libraries:** Bazel dependencies are managed in `MODULE.bazel` (e.g., Boost, Abseil, fmt, protobuf, googletest, yaml-cpp, etc.).\n- **Testing:** C++ unit tests are run via Bazel.\n\n### Common C++ Pitfalls & Workarounds\n\n- **Always use Bazelisk and Bazel for building the core.** Using a plain Bazel binary may result in missing dependencies or incompatible flags.\n- **If you encounter build issues related to missing system libraries, rerun `sudo ./bazel/install-deps.sh`.**\n- **Do not attempt to manually install or update C++ dependencies unless specifically instructed.**\n- **Always run lint and formatter before pushing. CI will fail on formatting/lint discrepancies.**\n- **If building in CI or a containerized environment, ensure the correct toolchain is available as specified in `tools/docker/README.md` or CI scripts.**\n- **Check for additional build and compile flags in `BUILD`, `MODULE.bazel` and related files.**\n\n### C++ coding guidelines\n\nCheck that these guidelines are followed for new code.\n\n- Write [scalable code](docs/writing-scalable-and-high-performance-code.md).\n- Do not declare new `operator<<(ostream& os, type)` overloads, instead prefer to use a `format_to` member function inside `type` as described in\nsrc/v/base/format_to.h.\n- Prefer using latest C++ features (C++23).\n- Use `ss` namespace as a prefix for Seastar types (e.g. `ss::future`, `ss::promise`).\n- Use `vassert(cond, msg, msg_args...)` macro for assertions. It is\n  similar to `assert(cond)` but it is always enabled and it prints the message\n  to the log. Use `dassert` for assertions that are only enabled in debug mode.\n- Use `vlog(method, fmt, args...)` for logging. `method` is the method reference\n  for the logger to use. I.e. `vlog(stlog.info, \"Hello world\");`. Where `stlog`\n  is defined as `ss::logger stlog(\"storage\");`.\n- Do not use `std::vector` for containers that may grow very large, instead use `chunked_vector`.\n- Do not use `std::unordered_map` for containers that may grow very large, instead use `chunked_hash_map`.\n- Instead of long if-else chains for mapping string to values, use\n  `string_switch` mechanism defined in\n  [string_switch.h](./src/v/strings/string_switch.h).\n- Don't use `ss::parallel_for_each` with ranges that can grow large such as\n  partitions, topics or segments. Instead prefer `ss::max_concurrent_for_each` to\n  limit concurrency. Think about how much concurrency is needed to adequately hide\n  latency. See our\n  [docs](https://redpandadata.atlassian.net/wiki/x/AQBZTw#Managing-Concurrency-in-the-system)\n  for more background.\n- Do not call the `get_exception` method on a future within a logging or\n  assertion statement (e.g. `vlog(..., fut.get_exception(), ...)`). Instead,\n  assign the return value of `get_exception` in a variable and pass the variable.\n\n### Coroutines\n\nFollow [coroutines best practices](docs/writing-high-performance-coroutine-code.md) when using\ncoroutines.\n\n#### Lambda coroutines, coroutine argument capture, and deducing this\n\nWhen a lambda coroutine is passed to APIs like `seastar::future::then()`,\nthe lambda object is stored in managed memory that gets freed once the\ncontinuation returns—but the coroutine may still be suspended and later\naccess its captures, causing use-after-free. This happens because the\ncoroutine frame holds a reference to the lambda's capture storage, which\nbecomes dangling. The C++23 \"deducing this\" syntax\n`([captures...](this auto, args...))` solves this by moving the captures\ndirectly into the coroutine frame rather than referencing them through the\nlambda object, decoupling capture lifetime from the lambda's lifetime.\nThis is distinct from the recursive-lambda use case—here this auto is required\nfor memory safety in coroutines, not self-reference.\n\n### Exception handling\n\n- Read the \"Exceptions in coroutines\" section within [tutorial.md](external/+non_module_dependencies+seastar/doc/tutorial.md)\n  to best understand how exceptions should be handled in Redpanda.\n- `throw` and `std::rethrow_exception()` may have an effect on performance, especially in hot paths.\n  There are several `seastar` utilities that should be preferred to explicitly throwing exceptions.\n  Namely, `ss::coroutine::try_future()`, `ss::coroutine::as_future()`, `ss::coroutine::return_exception()`,\n  and `ss::coroutine::return_exception_ptr()`. This is only a brief summary of the content in the tutorial above,\n  which must be read to fully understand exception handling in Redpanda.\n\n### C++ coding style\n\n- Use snake_case for identifiers. Use CamelCase for concepts.\n- Use Doxygen comments with 3-slashes (///) for public APIs\n\n### Code comments\n\n- **Default to no comments** - clear code and good names are better\n- **Avoid comments that restate the code** - they become stale\n- **Prefer alternatives:**\n  - Better variable/function names\n  - Log lines (serve as documentation and debugging)\n- **Do add comments for:**\n  - Doc comments (`/// \\brief`) on public types explaining purpose/usage\n  - Complex algorithms or non-obvious \"gotchas\"\n  - Test comments explaining input format or test intent\n  - Links to external resources (specs, docs, issues)\n  - Mapping internal types/concepts to external formats (e.g., wire protocols, APIs)\n  - ASCII diagrams for complex state machines or data flows\n- **Avoid obvious branching comments** - `if (x)` rarely needs `// when x is true`\n\n### Benchmarking\n\n  - Run benchmarks like `bazel run --config=release //src/v/utils/tests:coro_rpbench`\n  - Get --help like: `bazel run --config=release //src/v/utils/tests:coro_rpbench -- --help`\n  - If running/writing benchmarks read this also: external/+non_module_dependencies+seastar/tests/perf/perf-tests.md\n\n### More C++-Specific References\n\n- [MODULE.bazel](https://github.com/redpanda-data/redpanda/blob/dev/MODULE.bazel)\n- [BUILD](https://github.com/redpanda-data/redpanda/blob/dev/BUILD)\n\n---\n\n## Python specific instructions\n\n### Instructions for python test code under tests/rptest\n\n- Avoid catching bare `except:` as this can hide system exceptions, including exceptions\n  raised by a signal when a test is being forcibly timed out. Instead use `except Exception:`.\n- After modifying files under `tests/rptest`, run type checking via\n  `tools/type-checking/type-check.sh check <files...>` (paths positional, relative to `tests/`)\n  to scope it to your changes. Do not invoke `type-check.py` directly.\n  See `tools/type-checking/README.md` for more details: the per-file\n  strictness levels in `type-check-strictness.json` and how to promote\n  files to stricter levels after adding type hints.\n\n### Instructions for type hints\n\n- Use modern style with `|` instead of `Union` or `Optional`\n\nFor further details, consult:\n- [README.md](https://github.com/redpanda-data/redpanda/blob/dev/README.md)\n- [CONTRIBUTING.md](https://github.com/redpanda-data/redpanda/blob/dev/CONTRIBUTING.md)\n- [Redpanda Documentation](https://redpanda.com/documentation)\n- CI/CD configs in `.github/workflows/` and `.buildkite/`\n\n---\n\n## Commit message instructions\n\nWhen writing or critiquing commit messages, follow these guidelines:\n\n**Format:**\n```\narea[/detail]: short description\n\n<optional body>\n```\n\n- Title: ≤72 chars\n- Body: wrapped at 72 chars\n\n**Check git history first** to match the style of the area you're changing:\n```bash\ngit log --oneline --no-merges -- path/to/changed/files | head -20\n```\n\n**Goal:** Make the commit easy to review and provide context for future readers (via `git blame`/`git log`)\n\n**Rules:**\n- Title: imperative mood, lowercase after colon, no period\n- Keep bodies concise (1-2 lines typical, trivial changes need only a title)\n- Don't reference GitHub issues/PRs or Jira tickets\n- Use the body to help reviewers and future readers understand:\n  - The \"why\": motivation, design choices, preparation for future work\n  - The \"what\": new abstractions introduced, non-obvious changes\n  - Integration tests: briefly note what behaviors are covered\n- Don't restate what's obvious from the diff, but duplicating a doc comment is fine if it helps reviewers understand the change faster.\n\n_Results from code search may be incomplete. For more C++ details, see the [repository code search](https://github.com/redpanda-data/redpanda/search?q=c%2B%2B)._\n","bazel/clang_tidy/plugins/README.md":"# Redpanda clang-tidy plugins\n\nThis directory contains sources and bazel targets for custom out-of-tree clang-tidy checks.\nIt contains a single `redpanda_cc_binary` target, `plugins.so`, which contains a single clang-tidy\nmodule, `redpanda-module`.\n\n## Building\n\n```sh\n$ bazel build //bazel/clang_tidy/plugins:plugins.so\n```\n\n## Adding a new check\n\n1. Write your check in a separate file or files. e.g. `plugins/redpanda_noop_check.h`.\n2. Add your sources directly to `plugins.so.srcs` including any new dependencies.\n3. Register the check by name in `plugins/redpanda_tidy_module.h`.\n   - The name should start with \"redpanda-\".\n\nChecks introduced in this way are automatically loaded in `bazel build --config=clang-tidy //...`.\n\nIf you want to try your check on some source file in isolation, you can invoke the tool directly:\n\n```sh\n$ bazel build //bazel/clang_tidy/plugins:plugins.so\n$ ./tools/clang-tidy --checks=-*,redpanda-my-fancy-check -list-checks\n$ ./tools/clang-tidy --checks=-*,redpanda-my-fancy-check /path/to/some/foo.cc\n```\n","tools/docker/README.md":"# Docker Image with Redpanda Toolchain\n\nCI bazel toolchain image (everything CI runs in this image).\n","tools/go-comment-pbgen/README.md":"# Go Protobuf Comment Generator\n\nCustom `protoc` plugin that extracts proto comments and makes them available at runtime for `rpk shadow config generate-template`.\n\n## Overview\n\nGenerates `comments.pb.go` files containing a simple `map[string]string` of proto field comments keyed by descriptor full names. Proto `.pb.go` files come from `buf.build/gen/go/redpandadata/core`.\n\n\n## Usage\n\nGenerate comment files:\n```bash\nbuf generate --path proto\n```\n\nAccess comments in code:\n```go\nimport v2comments \"github.com/redpanda-data/redpanda/src/go/rpk/gen/protocomments/admin/v2\"\n\ncomment := v2comments.GetCommentForField(fieldDescriptor)\n// Or by full name:\ncomment = v2comments.GetComment(\"redpanda.core.admin.v2.ShadowLink.uuid\")\n```\n\n## Generated Code\n\nEach package gets one `comments.pb.go`:\n```go\npackage v2\n\n// Comments maps descriptor full names to their leading comments\nvar Comments = map[string]string{\n    \"redpanda.core.admin.v2.ShadowLink.uuid\": \"The UUID of the shadow link\",\n    \"redpanda.core.admin.v2.ShadowLink.name\": `The name of the shadow link\nThis is a multi-line comment`,\n}\n\nfunc GetComment(fullName string) string\nfunc GetCommentForField(fd protoreflect.FieldDescriptor) string\nfunc GetCommentForMessage(md protoreflect.MessageDescriptor) string\nfunc GetCommentForEnum(ed protoreflect.EnumDescriptor) string\nfunc GetCommentForEnumValue(evd protoreflect.EnumValueDescriptor) string\n```\n\n**Multi-line comments**: Use raw string literals (backticks) to preserve actual newlines instead of `\\n` escape sequences.\n\n## Output Structure\n\n```\nsrc/go/rpk/gen/protocomments/\n├── admin/v2/comments.pb.go\n└── common/v1/comments.pb.go\n```\n\n## Development\n\nModify plugin:\n```bash\n# 1. Edit generator.go\n# 2. Regenerate\nbuf generate --path \"proto/redpanda/core/admin/v2/shadow_link.proto\",\"proto/redpanda/core/common/v1/acl.proto\",\"proto/redpanda/core/common/v1/tls.proto\"\n# 3. Test\ncd src/go/rpk && go build ./...\n```\n\nCI checks that generated files are up-to-date in `.github/workflows/rpk-build.yml`.\n","tools/metrics-mock/README.md":"# Metrics Mock\n\nmetrics-mock.py is a script that starts an http server and serves\nredpanda-like metrics from /metrics and /public-metrics endpoints.\n\nSo it serves as a stand-in for Redpanda when testing things in the\nmetrics pipeline, as using a large clusters simply to generate large\nnumbers of metrics is wasteful.\n\n## Usage\n\nThe script has no dependencies and should be directly executable on\nPython 3.10 or later.\n\nThe primary functionality of metrics mock is accessble through `metrics-mock.py host`,\nwhich starts the HTTP server on port `9644` (configurable with `--port`):\n\n```\n./metrics-mock host\n```\n\nOn another terminal run:\n\n```\n$ curl -sL 'http://127.0.0.1:9644/metrics' | head\n```\n\nwhich should produce output like:\n\n```\n# HELP vectorized_alien_receive_batch_queue_length Current receive batch queue length\n# TYPE vectorized_alien_receive_batch_queue_length gauge\nvectorized_alien_receive_batch_queue_length{shard=\"0\"} 5923\n# HELP vectorized_alien_total_received_messages Total number of received messages\n# TYPE vectorized_alien_total_received_messages counter\nvectorized_alien_total_received_messages{shard=\"0\"} 1033\n# HELP vectorized_alien_total_sent_messages Total number of sent messages\n# TYPE vectorized_alien_total_sent_messages counter\nvectorized_alien_total_sent_messages{shard=\"0\"} 13440\n# HELP vectorized_application_build Redpanda build information\n```\n\nThere are several options to modify the behavior of the metrics endpoint,\nspecifically to emulate different sizes of Redpanda node and Redpanda cluster.\n\nE.g., to approximate a T7 cluster, use the following flags:\n\n\n```\n./metrics-mock.py print --partitions 50000 --topics 10 --shards 31 --nodes 9\n```\n\nEach `./metrics-mock.py` instance acts as a single node in a multi-node cluster:\nin the above example the mock will act as a single node in a 9 node cluster. This\nis important when deciding which metrics appear for resources which are allocated\nto a partitcular subset nodes, such as topic partitions. To emulate a differnet\nnode, pass `--node-id`. If you are emulating a multi-node cluster you should pass\na different node id to each mock, or else they all return the same metrics for\nper-topic, per-partition or per-group metrics, which will significantly underestimate\nthe total metrics count.\n\n\n\n\n","tools/offline_log_viewer/README.md":"Offline tool to parse RedPanda disk logs.\nUsage: `python viewer.py -h`","tools/p/README.md":"The `p` exectuable script is a containerized drop-in replacement for `p`.\n\n```\ntools/p/p compile\n```\n","tools/rp_storage_tool/README.md":"# rp-storage-tool\n\n## Purpose\n\nThis low level tool is for offline use by Redpanda Engineering when diagnosing\nfaults.\n\nThis tool is **not** for everyday use on live clusters.\n\n## Quickstart\n\n    # Get a rust toolchain\n    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n\n    # Compile and run\n    cargo run --release -- --backend=<aws|gcp|azure> scan --source=<bucket name>\n\n## Installation\n\nTo run the tool outside a built tree, you may simply copy the statically linked binary.\n\n    # Compile\n    cargo build --release\n\n    # Grab the binary\n    cp target/release/rp-storage-tool /usr/local/bin\n\n## Usage\n\n### Typical usage\n\nThe following examples assume you are running on an AWS instance with IAM roles configured\nfor access to your bucket. If you need to specify static keys, see the \"Working with local object stores\"\nsection below.\n\n    # Scan the bucket, store metadata in a file, and report any metadata anomalies\n    $ rp-storage-tool --backend aws scan-metadata --source=my-bucket --meta-file my-bucket.json\n\n    # Scan the data within a topic of interest, and report any anomalies.  Use the metadata\n    # file from the last step to avoid re-scanning all objects in the bucket.\n    $ rp-storage-tool --filter kafka/my-topic/*_* --backend aws scan-data --source=my-bucket --meta-file my-bucket.json\n\n    # Having identified an issue, extract the data from a partition for further analysis offline\n    $ mkdir my-data-dump\n    $ rp-storage-tool --filter kafka/my-topic/13_63 --backend aws extract --source=my-bucket --meta-file my-bucket.json --sink ./my-data-dump/\n\n### Generic parameters\n\n* The `--backend` argument selects the cloud storage backend. This has a default (AWS) for convenience when working with\n  commands that don't use cloud storage, but ordinarily you should be specifying it.\n* The `--filter` argument controls which topics & partitions will be examined. When scanning metadata we\n  always scan all objects, but will only store+analyze metadata for partitions matching the filter.\n* If you are running on a node with authentication already set up (e.g. IAM Roles on AWS), this will Just Work.\n  Otherwise you may need to set the appropriate access key/secret environment variables for the cloud platform you are\n  connecting to.\n\n### Subcommands\n\n#### `scan-metadata`\n\nThis walks the objects in a bucket used by Redpanda Tiered Storage and reports on any inconsistencies.\n**Not all issues this tool reports are harmful**, for example `segments_outside_manifest` may contain objects harmlessly\nleft behind when Redpanda was restarted during an upload.\n\n#### `scan-data`\n\nThis walks the data within segments (you probably want to use `--filter` to only scan segments for certain NTPS)\nto check that it is readable, and that the metadata in the partition manifest is consistent with the data.\n\n#### `extract`\n\nFor NTPs matching the filter, copy all metadata and data objects into a local folder.\n\n## Working with local object stores\n\nThis tool uses environment variables for any special configuration of storage backends required for working outside of\nreal cloud environments.\n\nFor example, to use the tool with a bucket called `data` in a minio cluster at `aplite:9000`:\n\n    AWS_ALLOW_HTTP=1 AWS_ACCESS_KEY_ID=minioadmin AWS_SECRET_ACCESS_KEY=minioadmin AWS_REGION=us-east-1 \\\n      AWS_ENDPOINT=http://aplite:9000 cargo run --release -- --backend aws scan --source data\n\n### Building a portable binary\n\nIf you are building on a workstation and copying the binary to a remote machine, and your\nworkstation doesn't happen to run the same linux distro as the remote machine, it is\nuseful to build a statically linked binary that will work on any distro.\n\n    # A dependency requires this to build: adjust as needed if not on an RPM distro\n    sudo dnf install -y musl-gcc\n\n    # Install the Rust toolchain for musl (statically linked libc)\n    rustup target add x86_64-unknown-linux-musl\n\n    # Build with the musl toolchain\n    cargo build --release --target=x86_64-unknown-linux-musl\n\n    # Output in target/release/rp-storage-tool\n\n","tools/type-checking/README.md":"# Ducktape Type Checking\n\nThis directory contains the infrastructure for running PEP-484 type checking on Redpanda's Python test suite (ducktape) using [pyright](https://github.com/microsoft/pyright).\n\n## Overview\n\nThe type checking system provides:\n- **Gradual type checking adoption** with per-file strictness levels\n- **Automated promotion detection** to identify files ready for stricter checking\n- **CI integration** to prevent type regressions\n- **Docker-based execution** for consistent environments\n\n## Files\n\n### `type-check.py`\n\nThe main type checking orchestration script. Provides multiple commands:\n\n- **`check`** - Run type checking on files and report errors\n- **`promotion-check`** - Identify files that can be promoted to stricter type checking levels\n- **`fruit`** - Show files with fewest errors at each target level (low-hanging fruit for type checking improvements)\n- **`ci`** - Run both `check` and `promotion-check` and decorates the results (used in CI)\n- **`check-sorted`** - Verify that `type-check-strictness.json` is sorted\n- **`pre-commit`** - Run `check` on changed files (intended for use as a pre-commit hook); accepts paths rooted at repo root (`tests/...`) and strips the prefix internally\n\n### `type-check-strictness.json`\n\nConfiguration file that maps Python files to their current type checking strictness level. Files not listed default to `strict` level.\n\n**Strictness levels** (from most to least strict):\n- `strict` - Full type checking (default for unlisted files)\n- `standard` - Standard type checking\n- `basic` - Basic type checking\n- `off` - Minimal checking (syntax, imports, etc.)\n- `skip` - No type checking at all\n\n### `Dockerfile`\nMulti-stage Docker image for running type checking in a consistent environment. Installs Python dependencies and pyright.\n\n### `type-check.sh`\nShell script that builds the Docker image and runs type checking inside a container.\n\n### `requirements.txt`\nPython dependencies for type checking (primarily pyright).\n\n## Usage\n\n### Local Development\n\n#### Using task\n\nThere is a type-check `task` which is a very thin wrapper around the scripts: this is probably the most convenient way to call it as it works\nanywhere in the repository. Call it with arguments for the script after `--`, like so:\n\n```\ntask rp:type-check -- --help\n```\n\n#### Using type-check.sh\n\nYou can also call the script as `tools/type-checking/type-check.sh --help`. This is essentially the same as the `task` invocation above. This runs the underlying python script inside a docker container, so it should \"just work\".\n\n#### Using type-check.py\n\nLocally you should also able to run the .py script directly, like `tools/type-checking/type-check.py --help`  if you have run [local_venv.sh](../../tests/local_venv.sh).\n\n### Examples\n\nThese examples all the `task` invocation method but work with any of the methods above.\n\n```bash\n# Run type checking on all files\ntask rp:type-check -- check\n\n# Check for files ready to be promoted to stricter levels\ntask rp:type-check -- promotion-check\n\n# Find low-hanging fruit - files with fewest errors at each target level\ntask rp:type-check -- fruit\n\n# Show top 25 fruit candidates\ntask rp:type-check -- fruit --top 25\n\n# Show detailed error information with verbose output\ntask rp:type-check -- fruit --verbose 2\n\n# Automatically promote eligible files\ntask rp:type-check -- promotion-check --update\n\n# Run CI checks (both check and promotion-check)\ntask rp:type-check -- ci\n\n# Check specific files (paths/globs are positional, relative to tests/)\ntask rp:type-check -- check rptest/tests/my_test.py\n\n# Force all files to be checked at a specific level\ntask rp:type-check -- check --force-level strict\n```\n\n## Strictness Level Management\n\nIn order to have type checking useful immediately, without needing to fix 1000s of existing type errors, the system uses a progressive approach to type checking adoption:\n\n1. **New files** default to `strict` type checking\n2. **Existing files** are assigned to the strictest level they currently pass\n3. **Promotion detection** automatically identifies when files can move to stricter levels\n4. **Automated updates** can promote files with `--update` flag\n\n### Adding Type Hints to Files\n\nWhen adding type hints to improve a file:\n\n1. Run `tools/type-checking/type-check.py promotion-check --update`\n2. This will automatically move the file to the strictest level it now passes\n3. Commit both your type hint changes and the updated `type-check-strictness.json`\n\n### Finding Low-Hanging Fruit\n\nThe `fruit` command helps identify files that are closest to being promotable to stricter type checking levels by showing files with the fewest errors at each target level:\n\n```bash\n# Show top 10 files with fewest errors per target level\ntask rp:type-check -- fruit\n\n# Show top 20 files with fewest errors per target level\ntask rp:type-check -- fruit --top 20\n\n# Show detailed error information with verbose output\ntask rp:type-check -- fruit --verbose 2\n```\n\nThis is useful for:\n- **Prioritizing type checking improvements** - start with files that need minimal fixes\n- **Setting realistic goals** - see how many errors need to be fixed for each promotion\n- **Understanding error distribution** - identify patterns in type checking issues\n\nThe command groups files by their potential promotion target (off→basic, basic→standard, etc.) and sorts them by error count, making it easy to find files that are closest to passing at the next strictness level.\n\n### Failed Promotion Check\n\nYou may be here because CI failed with a \"promotion check\" error. This means that the strictness level for some files are set too low, i.e., your change has improved the state of the Python code base and as a reward you can promote some files to higher strictness levels. Pat yourself on the back, then use the following command to automatically update the strictness file:\n\n```bash\n# Automatically update the configuration\ntask rp:type-check -- promotion-check --update\n\n# Commit the updated type-check-strictness.json\ngit add tools/type-checking/type-check-strictness.json\ngit commit -m \"chore: update type checking strictness\"\n```\n\n## Command Line Options\n\n- `--tests-root PATH` - Path to tests directory (auto-detected by default)\n- `--config PATH` - Path to pyrightconfig.json\n- `input_files` - Positional paths/globs of files to check, relative to `tests/` (default: `rptest/**/*.py`)\n- `--force-level LEVEL` - Override strictness levels and check all files at specified level\n- `--no-venv` - Don't use virtual environment when running pyright\n- `--verbose [LEVEL]` - Enable verbose output (0=off, 1=verbose, 2=very verbose)\n- `--update` - Update type-check-strictness.json in-place (for promotion-check)\n- `--top N` - Number of files to show per target level in fruit command (default: 10)\n\n## CI Integration\n\nThe type checking runs automatically on pull requests via GitHub Actions (`.github/workflows/python-type-check.yml`). The CI:\n\n1. Builds the Docker image from `Dockerfile`\n2. Runs `type-check.py ci` which executes both type checking and promotion detection\n3. Fails if there are type errors or files that should be promoted\n\nThis check tries to be as fast as possible, and usually runs in about 3 minutes if the docker image build is fully cached (usually the case), or a few minutes more if not.\n\n## Architecture\n\nThe system is designed around Microsoft's pyright type checker with these key features:\n\n- **Segmented checking**: Files are grouped by strictness level and checked separately\n- **Temporary configs**: Generates temporary pyrightconfig.json files for each strictness level\n- **Batch processing**: Efficiently checks multiple files at once when possible\n- **Virtual environment integration**: Uses `.venv` if available for consistent Python environment\n\n## Adding New Features\n\nThe `type-check.py` script is modular and can be extended with new commands by:\n\n1. Adding the command name to the `CMDS` list\n2. Adding documentation to `COMMAND_DOC`\n3. Implementing a method on the `TypeCheck` class with the command name (with hyphens converted to underscores)\n\nThe script follows the pattern where each command method returns `True` for success, `False` for failure, or `None` for success (which is treated as `True`).\n"},"files":{".claude/skills/add-admin-v2-endpoint/SKILL.md":"---\nname: add-admin-v2-endpoint\ndescription: Use when adding or modifying a Redpanda admin v2 ConnectRPC endpoint — authoring the protobuf under proto/redpanda/core/admin/v2/, wiring it into the Bazel build, implementing the C++ service handler under src/v/redpanda/admin/services/, registering it on the admin server, and regenerating Python bindings for ducktape integration tests. Covers both new RPCs on an existing service and brand-new services.\n---\n\n# Add an Admin v2 ConnectRPC Endpoint\n\nThis skill walks through every layer touched by a new admin v2 RPC: the `.proto`, the Bazel build, the C++ service implementation, registration on the admin server, and the Python bindings consumed by ducktape tests.\n\nFor generic protobuf authoring conventions (field options, message design, well-known types), the canonical reference is `proto/redpanda/README.md`. This skill assumes you've read that and focuses on what's specific to admin v2.\n\n## Reference example\n\nCommit `04d3160c3a` (`cluster: add manual upgrade finalization with admin v2 RPC`) is the cleanest minimal example — a new service with a single RPC. Skim these when unsure:\n\n- `proto/redpanda/core/admin/v2/features.proto` — the proto\n- `proto/redpanda/core/admin/v2/BUILD` — the BUILD entries (look at the `features_proto` / `features_redpanda_proto` pair)\n- `src/v/redpanda/admin/services/features.h` and `features.cc` — the C++ impl\n- `src/v/redpanda/admin/services/BUILD` — the `features` cc_library target\n- `src/v/redpanda/application_admin.cc` — the `add_service` registration\n\n`git show 04d3160c3a` will print the full diff.\n\n## Procedure\n\n### 1. Author the protobuf\n\nCreate or extend a `.proto` under `proto/redpanda/core/admin/v2/`. One file per service domain; the filename matches the dominant service (`features.proto` → `FeaturesService`).\n\nRequired header for a new file:\n\n```proto\n// (BSL copyright header — copy from features.proto:1-13)\nsyntax = \"proto3\";\n\npackage redpanda.core.admin.v2;\n\nimport \"proto/redpanda/core/pbgen/options.proto\";\nimport \"proto/redpanda/core/pbgen/rpc.proto\";\n\noption (pbgen.cpp_namespace) = \"proto::admin\";\n```\n\nNotes:\n\n- `package` is always `redpanda.core.admin.v2` for admin v2.\n- `option (pbgen.cpp_namespace) = \"proto::admin\";` is mandatory and always exactly this value for admin v2 — the short form (`pbgen.cpp_namespace`, not the FQN `redpanda.core.pbgen.cpp_namespace`) is what's used in the tree.\n- Import `rpc.proto` only when the file defines a `service`.\n\nService / RPC block:\n\n```proto\nservice MyService {\n    // One short paragraph explaining what the RPC does and any\n    // async/poll behavior the caller needs to know.\n    rpc MyMethod(MyMethodRequest) returns (MyMethodResponse) {\n        option (pbgen.rpc) = {\n            authz: SUPERUSER\n        };\n    }\n}\n\nmessage MyMethodRequest {}\nmessage MyMethodResponse {}\n```\n\n- Every RPC must carry `option (pbgen.rpc) = { authz: ... }`. Default to `SUPERUSER` for admin v2 — anything else (`USER`, `PUBLIC`) needs core-team approval (see `proto/redpanda/core/pbgen/rpc.proto`).\n- Optional `http_route: \"/v2/...\"` adds an alias HTTP path. The auto-generated `/<package>.<Service>/<Method>` route always works without it.\n- Naming: messages are `<RpcName>Request` / `<RpcName>Response`, one pair per RPC. Empty messages are fine.\n\nField-level options worth knowing (full reference in `proto/redpanda/core/pbgen/options.proto`):\n\n- `[(pbgen.iobuf) = true]` on `string` fields that may exceed 128 KiB — generates `iobuf` instead of `ss::sstring`. `bytes` is already iobuf-backed.\n- `[(pbgen.ptr) = true]` on message fields for indirection (recursive types or when you need to distinguish unset).\n- `[debug_redact = true]` on sensitive fields — suppresses them from `fmt::formatter` output (passwords, tokens, etc.).\n\nWell-known types (`google.protobuf.Duration`, `Timestamp`, `FieldMask`) are supported; importing them requires the corresponding BUILD deps in step 2.\n\n### 2. Wire up the Bazel build\n\nEach `.proto` needs **two stacked targets** in `proto/redpanda/core/admin/v2/BUILD`. Append (alphabetical order in the file):\n\n```python\nproto_library(\n    name = \"myservice_proto\",\n    srcs = [\"myservice.proto\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//proto/redpanda/core/pbgen:options_proto\",\n        \"//proto/redpanda/core/pbgen:rpc_proto\",\n        # Add per-import:\n        # \"@protobuf//:timestamp_proto\",\n        # \"@protobuf//:duration_proto\",\n        # \"@protobuf//:field_mask_proto\",\n        # \"@googleapis//google/api:field_info_proto\",\n        # \"@googleapis//google/api:field_behavior_proto\",\n        # \"@googleapis//google/api:resource_proto\",\n        # Cross-proto imports: e.g. \":kafka_connections_proto\"\n    ],\n)\n\nredpanda_proto_library(\n    name = \"myservice_redpanda_proto\",\n    protos = [\":myservice_proto\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        # Add when the proto uses Timestamp or Duration:\n        # \"@abseil-cpp//absl/time:time\",\n        # Add when the proto uses FieldMask:\n        # \"//src/v/serde/protobuf:field_mask\",\n        # For cross-proto deps the *_redpanda_proto sibling goes here:\n        # \":kafka_connections_redpanda_proto\"\n    ],\n)\n```\n\nRules:\n\n- `proto_library.deps` mirrors `import` statements in the `.proto` file.\n- `redpanda_proto_library.deps` carries C++ deps the *generated* code needs (e.g. abseil for timestamps), plus any sibling `_redpanda_proto` targets for cross-proto imports.\n- The pbgen plugin lives at `bazel/pbgen/`; it generates `<name>.proto.h` and `<name>.proto.cc` with the message types, formatters, and service stubs.\n\n### 3. Implement the C++ service\n\nIf extending an existing service (just adding a new RPC), open the matching `src/v/redpanda/admin/services/<name>.{h,cc}` and add the new handler. Skip to step 4 after.\n\nFor a brand-new service, create `src/v/redpanda/admin/services/<name>.h`:\n\n```cpp\n/*\n * Copyright 2026 Redpanda Data, Inc.\n * (BSL header — copy from features.h:1-10)\n */\n#pragma once\n\n#include \"cluster/fwd.h\"\n#include \"proto/redpanda/core/admin/v2/<name>.proto.h\"\n#include \"redpanda/admin/proxy/client.h\"\n\nnamespace admin {\n\nclass <name>_service_impl : public proto::admin::<name>_service {\npublic:\n    <name>_service_impl(\n      admin::proxy::client,\n      /* collaborators — e.g. cluster::controller*, ss::sharded<X>& */);\n\n    ss::future<proto::admin::<rpc>_response> <rpc>(\n      serde::pb::rpc::context, proto::admin::<rpc>_request) override;\n\nprivate:\n    admin::proxy::client _proxy_client;\n    /* members for collaborators */\n};\n\n} // namespace admin\n```\n\nNaming conventions (CamelCase → snake_case translation):\n\n- Service base class: `proto::admin::<service>_service` (e.g. `FeaturesService` → `features_service`)\n- Generated client (for inter-node proxying): `proto::admin::<service>_service_client`\n- Request / response types: `proto::admin::<rpc_name>_request` / `_response`\n- Handler method name: snake_case of the RPC name (e.g. `FinalizeUpgrade` → `finalize_upgrade`)\n- Implementation class: `admin::<service>_service_impl`, in namespace `admin` (NOT `proto::admin`)\n\nHandler signature is fixed: `ss::future<...response>` return type, `serde::pb::rpc::context` first arg, request second, `override`.\n\nThen `src/v/redpanda/admin/services/<name>.cc`:\n\n```cpp\n#include \"redpanda/admin/services/<name>.h\"\n#include \"redpanda/admin/services/utils.h\"\n#include \"serde/protobuf/rpc.h\"\n#include <seastar/core/coroutine.hh>\n\nnamespace admin {\nnamespace {\nss::logger <name>log{\"admin_api_server/<name>_service\"};\n} // namespace\n\n<name>_service_impl::<name>_service_impl(/* args */) : _proxy_client(...) {}\n\nss::future<proto::admin::<rpc>_response>\n<name>_service_impl::<rpc>(\n  serde::pb::rpc::context ctx, proto::admin::<rpc>_request req) {\n    vlog(<name>log.trace, \"<rpc>: {}\", req);\n    // ... validate, optionally redirect-to-leader, do work ...\n    co_return proto::admin::<rpc>_response{};\n}\n\n} // namespace admin\n```\n\nPatterns to know — read the existing service files for live examples:\n\n- **Validation failures throw typed exceptions** declared in `src/v/serde/protobuf/rpc.h:241-287`: `invalid_argument_exception`, `failed_precondition_exception`, `not_found_exception`, `already_exists_exception`, `permission_denied_exception`, `unavailable_exception`, `unauthenticated_exception`, etc. Each maps to a ConnectRPC/gRPC status code. Example: `features.cc:46`.\n- **Leader redirect**: `admin::utils::redirect_to_leader(...)` from `services/utils.h` returns an optional `node_id`; if set, proxy the call to that node using the generated `_service_client`. Pattern in `features.cc:51-64`.\n- **Proxying to peers**: `_proxy_client.make_client_for_node<proto::admin::<service>_service_client>(node)` for one target, or `.make_clients_for_other_nodes<...>()` for scatter-gather. Defined in `redpanda/admin/proxy/client.h`.\n- **License gating**: `admin::utils::check_license(_feature_table.local())` if the RPC is enterprise-gated. See `cluster.cc:51`.\n- **Avoid proxy loops** in scatter-gather handlers: gate with `proxy::is_proxied(ctx)` (example in `cluster.cc:56`).\n\nAdd the BUILD entry to `src/v/redpanda/admin/services/BUILD`:\n\n```python\nredpanda_cc_library(\n    name = \"<name>\",\n    srcs = [\"<name>.cc\"],\n    hdrs = [\"<name>.h\"],\n    implementation_deps = [\n        \":utils\",\n        # .cc-only collaborators\n    ],\n    deps = [\n        \"//proto/redpanda/core/admin/v2:<name>_redpanda_proto\",\n        \"//src/v/cluster:fwd\",\n        \"//src/v/redpanda/admin/proxy:client\",\n        \"//src/v/serde/protobuf:rpc\",\n        \"@seastar\",\n        # types that appear in <name>.h\n    ],\n)\n```\n\nRule: types that appear in the `.h` belong in `deps`; collaborators only used in the `.cc` belong in `implementation_deps`.\n\n### 4. Register the service\n\nTwo edits in `src/v/redpanda/`:\n\n**4a. `src/v/redpanda/BUILD`** — add the new services library to the top-level `redpanda` library's `deps` list (kept sorted):\n\n```python\n\"//src/v/redpanda/admin/services:<name>\",\n```\n\n**4b. `src/v/redpanda/application_admin.cc`** — two changes:\n\n1. Include the header (alphabetical with the other service includes):\n   ```cpp\n   #include \"redpanda/admin/services/<name>.h\"\n   ```\n2. Inside `_admin.invoke_on_all([this, node_id](admin_server& s) { ... })`, append an `add_service` call. `create_client()` is the local lambda defined at the top of the block; pass other collaborators via `controller.get()` or `std::ref(...)`:\n   ```cpp\n   s.add_service(\n     std::make_unique<admin::<name>_service_impl>(\n       create_client(), /* collaborators */));\n   ```\n\nThat's it for registration. `add_service` walks the service's routes and registers an HTTP POST handler per route at `route.path` with the auth wrapper keyed on `route.authz_level`.\n\n### 5. Build and format\n\n```bash\nbazel build //proto/redpanda/core/admin/v2:<name>_redpanda_proto\nbazel build //src/v/redpanda/admin/services:<name>\nbazel build //src/v/redpanda:application\nbazel run //tools:clang_format\n```\n\nFix compilation errors before moving on. The pbgen plugin's error messages are usually self-explanatory; common causes are missing BUILD deps from step 2 or imports in the `.proto` without matching `proto_library.deps`.\n\n### 6. Regenerate ducktape Python bindings\n\nDucktape tests consume admin v2 via committed-to-repo Python bindings under `tests/rptest/clients/admin/proto/`. They are regenerated by a script — CI (`.github/workflows/check-ducktape-protos.yml`) fails the PR if they're stale, so this step is mandatory whenever a `.proto` under `proto/redpanda/core/admin/**` changes.\n\nPrerequisite: `uv` installed (https://docs.astral.sh/uv/getting-started/installation/). The script aborts otherwise.\n\n```bash\nbazel fetch //proto/...          # one-time; populates the googleapis external dir\ntools/regenerate_ducktape_protos.sh\n```\n\nThe script wipes `tests/rptest/clients/admin/proto/**/*.py{,i}` and regenerates from the proto glob using `grpc_tools.protoc` + `mypy-protobuf` + `connect-python`, then runs `protoletariat` to rewrite imports to relative form. The glob picks up new `.proto` files automatically — **no manifest to update**.\n\nCommit the generated files. `.gitignore` does not exclude them.\n\n### 7. Add a ducktape client accessor (only for new services)\n\nIf you created a brand-new service, expose it from the `Admin` wrapper at `tests/rptest/clients/admin/v2.py`. Two edits:\n\n1. Add imports near the top (line ~9), alongside the existing service imports:\n   ```python\n   from rptest.clients.admin.proto.redpanda.core.admin.v2 import (\n       <name>_pb2,\n       <name>_pb2_connect,\n       # ... existing\n   )\n   ```\n   And add a convenience re-export (`<name>_pb = <name>_pb2`) in the re-export block (around line ~52).\n\n2. Add an accessor method on `Admin` matching the existing pattern (line ~133):\n   ```python\n   def <name>(self, **kwargs: Any) -> <name>_pb2_connect.<Name>ServiceClient:\n       return self._make_service(<name>_pb2_connect.<Name>ServiceClient, **kwargs)\n   ```\n\nExisting services (`broker`, `cluster`, `features`, `security`, `shadow_link`, `datalake`, `debug`, `breakglass`, `metastore`, `l0`, `internal_shadow_link`) show the shape. If you only added a new RPC to an existing service, no edit to `v2.py` is needed — `_make_service` returns the generated client which exposes every RPC as a method.\n\nA ducktape test uses the new RPC like this:\n\n```python\nfrom rptest.clients.admin.v2 import Admin as AdminV2, <name>_pb\n\nadmin_v2 = AdminV2(self.redpanda,\n                   auth=(self.superuser.username, self.superuser.password))\nreq = <name>_pb.<Rpc>Request(...)\nresp = admin_v2.<name>().<rpc>(req)\n```\n\n## Verification\n\nAfter all edits:\n\n```bash\nbazel build //...\nbazel run //tools:clang_format\ntools/regenerate_ducktape_protos.sh   # idempotent — confirms generated tree matches\ngit status                            # should show all expected files staged\n```\n\nManual smoke (optional, but the canonical way to confirm wiring):\n\n1. `bazel build //src/v/redpanda:application` or a debug-binary target.\n2. Start a local cluster; invoke the RPC via the ducktape `AdminV2` client, or `curl` against `/redpanda.core.admin.v2.<Service>/<Method>` with a protobuf-encoded body.\n\n## Out of scope\n\n**Go bindings for `rpk` consumption.** Admin v2 Go bindings are not generated in this repo — they're pulled from the Buf Schema Registry as a Go module (`buf.build/gen/go/redpandadata/core/...`). CI publishes to BSR automatically on merge to `dev` or a `v*` branch (see `.github/workflows/buf.yml`). Consuming a new RPC from `rpk` is a follow-up PR after the proto change has merged and the BSR push has run — out of scope for this skill.\n\nIf the user mentions needing `rpk` to consume the new RPC, surface this as a separate follow-up task rather than attempting it in the same change.\n\n## Rules and pitfalls\n\n- **Auth level defaults to `SUPERUSER`.** Don't use `USER` or `PUBLIC` without explicit approval — the proto README is clear on this.\n- **The proto's `option (pbgen.cpp_namespace)` is always `\"proto::admin\"` for admin v2.** Don't invent a per-service namespace.\n- **`add_service` registration order matters only for readability.** The existing block in `application_admin.cc` is not strictly sorted; match its grouping (regular services, then `if (cloud_topics_app)` services, then internal/security at the end).\n- **`bazel fetch //proto/...` is a prerequisite for `regenerate_ducktape_protos.sh`** — the script needs the googleapis external dir to exist. The script's error message is clear, but the failure mode (script aborts with \"googleapis directory not found\") is easy to misdiagnose if you skip it.\n- **CI enforces generated-file freshness.** `check-ducktape-protos` will fail if you forget step 6. The fix is always: re-run the script and commit the diff.\n- **Don't add comments that restate the code.** RPC docstrings in the `.proto` are valuable (they become the public contract); duplicating them in the C++ handler isn't.\n- **The handler signature `ss::future<R> name(serde::pb::rpc::context, Request)` is fixed** — context first, request second, both by value, return future. Deviating produces inscrutable pbgen-template errors.\n- **Validation errors must throw the typed exceptions from `src/v/serde/protobuf/rpc.h`.** Returning a default response, returning an `errc`, or throwing `std::runtime_error` will not surface a proper ConnectRPC status to the caller.\n",".claude/skills/create-backport-branch/SKILL.md":"---\nname: create-backport-branch\ndescription: \"Create a new backport branch with prefix `ai-backport-` by cherry-picking all commits from a GitHub PR onto a target release branch or commit. Use when the user wants to backport a PR, create a backport branch, cherry-pick PR commits to a release branch, or mentions backporting changes from dev/main to a version branch (e.g. v25.1.x, v25.2.x). Requires two arguments: TARGET (the release branch or commit hash to backport onto) and PR_NUM (the GitHub PR number to backport).\"\nargument-hint: [target-branch-or-commit] [pr-number]\nallowed-tools: Bash(git *), Bash(gh *), Bash(date *), Bash(grep *), Bash(wc *), Read, Write, Edit, Glob, Grep, Agent\nmodel: opus[1m]\neffort: high\n---\n\n# Create Backport Branch\n\nCherry-pick all commits from a GitHub PR onto a target release branch, resolving conflicts along the way.\n\nArguments:\n- `$0` is target — a release branch (e.g. `v25.1.x`) or a commit hash (e.g. `81d9e1af33`)\n- `$1` is pr-number (e.g. `12345`)\n\n## Procedure\n\n### 1. Validate inputs and set up\n\n1. Both arguments must be provided. If either target-branch or pr-number is empty, abort with: \"Usage: /create-backport-branch <target-branch> <pr-number>\"\n2. Generate the backport-branch-name using the current Unix timestamp:\n   ```bash\n   date +%s\n   ```\n   backport-branch-name format: `ai-backport-pr-${pr_number}-${target_branch}-${unix_timestamp}`\n\n### 2. Create the backport branch\n\n```bash\ngit checkout -b $backport_branch_name $target_branch\n```\n\n### 3. Fetch PR commits\n\nGet the list of commit SHAs from the PR:\n\n```bash\ngh api \"repos/{owner}/{repo}/pulls/$pr_number/commits\" --paginate --jq '[.[].sha[0:10]]|join(\" \")'\n```\n\n`gh` resolves the `{owner}` and `{repo}` placeholders from the git remote of the current directory (see [`gh api` docs](https://cli.github.com/manual/gh_api)). Run this from a checkout of the repo that owns the PR.\n\nSave the full list of commits as `git_commits` and report how many commits were found.\n\n### 4. Cherry-pick commits\n\nRun the cherry-pick for all commits at once:\n\n```bash\ngit cherry-pick -x $git_commits\n```\n\nTrack these counters as you go:\n- **total_commits**: number of commits from the PR\n- **conflicts_resolved**: number of commits that needed conflict resolution\n- **commits_skipped**: number of commits skipped because they were already present on the target branch\n- **generated_files_touched**: list of generated files that were part of the cherry-picked commits (whether conflicted or not)\n\nIf the cherry-pick succeeds cleanly, check if any generated files were touched (see step 4a), then skip to step 6.\n\n### 4a. Check for generated files in cherry-picked commits\n\nAfter each successful cherry-pick (clean or after conflict resolution), check if any generated files were touched. Use the same generated file patterns from step 5.3. For clean cherry-picks, check the files changed in the commit(s) just applied:\n\n```bash\ngit diff --name-only HEAD~1 HEAD\n```\n\nMatch the output against the generated file patterns. Add any matches to `generated_files_touched`.\n\n### 5. Resolve conflicts\n\nIf the cherry-pick fails with merge conflicts, attempt resolution. For each conflicted state:\n\n1. Identify the commit that caused the conflict (the one currently being cherry-picked).\n2. List conflicted files: `git diff --name-only --diff-filter=U`\n3. Categorize each file:\n   - **Generated files** (accept theirs and move on): `*.pb.go`, `*.pb.h`, `*.pb.cc`, `*.pb.rs`, `*_pb2.py`, `go.sum`, `*.lock`, `MODULE.bazel.lock`, `package-lock.json`, `Cargo.lock`, `MODULE.bazel`\n     - For generated files: `git checkout --theirs -- $FILE && git add -- $FILE`\n     - Add each generated file to the `generated_files_touched` list\n   - **Modify/delete conflicts**: file deleted on one side but modified on the other. These require human judgment — **abort immediately** (see abort procedure below).\n   - **Content conflicts**: files with `<<<<<<<` / `=======` / `>>>>>>>` markers. Attempt resolution.\n\n4. For each content conflict:\n   a. Read the full conflicted file\n   b. Run `git log -p -1 $FAILING_COMMIT -- $FILE` to understand what the cherry-picked commit intended\n   c. Understand both sides:\n      - **HEAD side** (`<<<<<<<` to `=======`): what the target release branch has\n      - **Incoming side** (`=======` to `>>>>>>>`): what the cherry-picked commit brings\n   d. Read surrounding code if needed to understand context (imports, types, function signatures)\n   e. Determine the correct resolution:\n      - The goal is to apply the *intent* of the cherry-picked commit to the target branch\n      - Adapt to the target branch's patterns, names, and APIs where they diverge\n   f. Edit the file to remove all conflict markers and write the correct merged code\n   g. Stage: `git add -- $FILE`\n\n5. Verify the resolution is clean:\n   ```bash\n   grep -rn '<<<<<<< \\|=======$\\|>>>>>>> ' -- $RESOLVED_FILES\n   git diff --name-only --diff-filter=U\n   ```\n   Both commands should produce no output. If unmerged paths remain, something was missed.\n\n6. Continue the cherry-pick:\n   ```bash\n   git cherry-pick --continue --no-edit\n   ```\n   If `--continue` reports \"The previous cherry-pick is now empty\", the commit's changes are already on the target branch. Run `git cherry-pick --skip` to move on, and increment `commits_skipped`.\n\n7. Increment `conflicts_resolved` counter.\n\n8. If the next commit also conflicts, repeat from step 5.1.\n\n### 5a. Empty cherry-picks (no conflict)\n\nA cherry-pick can also be empty without any conflict — git will report \"The previous cherry-pick is now empty\" immediately. This happens when the commit's changes already exist on the target branch (e.g., from a prior backport or parallel fix). Run `git cherry-pick --skip` to move on and increment `commits_skipped`. This is normal and not an error.\n\n**Abort procedure** — If at any point you are uncertain how to resolve a conflict (ambiguous intent, modify/delete conflicts, architectural changes you don't understand, or anything that feels risky):\n\n1. Run `git cherry-pick --abort`\n2. Report an error with:\n   - The exact `git cherry-pick -x ...` command that was being attempted\n   - The specific commit SHA that caused the uncertain conflict\n   - Which files were conflicted and why you couldn't resolve them\n3. Stop immediately. Do NOT attempt further cherry-picks.\n\n### 6. Final report\n\nAfter all commits are cherry-picked successfully, resolve the source-PR URL dynamically:\n\n```bash\npr_url=$(gh pr view $pr_number --json url --jq .url)\n```\n\nThen emit this report to stdout:\n\n```\nBackport of PR ${pr_url}\n\n- Command: git cherry-pick -x ${git_commits}\n- Commits backported: ${total_commits}\n- Conflicts resolved: ${conflicts_resolved}\n- Commits skipped (already on target): ${commits_skipped}\n- Backport branch: ${backport_branch_name}\n\n## Conflict details\n\n- ${commit_sha} (${file}): one-line explanation of what conflicted and how it was resolved\n```\n\nFor each conflict resolved, include a one-line explanation covering what conflicted and how you resolved it. This helps PR reviewers understand the backport without reading every diff.\n\nIf `generated_files_touched` is non-empty, append this warning section:\n\n```\n## ⚠️ Generated files\n\nThe following files were cherry-picked and may need regeneration:\n\n- ${file_1}\n- ${file_2}\n\nThese files were accepted as-is from the source branch. Before merging,\nregenerate them on the target branch to ensure they're correct. For example:\n- MODULE.bazel.lock: run `bazel mod deps --lockfile_mode=update`\n- *.pb.go / *.pb.cc / *.pb.h: rebuild protobuf targets\n- go.sum: run `go mod tidy`\n```\n\n**Optional: machine-readable report.** If the `SKILL_REPORT_FILE` env var is set (callers like GitHub Actions workflows use this), write the exact same report text to that file path. The file content is designed to be usable verbatim as the body of the backport PR. Humans invoking the skill directly do not set this variable and simply read the report on stdout.\n\n## Rules\n\n- NEVER guess at conflict resolutions. If uncertain, abort immediately using the abort procedure.\n- NEVER silently drop code. Incoming changes that add new functionality must be preserved (adapted to the target branch's conventions).\n- Do NOT add comments to resolved code unless the original commit had them.\n- Preserve the formatting style of the target branch.\n- For generated files (protobuf, lockfiles, MODULE.bazel), accept theirs during the cherry-pick to unblock it. Two follow-ups are then required:\n  - List each touched generated file under the \"Generated files\" warning section in the PR body so the human reviewer regenerates and commits the correct version before merge.\n  - Do not describe these files as \"regenerated by the build\" in conflict-detail lines. Bazel/protoc/etc. update them in the local workspace only; CI does not push a corrected version back to the PR, so any regeneration must be committed manually.\n",".claude/skills/create-backport-branch/evals/evals.json":"{\n  \"skill_name\": \"create-backport-branch\",\n  \"evals\": [\n    {\n      \"id\": 0,\n      \"name\": \"clean-cherry-pick\",\n      \"prompt\": \"/create-backport-branch 81d9e1af33 30160\",\n      \"expected_output\": \"Creates a backport branch from commit 81d9e1af33 (v25.3.x before PR 30160 was backported). The single commit from PR #30160 should cherry-pick cleanly with no conflicts and no empty result. Produces a structured report.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Branch was created with naming pattern ai-backport-pr-30160-81d9e1af33-{timestamp}\",\n        \"Cherry-pick command used the -x flag to record the source commit\",\n        \"Cherry-pick succeeded cleanly with no conflicts\",\n        \"Final report includes total commits count (1), conflicts resolved count (0), and commits skipped count (0)\",\n        \"Final report includes the backport branch name\",\n        \"The backport branch has exactly 1 new commit beyond the base\"\n      ]\n    },\n    {\n      \"id\": 1,\n      \"name\": \"multi-commit-conflict\",\n      \"prompt\": \"/create-backport-branch 5cdd00f4ca 30089\",\n      \"expected_output\": \"Creates a backport branch from commit 5cdd00f4ca (v26.1.x). Cherry-picks 8 commits from PR #30089. Should encounter conflict in feature_table.h and resolve it by adapting to the target branch's release_version values.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Branch was created with naming pattern ai-backport-pr-30089-5cdd00f4ca-{timestamp}\",\n        \"All 8 commits were fetched from the PR via gh api\",\n        \"Cherry-pick command used the -x flag\",\n        \"Either all conflicts were resolved and continued, OR agent aborted cleanly listing the specific commit SHA and conflicted files\",\n        \"If conflicts were resolved, the final report includes a per-conflict one-line explanation\",\n        \"No conflict markers (<<<<<<< or ======= or >>>>>>>) remain in any resolved files\"\n      ]\n    },\n    {\n      \"id\": 2,\n      \"name\": \"generated-files-modify-delete\",\n      \"prompt\": \"/create-backport-branch d0a6248434 30115\",\n      \"expected_output\": \"Creates a backport branch from commit d0a6248434 (v25.1.x). Cherry-picks 3 commits from PR #30115. Should encounter modify/delete conflict on buf.gen.yaml and abort cleanly per skill rules.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Branch was created with naming pattern ai-backport-pr-30115-d0a6248434-{timestamp}\",\n        \"Generated files (go.sum, MODULE.bazel, or similar) were identified\",\n        \"Cherry-pick command used the -x flag\",\n        \"Agent aborted cleanly on modify/delete conflict, listing the specific commit SHA and conflicted files\",\n        \"Abort report includes the exact cherry-pick command that was being attempted\",\n        \"Final report includes total commits count, conflicts resolved count, and commits skipped count\"\n      ]\n    },\n    {\n      \"id\": 3,\n      \"name\": \"already-present-empty\",\n      \"prompt\": \"/create-backport-branch ea6e2533c7 30160\",\n      \"expected_output\": \"Creates a backport branch from commit ea6e2533c7 (v25.3.x after PR 30160 was already backported). The cherry-pick should result in an empty commit since the change is already present. Should handle with git cherry-pick --skip and report as skipped.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Branch was created with naming pattern ai-backport-pr-30160-ea6e2533c7-{timestamp}\",\n        \"Cherry-pick command used the -x flag\",\n        \"Empty cherry-pick was handled with git cherry-pick --skip (not --abort)\",\n        \"Final report includes commits skipped count of 1\",\n        \"Final report correctly indicates the commit was already on the target branch\",\n        \"The backport branch was preserved (not deleted)\"\n      ]\n    }\n  ]\n}\n",".claude/skills/improve-coverage/SKILL.md":"---\nname: improve-coverage\ndescription: Improve code coverage for a Bazel C++ test target or source file\nuser-invocable: true\n---\n\n# Improve Code Coverage\n\nImprove test coverage for a Bazel C++ test target or source file path.\n\n## Input Resolution\n\n1. **Bazel test target** (starts with `//`, e.g. `//src/v/bytes/tests:iobuf_test`):\n   use it directly.\n\n2. **Source file path** (e.g. `src/v/bytes/iobuf.cc`): find the test target:\n   ```bash\n   bazel query \"kind('.*_test', rdeps(//src/v/..., //$(dirname $FILE):$(basename $FILE .cc), 1))\" 2>/dev/null\n   ```\n   If multiple targets match, pick the one in the closest `tests/` directory.\n   If none match, tell the user and stop.\n\n## Workflow\n\n### Step 1: Baseline Coverage\n\nRun coverage and read the LLM report:\n\n```bash\ntools/run-cov <target> -r llm\n```\n\nNote the **total line coverage percentage** — this is the baseline to beat.\n\nRead the report and identify:\n- **Uncovered functions** — highest impact. One test can cover many lines.\n- **Partially covered functions** — uncovered branches indicate missing\n  error-path or edge-case tests.\n- **Zero-coverage files in scope** — may need new test cases entirely.\n\n### Step 2: Understand the Code\n\nRead the source files named in the report. Focus on:\n- Uncovered functions: what they do, inputs, preconditions.\n- The existing test file: patterns, fixtures, helpers.\n\nKey codebase patterns:\n- `redpanda_cc_gtest` targets use GTest (`TEST()`, `TEST_F()`).\n  `redpanda_cc_btest` targets use Seastar Boost test\n  (`SEASTAR_THREAD_TEST_CASE`). Match whichever the test file uses.\n- Many functions are `ss::future<>` coroutines — test with\n  `SEASTAR_THREAD_TEST_CASE` or the Seastar GTest runner.\n- Use `ss` namespace prefix for Seastar types.\n- Use `EXPECT_*` for most checks, `ASSERT_*` only when continuing would crash.\n\n### Step 3: Write Tests\n\nPrioritize by impact:\n1. **Uncovered functions** — call each one. A 20-line function = 20 lines covered.\n2. **Uncovered error paths** — trigger the uncovered branches in partially\n   covered functions.\n3. **Zero-coverage files** — add smoke tests if practical.\n\nRules:\n- Add tests to the **existing test file**. Don't create new files or BUILD\n  targets unless necessary.\n- Follow existing naming conventions in the file.\n- One logical behavior per test case.\n- Prefer deterministic tests — construct error conditions directly.\n- Do NOT add comments that restate the code.\n\n### Step 4: Verify Build\n\n```bash\nbazel build <target>\n```\n\nFix compilation errors before proceeding.\n\n### Step 5: Verify Coverage\n\nRe-run coverage:\n\n```bash\ntools/run-cov <target> -r llm\n```\n\nCompare to the baseline. Report:\n\n```\nCoverage: X.Y% → A.B% (+N.N%)\nNew lines covered: <count>\n```\n\nIf coverage did not improve, investigate:\n- Build errors preventing the new tests from running?\n- Code compiled out by preprocessor guards?\n- Template instantiations not triggered by test types?\n\n### Step 6: Summary\n\nReport:\n- Which functions/paths are now covered\n- What test cases were added\n- Before/after coverage numbers\n- Remaining significant gaps and suggestions\n",".github/copilot-instructions.md":"CLAUDE.md","CLAUDE.md":"# Copilot Coding Agent Onboarding Guide for `redpanda-data/redpanda`\n\n## High-Level Overview\n\n**What is Redpanda?**\n\nRedpanda is a high-performance, Apache Kafka®-compatible streaming data platform. It is written primarily in C++ for the core, with Go for CLI tooling (`rpk`), and some Python for auxiliary scripts and tests. Redpanda is designed to be lightweight, fast, and simple to operate, omitting ZooKeeper and the JVM.\n\nIt uses extensively the thread-per-core model and asynchronous (coroutines, futures) programming model.\n\n**Repository Characteristics:**\n- **Large multi-language codebase:** C++ (core), Go (CLI/tools), Python (testing/scripts), Bash and Bazel for builds.\n- **Build System:** Bazel (with Bazelisk) for core.\n- **Target Platforms:** Linux (primary), some support for macOS and Windows.\n- **Key Directories:**\n  - `src/v/`: Core C++ source code\n  - `src/go/`: Go CLI and tools\n  - `bazel/`, `BUILD`, `MODULE.bazel`: Bazel scripts and definitions\n  - `tools/`: Development and helper scripts\n  - `tests/`: Test suites\n  - `conf/`: Configuration files\n  - `proto/`: Protobuf definitions for Redpanda services and APIs\n  - `.github/`, `.buildkite/`: CI/workflow automation\n- **Documentation:** [Docs site](https://redpanda.com/documentation) and `docs/`.\n\n## Build, Test, and Validation Instructions\n\n### Prerequisites (Always Perform)\n\n1. **Install Bazelisk** (the required Bazel wrapper):\n   ```bash\n   wget -O ~/bin/bazel https://github.com/bazelbuild/bazelisk/releases/latest/download/bazelisk-linux-amd64\n   chmod +x ~/bin/bazel\n   export PATH=\"$HOME/bin:$PATH\"\n   ```\n2. **Install system dependencies**:\n   ```bash\n   sudo ./bazel/install-deps.sh\n   ```\n   _This must always be run before any Bazel build, especially on a fresh system or after dependency updates._\n\n### Core Build Steps\n\n- **Build all (fastbuild):**\n  ```bash\n  bazel build //...\n  ```\n- **Test all:**\n  ```bash\n  bazel test //...\n  ```\n- **Lint (C++):**\n  - Formatting and linting are enforced. Use:\n    ```bash\n    bazel run //tools:clang_format\n    ```\n  - Configs: `.clang-format`, `.clang-tidy`, etc.\n\n- **Go CLI (`rpk`) Build:**\n  ```bash\n  bazel build //:rpk\n  ```\n\nSee `.bazelrc` for more details on build settings and config modes\n\n### Validation and CI\n\n- **Pre-push/merge:** All changes are validated by CI (GitHub Actions, Buildkite) for build, test, and lint.\n- **Formatting and lint checks are enforced; run locally before PRs.**\n- **Target branch:** Always open PRs against `dev`.\n\n## Project Layout & Architectural Notes\n\n- **Main C++ source:** `src/v/`\n- **Go CLI:** `src/go/rpk/`\n- **Build configuration:** `.bazelrc`, `.bazelversion`, `BUILD`, `MODULE.bazel`, and `bazel/`\n- **CI configuration:** `.github/workflows/`, `.buildkite/`\n- **Testing:** `tests/`\n- **Config:** `conf/`\n- **Docker:** `tools/docker/`\n\n### Lint/Formatting Configs:\n- `.clang-format`, `.clang-tidy*`: C++ style\n- `tools/ruff`, `.ruff.toml`: Python formatting\n\n### CI/CD Checks\n\n- **Build, test, and lint are enforced by CI.** Use the same steps as above locally before PRs.\n\n### File Index (Root Level)\n- `.bazelignore`, `.bazelrc`, `.bazelversion`, `BUILD`, `MODULE.bazel`, `README.md`, `CONTRIBUTING.md`, `SECURITY.md`, `CODE_OF_CONDUCT.md`, `LICENSES/`, `bazel/`, `src/`, `tests/`, `conf/`, `tools/`, `.github/`, `.buildkite/`, etc.\n\n---\n\n## Protobuf-Specific Instructions\n\n### Protobuf Coding Guidelines\n\nFollow the guidelines provided in `proto/redpanda/README.md` for basic Protobuf standards. For admin v2 ConnectRPC endpoints (proto → C++ service impl → registration → ducktape Python bindings), use the `add-admin-v2-endpoint` skill.\n\n### Protobuf Build & Environment\n- **Primary Protobuf code lives in `proto/`.**\n- **Formatting:** `.clang-format` in the root directory is enforced. Always run `clang-format` before committing changes or submitting a PR:\n  ```bash\n  bazel run //tools:clang_format\n  ```\n\n## C++-Specific Instructions\n\n### C++ Build & Environment\n\n- **Primary C++ code lives in `src/v/`.**\n- **C++ build is managed by Bazel.** All dependencies and toolchains are configured via Bazel rules and the `MODULE.bazel` file. Do not manually install C++ dependencies unless explicitly instructed in documentation.\n- **Compiler Standard:** C++23 is required. Some SDK components (e.g., `src/transform-sdk/cpp/`) use C++23 and specific flags like `-Wall`, `-fno-exceptions`, and for some targets, `-stdlib=libc++`.\n- **Sanitizers:** Some components and test builds use sanitizers (address, leak, undefined) via `-fsanitize=address,leak,undefined` for both compile and link.\n- **Suppression Files:** Leak, undefined, and other sanitizer suppressions can be found in the root as `lsan_suppressions.txt`, `ubsan_suppressions.txt`.\n- **C++ Linting:** `.clang-format` and `.clang-tidy` in the root directory are enforced. Always run `clang-format` before committing changes or submitting a PR:\n  ```bash\n  bazel run //tools:clang_format\n  ```\n- **C++ Libraries:** Bazel dependencies are managed in `MODULE.bazel` (e.g., Boost, Abseil, fmt, protobuf, googletest, yaml-cpp, etc.).\n- **Testing:** C++ unit tests are run via Bazel.\n\n### Common C++ Pitfalls & Workarounds\n\n- **Always use Bazelisk and Bazel for building the core.** Using a plain Bazel binary may result in missing dependencies or incompatible flags.\n- **If you encounter build issues related to missing system libraries, rerun `sudo ./bazel/install-deps.sh`.**\n- **Do not attempt to manually install or update C++ dependencies unless specifically instructed.**\n- **Always run lint and formatter before pushing. CI will fail on formatting/lint discrepancies.**\n- **If building in CI or a containerized environment, ensure the correct toolchain is available as specified in `tools/docker/README.md` or CI scripts.**\n- **Check for additional build and compile flags in `BUILD`, `MODULE.bazel` and related files.**\n\n### C++ coding guidelines\n\nCheck that these guidelines are followed for new code.\n\n- Write [scalable code](docs/writing-scalable-and-high-performance-code.md).\n- Do not declare new `operator<<(ostream& os, type)` overloads, instead prefer to use a `format_to` member function inside `type` as described in\nsrc/v/base/format_to.h.\n- Prefer using latest C++ features (C++23).\n- Use `ss` namespace as a prefix for Seastar types (e.g. `ss::future`, `ss::promise`).\n- Use `vassert(cond, msg, msg_args...)` macro for assertions. It is\n  similar to `assert(cond)` but it is always enabled and it prints the message\n  to the log. Use `dassert` for assertions that are only enabled in debug mode.\n- Use `vlog(method, fmt, args...)` for logging. `method` is the method reference\n  for the logger to use. I.e. `vlog(stlog.info, \"Hello world\");`. Where `stlog`\n  is defined as `ss::logger stlog(\"storage\");`.\n- Do not use `std::vector` for containers that may grow very large, instead use `chunked_vector`.\n- Do not use `std::unordered_map` for containers that may grow very large, instead use `chunked_hash_map`.\n- Instead of long if-else chains for mapping string to values, use\n  `string_switch` mechanism defined in\n  [string_switch.h](./src/v/strings/string_switch.h).\n- Don't use `ss::parallel_for_each` with ranges that can grow large such as\n  partitions, topics or segments. Instead prefer `ss::max_concurrent_for_each` to\n  limit concurrency. Think about how much concurrency is needed to adequately hide\n  latency. See our\n  [docs](https://redpandadata.atlassian.net/wiki/x/AQBZTw#Managing-Concurrency-in-the-system)\n  for more background.\n- Do not call the `get_exception` method on a future within a logging or\n  assertion statement (e.g. `vlog(..., fut.get_exception(), ...)`). Instead,\n  assign the return value of `get_exception` in a variable and pass the variable.\n\n### Coroutines\n\nFollow [coroutines best practices](docs/writing-high-performance-coroutine-code.md) when using\ncoroutines.\n\n#### Lambda coroutines, coroutine argument capture, and deducing this\n\nWhen a lambda coroutine is passed to APIs like `seastar::future::then()`,\nthe lambda object is stored in managed memory that gets freed once the\ncontinuation returns—but the coroutine may still be suspended and later\naccess its captures, causing use-after-free. This happens because the\ncoroutine frame holds a reference to the lambda's capture storage, which\nbecomes dangling. The C++23 \"deducing this\" syntax\n`([captures...](this auto, args...))` solves this by moving the captures\ndirectly into the coroutine frame rather than referencing them through the\nlambda object, decoupling capture lifetime from the lambda's lifetime.\nThis is distinct from the recursive-lambda use case—here this auto is required\nfor memory safety in coroutines, not self-reference.\n\n### Exception handling\n\n- Read the \"Exceptions in coroutines\" section within [tutorial.md](external/+non_module_dependencies+seastar/doc/tutorial.md)\n  to best understand how exceptions should be handled in Redpanda.\n- `throw` and `std::rethrow_exception()` may have an effect on performance, especially in hot paths.\n  There are several `seastar` utilities that should be preferred to explicitly throwing exceptions.\n  Namely, `ss::coroutine::try_future()`, `ss::coroutine::as_future()`, `ss::coroutine::return_exception()`,\n  and `ss::coroutine::return_exception_ptr()`. This is only a brief summary of the content in the tutorial above,\n  which must be read to fully understand exception handling in Redpanda.\n\n### C++ coding style\n\n- Use snake_case for identifiers. Use CamelCase for concepts.\n- Use Doxygen comments with 3-slashes (///) for public APIs\n\n### Code comments\n\n- **Default to no comments** - clear code and good names are better\n- **Avoid comments that restate the code** - they become stale\n- **Prefer alternatives:**\n  - Better variable/function names\n  - Log lines (serve as documentation and debugging)\n- **Do add comments for:**\n  - Doc comments (`/// \\brief`) on public types explaining purpose/usage\n  - Complex algorithms or non-obvious \"gotchas\"\n  - Test comments explaining input format or test intent\n  - Links to external resources (specs, docs, issues)\n  - Mapping internal types/concepts to external formats (e.g., wire protocols, APIs)\n  - ASCII diagrams for complex state machines or data flows\n- **Avoid obvious branching comments** - `if (x)` rarely needs `// when x is true`\n\n### Benchmarking\n\n  - Run benchmarks like `bazel run --config=release //src/v/utils/tests:coro_rpbench`\n  - Get --help like: `bazel run --config=release //src/v/utils/tests:coro_rpbench -- --help`\n  - If running/writing benchmarks read this also: external/+non_module_dependencies+seastar/tests/perf/perf-tests.md\n\n### More C++-Specific References\n\n- [MODULE.bazel](https://github.com/redpanda-data/redpanda/blob/dev/MODULE.bazel)\n- [BUILD](https://github.com/redpanda-data/redpanda/blob/dev/BUILD)\n\n---\n\n## Python specific instructions\n\n### Instructions for python test code under tests/rptest\n\n- Avoid catching bare `except:` as this can hide system exceptions, including exceptions\n  raised by a signal when a test is being forcibly timed out. Instead use `except Exception:`.\n- After modifying files under `tests/rptest`, run type checking via\n  `tools/type-checking/type-check.sh check <files...>` (paths positional, relative to `tests/`)\n  to scope it to your changes. Do not invoke `type-check.py` directly.\n  See `tools/type-checking/README.md` for more details: the per-file\n  strictness levels in `type-check-strictness.json` and how to promote\n  files to stricter levels after adding type hints.\n\n### Instructions for type hints\n\n- Use modern style with `|` instead of `Union` or `Optional`\n\nFor further details, consult:\n- [README.md](https://github.com/redpanda-data/redpanda/blob/dev/README.md)\n- [CONTRIBUTING.md](https://github.com/redpanda-data/redpanda/blob/dev/CONTRIBUTING.md)\n- [Redpanda Documentation](https://redpanda.com/documentation)\n- CI/CD configs in `.github/workflows/` and `.buildkite/`\n\n---\n\n## Commit message instructions\n\nWhen writing or critiquing commit messages, follow these guidelines:\n\n**Format:**\n```\narea[/detail]: short description\n\n<optional body>\n```\n\n- Title: ≤72 chars\n- Body: wrapped at 72 chars\n\n**Check git history first** to match the style of the area you're changing:\n```bash\ngit log --oneline --no-merges -- path/to/changed/files | head -20\n```\n\n**Goal:** Make the commit easy to review and provide context for future readers (via `git blame`/`git log`)\n\n**Rules:**\n- Title: imperative mood, lowercase after colon, no period\n- Keep bodies concise (1-2 lines typical, trivial changes need only a title)\n- Don't reference GitHub issues/PRs or Jira tickets\n- Use the body to help reviewers and future readers understand:\n  - The \"why\": motivation, design choices, preparation for future work\n  - The \"what\": new abstractions introduced, non-obvious changes\n  - Integration tests: briefly note what behaviors are covered\n- Don't restate what's obvious from the diff, but duplicating a doc comment is fine if it helps reviewers understand the change faster.\n\n_Results from code search may be incomplete. For more C++ details, see the [repository code search](https://github.com/redpanda-data/redpanda/search?q=c%2B%2B)._\n","bazel/clang_tidy/plugins/README.md":"# Redpanda clang-tidy plugins\n\nThis directory contains sources and bazel targets for custom out-of-tree clang-tidy checks.\nIt contains a single `redpanda_cc_binary` target, `plugins.so`, which contains a single clang-tidy\nmodule, `redpanda-module`.\n\n## Building\n\n```sh\n$ bazel build //bazel/clang_tidy/plugins:plugins.so\n```\n\n## Adding a new check\n\n1. Write your check in a separate file or files. e.g. `plugins/redpanda_noop_check.h`.\n2. Add your sources directly to `plugins.so.srcs` including any new dependencies.\n3. Register the check by name in `plugins/redpanda_tidy_module.h`.\n   - The name should start with \"redpanda-\".\n\nChecks introduced in this way are automatically loaded in `bazel build --config=clang-tidy //...`.\n\nIf you want to try your check on some source file in isolation, you can invoke the tool directly:\n\n```sh\n$ bazel build //bazel/clang_tidy/plugins:plugins.so\n$ ./tools/clang-tidy --checks=-*,redpanda-my-fancy-check -list-checks\n$ ./tools/clang-tidy --checks=-*,redpanda-my-fancy-check /path/to/some/foo.cc\n```\n","tools/docker/README.md":"# Docker Image with Redpanda Toolchain\n\nCI bazel toolchain image (everything CI runs in this image).\n","tools/go-comment-pbgen/README.md":"# Go Protobuf Comment Generator\n\nCustom `protoc` plugin that extracts proto comments and makes them available at runtime for `rpk shadow config generate-template`.\n\n## Overview\n\nGenerates `comments.pb.go` files containing a simple `map[string]string` of proto field comments keyed by descriptor full names. Proto `.pb.go` files come from `buf.build/gen/go/redpandadata/core`.\n\n\n## Usage\n\nGenerate comment files:\n```bash\nbuf generate --path proto\n```\n\nAccess comments in code:\n```go\nimport v2comments \"github.com/redpanda-data/redpanda/src/go/rpk/gen/protocomments/admin/v2\"\n\ncomment := v2comments.GetCommentForField(fieldDescriptor)\n// Or by full name:\ncomment = v2comments.GetComment(\"redpanda.core.admin.v2.ShadowLink.uuid\")\n```\n\n## Generated Code\n\nEach package gets one `comments.pb.go`:\n```go\npackage v2\n\n// Comments maps descriptor full names to their leading comments\nvar Comments = map[string]string{\n    \"redpanda.core.admin.v2.ShadowLink.uuid\": \"The UUID of the shadow link\",\n    \"redpanda.core.admin.v2.ShadowLink.name\": `The name of the shadow link\nThis is a multi-line comment`,\n}\n\nfunc GetComment(fullName string) string\nfunc GetCommentForField(fd protoreflect.FieldDescriptor) string\nfunc GetCommentForMessage(md protoreflect.MessageDescriptor) string\nfunc GetCommentForEnum(ed protoreflect.EnumDescriptor) string\nfunc GetCommentForEnumValue(evd protoreflect.EnumValueDescriptor) string\n```\n\n**Multi-line comments**: Use raw string literals (backticks) to preserve actual newlines instead of `\\n` escape sequences.\n\n## Output Structure\n\n```\nsrc/go/rpk/gen/protocomments/\n├── admin/v2/comments.pb.go\n└── common/v1/comments.pb.go\n```\n\n## Development\n\nModify plugin:\n```bash\n# 1. Edit generator.go\n# 2. Regenerate\nbuf generate --path \"proto/redpanda/core/admin/v2/shadow_link.proto\",\"proto/redpanda/core/common/v1/acl.proto\",\"proto/redpanda/core/common/v1/tls.proto\"\n# 3. Test\ncd src/go/rpk && go build ./...\n```\n\nCI checks that generated files are up-to-date in `.github/workflows/rpk-build.yml`.\n","tools/metrics-mock/README.md":"# Metrics Mock\n\nmetrics-mock.py is a script that starts an http server and serves\nredpanda-like metrics from /metrics and /public-metrics endpoints.\n\nSo it serves as a stand-in for Redpanda when testing things in the\nmetrics pipeline, as using a large clusters simply to generate large\nnumbers of metrics is wasteful.\n\n## Usage\n\nThe script has no dependencies and should be directly executable on\nPython 3.10 or later.\n\nThe primary functionality of metrics mock is accessble through `metrics-mock.py host`,\nwhich starts the HTTP server on port `9644` (configurable with `--port`):\n\n```\n./metrics-mock host\n```\n\nOn another terminal run:\n\n```\n$ curl -sL 'http://127.0.0.1:9644/metrics' | head\n```\n\nwhich should produce output like:\n\n```\n# HELP vectorized_alien_receive_batch_queue_length Current receive batch queue length\n# TYPE vectorized_alien_receive_batch_queue_length gauge\nvectorized_alien_receive_batch_queue_length{shard=\"0\"} 5923\n# HELP vectorized_alien_total_received_messages Total number of received messages\n# TYPE vectorized_alien_total_received_messages counter\nvectorized_alien_total_received_messages{shard=\"0\"} 1033\n# HELP vectorized_alien_total_sent_messages Total number of sent messages\n# TYPE vectorized_alien_total_sent_messages counter\nvectorized_alien_total_sent_messages{shard=\"0\"} 13440\n# HELP vectorized_application_build Redpanda build information\n```\n\nThere are several options to modify the behavior of the metrics endpoint,\nspecifically to emulate different sizes of Redpanda node and Redpanda cluster.\n\nE.g., to approximate a T7 cluster, use the following flags:\n\n\n```\n./metrics-mock.py print --partitions 50000 --topics 10 --shards 31 --nodes 9\n```\n\nEach `./metrics-mock.py` instance acts as a single node in a multi-node cluster:\nin the above example the mock will act as a single node in a 9 node cluster. This\nis important when deciding which metrics appear for resources which are allocated\nto a partitcular subset nodes, such as topic partitions. To emulate a differnet\nnode, pass `--node-id`. If you are emulating a multi-node cluster you should pass\na different node id to each mock, or else they all return the same metrics for\nper-topic, per-partition or per-group metrics, which will significantly underestimate\nthe total metrics count.\n\n\n\n\n","tools/offline_log_viewer/README.md":"Offline tool to parse RedPanda disk logs.\nUsage: `python viewer.py -h`","tools/p/README.md":"The `p` exectuable script is a containerized drop-in replacement for `p`.\n\n```\ntools/p/p compile\n```\n","tools/rp_storage_tool/README.md":"# rp-storage-tool\n\n## Purpose\n\nThis low level tool is for offline use by Redpanda Engineering when diagnosing\nfaults.\n\nThis tool is **not** for everyday use on live clusters.\n\n## Quickstart\n\n    # Get a rust toolchain\n    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n\n    # Compile and run\n    cargo run --release -- --backend=<aws|gcp|azure> scan --source=<bucket name>\n\n## Installation\n\nTo run the tool outside a built tree, you may simply copy the statically linked binary.\n\n    # Compile\n    cargo build --release\n\n    # Grab the binary\n    cp target/release/rp-storage-tool /usr/local/bin\n\n## Usage\n\n### Typical usage\n\nThe following examples assume you are running on an AWS instance with IAM roles configured\nfor access to your bucket. If you need to specify static keys, see the \"Working with local object stores\"\nsection below.\n\n    # Scan the bucket, store metadata in a file, and report any metadata anomalies\n    $ rp-storage-tool --backend aws scan-metadata --source=my-bucket --meta-file my-bucket.json\n\n    # Scan the data within a topic of interest, and report any anomalies.  Use the metadata\n    # file from the last step to avoid re-scanning all objects in the bucket.\n    $ rp-storage-tool --filter kafka/my-topic/*_* --backend aws scan-data --source=my-bucket --meta-file my-bucket.json\n\n    # Having identified an issue, extract the data from a partition for further analysis offline\n    $ mkdir my-data-dump\n    $ rp-storage-tool --filter kafka/my-topic/13_63 --backend aws extract --source=my-bucket --meta-file my-bucket.json --sink ./my-data-dump/\n\n### Generic parameters\n\n* The `--backend` argument selects the cloud storage backend. This has a default (AWS) for convenience when working with\n  commands that don't use cloud storage, but ordinarily you should be specifying it.\n* The `--filter` argument controls which topics & partitions will be examined. When scanning metadata we\n  always scan all objects, but will only store+analyze metadata for partitions matching the filter.\n* If you are running on a node with authentication already set up (e.g. IAM Roles on AWS), this will Just Work.\n  Otherwise you may need to set the appropriate access key/secret environment variables for the cloud platform you are\n  connecting to.\n\n### Subcommands\n\n#### `scan-metadata`\n\nThis walks the objects in a bucket used by Redpanda Tiered Storage and reports on any inconsistencies.\n**Not all issues this tool reports are harmful**, for example `segments_outside_manifest` may contain objects harmlessly\nleft behind when Redpanda was restarted during an upload.\n\n#### `scan-data`\n\nThis walks the data within segments (you probably want to use `--filter` to only scan segments for certain NTPS)\nto check that it is readable, and that the metadata in the partition manifest is consistent with the data.\n\n#### `extract`\n\nFor NTPs matching the filter, copy all metadata and data objects into a local folder.\n\n## Working with local object stores\n\nThis tool uses environment variables for any special configuration of storage backends required for working outside of\nreal cloud environments.\n\nFor example, to use the tool with a bucket called `data` in a minio cluster at `aplite:9000`:\n\n    AWS_ALLOW_HTTP=1 AWS_ACCESS_KEY_ID=minioadmin AWS_SECRET_ACCESS_KEY=minioadmin AWS_REGION=us-east-1 \\\n      AWS_ENDPOINT=http://aplite:9000 cargo run --release -- --backend aws scan --source data\n\n### Building a portable binary\n\nIf you are building on a workstation and copying the binary to a remote machine, and your\nworkstation doesn't happen to run the same linux distro as the remote machine, it is\nuseful to build a statically linked binary that will work on any distro.\n\n    # A dependency requires this to build: adjust as needed if not on an RPM distro\n    sudo dnf install -y musl-gcc\n\n    # Install the Rust toolchain for musl (statically linked libc)\n    rustup target add x86_64-unknown-linux-musl\n\n    # Build with the musl toolchain\n    cargo build --release --target=x86_64-unknown-linux-musl\n\n    # Output in target/release/rp-storage-tool\n\n","tools/type-checking/README.md":"# Ducktape Type Checking\n\nThis directory contains the infrastructure for running PEP-484 type checking on Redpanda's Python test suite (ducktape) using [pyright](https://github.com/microsoft/pyright).\n\n## Overview\n\nThe type checking system provides:\n- **Gradual type checking adoption** with per-file strictness levels\n- **Automated promotion detection** to identify files ready for stricter checking\n- **CI integration** to prevent type regressions\n- **Docker-based execution** for consistent environments\n\n## Files\n\n### `type-check.py`\n\nThe main type checking orchestration script. Provides multiple commands:\n\n- **`check`** - Run type checking on files and report errors\n- **`promotion-check`** - Identify files that can be promoted to stricter type checking levels\n- **`fruit`** - Show files with fewest errors at each target level (low-hanging fruit for type checking improvements)\n- **`ci`** - Run both `check` and `promotion-check` and decorates the results (used in CI)\n- **`check-sorted`** - Verify that `type-check-strictness.json` is sorted\n- **`pre-commit`** - Run `check` on changed files (intended for use as a pre-commit hook); accepts paths rooted at repo root (`tests/...`) and strips the prefix internally\n\n### `type-check-strictness.json`\n\nConfiguration file that maps Python files to their current type checking strictness level. Files not listed default to `strict` level.\n\n**Strictness levels** (from most to least strict):\n- `strict` - Full type checking (default for unlisted files)\n- `standard` - Standard type checking\n- `basic` - Basic type checking\n- `off` - Minimal checking (syntax, imports, etc.)\n- `skip` - No type checking at all\n\n### `Dockerfile`\nMulti-stage Docker image for running type checking in a consistent environment. Installs Python dependencies and pyright.\n\n### `type-check.sh`\nShell script that builds the Docker image and runs type checking inside a container.\n\n### `requirements.txt`\nPython dependencies for type checking (primarily pyright).\n\n## Usage\n\n### Local Development\n\n#### Using task\n\nThere is a type-check `task` which is a very thin wrapper around the scripts: this is probably the most convenient way to call it as it works\nanywhere in the repository. Call it with arguments for the script after `--`, like so:\n\n```\ntask rp:type-check -- --help\n```\n\n#### Using type-check.sh\n\nYou can also call the script as `tools/type-checking/type-check.sh --help`. This is essentially the same as the `task` invocation above. This runs the underlying python script inside a docker container, so it should \"just work\".\n\n#### Using type-check.py\n\nLocally you should also able to run the .py script directly, like `tools/type-checking/type-check.py --help`  if you have run [local_venv.sh](../../tests/local_venv.sh).\n\n### Examples\n\nThese examples all the `task` invocation method but work with any of the methods above.\n\n```bash\n# Run type checking on all files\ntask rp:type-check -- check\n\n# Check for files ready to be promoted to stricter levels\ntask rp:type-check -- promotion-check\n\n# Find low-hanging fruit - files with fewest errors at each target level\ntask rp:type-check -- fruit\n\n# Show top 25 fruit candidates\ntask rp:type-check -- fruit --top 25\n\n# Show detailed error information with verbose output\ntask rp:type-check -- fruit --verbose 2\n\n# Automatically promote eligible files\ntask rp:type-check -- promotion-check --update\n\n# Run CI checks (both check and promotion-check)\ntask rp:type-check -- ci\n\n# Check specific files (paths/globs are positional, relative to tests/)\ntask rp:type-check -- check rptest/tests/my_test.py\n\n# Force all files to be checked at a specific level\ntask rp:type-check -- check --force-level strict\n```\n\n## Strictness Level Management\n\nIn order to have type checking useful immediately, without needing to fix 1000s of existing type errors, the system uses a progressive approach to type checking adoption:\n\n1. **New files** default to `strict` type checking\n2. **Existing files** are assigned to the strictest level they currently pass\n3. **Promotion detection** automatically identifies when files can move to stricter levels\n4. **Automated updates** can promote files with `--update` flag\n\n### Adding Type Hints to Files\n\nWhen adding type hints to improve a file:\n\n1. Run `tools/type-checking/type-check.py promotion-check --update`\n2. This will automatically move the file to the strictest level it now passes\n3. Commit both your type hint changes and the updated `type-check-strictness.json`\n\n### Finding Low-Hanging Fruit\n\nThe `fruit` command helps identify files that are closest to being promotable to stricter type checking levels by showing files with the fewest errors at each target level:\n\n```bash\n# Show top 10 files with fewest errors per target level\ntask rp:type-check -- fruit\n\n# Show top 20 files with fewest errors per target level\ntask rp:type-check -- fruit --top 20\n\n# Show detailed error information with verbose output\ntask rp:type-check -- fruit --verbose 2\n```\n\nThis is useful for:\n- **Prioritizing type checking improvements** - start with files that need minimal fixes\n- **Setting realistic goals** - see how many errors need to be fixed for each promotion\n- **Understanding error distribution** - identify patterns in type checking issues\n\nThe command groups files by their potential promotion target (off→basic, basic→standard, etc.) and sorts them by error count, making it easy to find files that are closest to passing at the next strictness level.\n\n### Failed Promotion Check\n\nYou may be here because CI failed with a \"promotion check\" error. This means that the strictness level for some files are set too low, i.e., your change has improved the state of the Python code base and as a reward you can promote some files to higher strictness levels. Pat yourself on the back, then use the following command to automatically update the strictness file:\n\n```bash\n# Automatically update the configuration\ntask rp:type-check -- promotion-check --update\n\n# Commit the updated type-check-strictness.json\ngit add tools/type-checking/type-check-strictness.json\ngit commit -m \"chore: update type checking strictness\"\n```\n\n## Command Line Options\n\n- `--tests-root PATH` - Path to tests directory (auto-detected by default)\n- `--config PATH` - Path to pyrightconfig.json\n- `input_files` - Positional paths/globs of files to check, relative to `tests/` (default: `rptest/**/*.py`)\n- `--force-level LEVEL` - Override strictness levels and check all files at specified level\n- `--no-venv` - Don't use virtual environment when running pyright\n- `--verbose [LEVEL]` - Enable verbose output (0=off, 1=verbose, 2=very verbose)\n- `--update` - Update type-check-strictness.json in-place (for promotion-check)\n- `--top N` - Number of files to show per target level in fruit command (default: 10)\n\n## CI Integration\n\nThe type checking runs automatically on pull requests via GitHub Actions (`.github/workflows/python-type-check.yml`). The CI:\n\n1. Builds the Docker image from `Dockerfile`\n2. Runs `type-check.py ci` which executes both type checking and promotion detection\n3. Fails if there are type errors or files that should be promoted\n\nThis check tries to be as fast as possible, and usually runs in about 3 minutes if the docker image build is fully cached (usually the case), or a few minutes more if not.\n\n## Architecture\n\nThe system is designed around Microsoft's pyright type checker with these key features:\n\n- **Segmented checking**: Files are grouped by strictness level and checked separately\n- **Temporary configs**: Generates temporary pyrightconfig.json files for each strictness level\n- **Batch processing**: Efficiently checks multiple files at once when possible\n- **Virtual environment integration**: Uses `.venv` if available for consistent Python environment\n\n## Adding New Features\n\nThe `type-check.py` script is modular and can be extended with new commands by:\n\n1. Adding the command name to the `CMDS` list\n2. Adding documentation to `COMMAND_DOC`\n3. Implementing a method on the `TypeCheck` class with the command name (with hyphens converted to underscores)\n\nThe script follows the pattern where each command method returns `True` for success, `False` for failure, or `None` for success (which is treated as `True`).\n"},"items":[{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/redpanda-data/redpanda/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# Copilot Coding Agent Onboarding Guide for `redpanda-data/redpanda`\n\n## High-Level Overview\n\n**What is Redpanda?**\n\nRedpanda is a high-performance, Apache Kafka®-compatible streaming data platform. It is written primarily in C++ for the core, with Go for CLI tooling (`rpk`), and some Python for auxiliary scripts and tests. Redpanda is designed to be lightweight, fast, and simple to operate, omitting ZooKeeper and the JVM.\n\nIt uses extensively the thread-per-core model and asynchronous (coroutines, futures) programming model.\n\n**Repository Characteristics:**\n- **Large multi-language codebase:** C++ (core), Go (CLI/tools), Python (testing/scripts), Bash and Bazel for builds.\n- **Build System:** Bazel (with Bazelisk) for core.\n- **Target Platforms:** Linux (primary), some support for macOS and Windows.\n- **Key Directories:**\n  - `src/v/`: Core C++ source code\n  - `src/go/`: Go CLI and tools\n  - `bazel/`, `BUILD`, `MODULE.bazel`: Bazel scripts and definitions\n  - `tools/`: Development and helper scripts\n  - `tests/`: Test suites\n  - `conf/`: Configuration files\n  - `proto/`: Protobuf definitions for Redpanda services and APIs\n  - `.github/`, `.buildkite/`: CI/workflow automation\n- **Documentation:** [Docs site](https://redpanda.com/documentation) and `docs/`.\n\n## Build, Test, and Validation Instructions\n\n### Prerequisites (Always Perform)\n\n1. **Install Bazelisk** (the required Bazel wrapper):\n   ```bash\n   wget -O ~/bin/bazel https://github.com/bazelbuild/bazelisk/releases/latest/download/bazelisk-linux-amd64\n   chmod +x ~/bin/bazel\n   export PATH=\"$HOME/bin:$PATH\"\n   ```\n2. **Install system dependencies**:\n   ```bash\n   sudo ./bazel/install-deps.sh\n   ```\n   _This must always be run before any Bazel build, especially on a fresh system or after dependency updates._\n\n### Core Build Steps\n\n- **Build all (fastbuild):**\n  ```bash\n  bazel build //...\n  ```\n- **Test all:**\n  ```bash\n  bazel test //...\n  ```\n- **Lint (C++):**\n  - Formatting and linting are enforced. Use:\n    ```bash\n    bazel run //tools:clang_format\n    ```\n  - Configs: `.clang-format`, `.clang-tidy`, etc.\n\n- **Go CLI (`rpk`) Build:**\n  ```bash\n  bazel build //:rpk\n  ```\n\nSee `.bazelrc` for more details on build settings and config modes\n\n### Validation and CI\n\n- **Pre-push/merge:** All changes are validated by CI (GitHub Actions, Buildkite) for build, test, and lint.\n- **Formatting and lint checks are enforced; run locally before PRs.**\n- **Target branch:** Always open PRs against `dev`.\n\n## Project Layout & Architectural Notes\n\n- **Main C++ source:** `src/v/`\n- **Go CLI:** `src/go/rpk/`\n- **Build configuration:** `.bazelrc`, `.bazelversion`, `BUILD`, `MODULE.bazel`, and `bazel/`\n- **CI configuration:** `.github/workflows/`, `.buildkite/`\n- **Testing:** `tests/`\n- **Config:** `conf/`\n- **Docker:** `tools/docker/`\n\n### Lint/Formatting Configs:\n- `.clang-format`, `.clang-tidy*`: C++ style\n- `tools/ruff`, `.ruff.toml`: Python formatting\n\n### CI/CD Checks\n\n- **Build, test, and lint are enforced by CI.** Use the same steps as above locally before PRs.\n\n### File Index (Root Level)\n- `.bazelignore`, `.bazelrc`, `.bazelversion`, `BUILD`, `MODULE.bazel`, `README.md`, `CONTRIBUTING.md`, `SECURITY.md`, `CODE_OF_CONDUCT.md`, `LICENSES/`, `bazel/`, `src/`, `tests/`, `conf/`, `tools/`, `.github/`, `.buildkite/`, etc.\n\n---\n\n## Protobuf-Specific Instructions\n\n### Protobuf Coding Guidelines\n\nFollow the guidelines provided in `proto/redpanda/README.md` for basic Protobuf standards. For admin v2 ConnectRPC endpoints (proto → C++ service impl → registration → ducktape Python bindings), use the `add-admin-v2-endpoint` skill.\n\n### Protobuf Build & Environment\n- **Primary Protobuf code lives in `proto/`.**\n- **Formatting:** `.clang-format` in the root directory is enforced. Always run `clang-format` before committing changes or submitting a PR:\n  ```bash\n  bazel run //tools:clang_format\n  ```\n\n## C++-Specific Instructions\n\n### C++ Build & Environment\n\n- **Primary C++ code lives in `src/v/`.**\n- **C++ build is managed by Bazel.** All dependencies and toolchains are configured via Bazel rules and the `MODULE.bazel` file. Do not manually install C++ dependencies unless explicitly instructed in documentation.\n- **Compiler Standard:** C++23 is required. Some SDK components (e.g., `src/transform-sdk/cpp/`) use C++23 and specific flags like `-Wall`, `-fno-exceptions`, and for some targets, `-stdlib=libc++`.\n- **Sanitizers:** Some components and test builds use sanitizers (address, leak, undefined) via `-fsanitize=address,leak,undefined` for both compile and link.\n- **Suppression Files:** Leak, undefined, and other sanitizer suppressions can be found in the root as `lsan_suppressions.txt`, `ubsan_suppressions.txt`.\n- **C++ Linting:** `.clang-format` and `.clang-tidy` in the root directory are enforced. Always run `clang-format` before committing changes or submitting a PR:\n  ```bash\n  bazel run //tools:clang_format\n  ```\n- **C++ Libraries:** Bazel dependencies are managed in `MODULE.bazel` (e.g., Boost, Abseil, fmt, protobuf, googletest, yaml-cpp, etc.).\n- **Testing:** C++ unit tests are run via Bazel.\n\n### Common C++ Pitfalls & Workarounds\n\n- **Always use Bazelisk and Bazel for building the core.** Using a plain Bazel binary may result in missing dependencies or incompatible flags.\n- **If you encounter build issues related to missing system libraries, rerun `sudo ./bazel/install-deps.sh`.**\n- **Do not attempt to manually install or update C++ dependencies unless specifically instructed.**\n- **Always run lint and formatter before pushing. CI will fail on formatting/lint discrepancies.**\n- **If building in CI or a containerized environment, ensure the correct toolchain is available as specified in `tools/docker/README.md` or CI scripts.**\n- **Check for additional build and compile flags in `BUILD`, `MODULE.bazel` and related files.**\n\n### C++ coding guidelines\n\nCheck that these guidelines are followed for new code.\n\n- Write [scalable code](docs/writing-scalable-and-high-performance-code.md).\n- Do not declare new `operator<<(ostream& os, type)` overloads, instead prefer to use a `format_to` member function inside `type` as described in\nsrc/v/base/format_to.h.\n- Prefer using latest C++ features (C++23).\n- Use `ss` namespace as a prefix for Seastar types (e.g. `ss::future`, `ss::promise`).\n- Use `vassert(cond, msg, msg_args...)` macro for assertions. It is\n  similar to `assert(cond)` but it is always enabled and it prints the message\n  to the log. Use `dassert` for assertions that are only enabled in debug mode.\n- Use `vlog(method, fmt, args...)` for logging. `method` is the method reference\n  for the logger to use. I.e. `vlog(stlog.info, \"Hello world\");`. Where `stlog`\n  is defined as `ss::logger stlog(\"storage\");`.\n- Do not use `std::vector` for containers that may grow very large, instead use `chunked_vector`.\n- Do not use `std::unordered_map` for containers that may grow very large, instead use `chunked_hash_map`.\n- Instead of long if-else chains for mapping string to values, use\n  `string_switch` mechanism defined in\n  [string_switch.h](./src/v/strings/string_switch.h).\n- Don't use `ss::parallel_for_each` with ranges that can grow large such as\n  partitions, topics or segments. Instead prefer `ss::max_concurrent_for_each` to\n  limit concurrency. Think about how much concurrency is needed to adequately hide\n  latency. See our\n  [docs](https://redpandadata.atlassian.net/wiki/x/AQBZTw#Managing-Concurrency-in-the-system)\n  for more background.\n- Do not call the `get_exception` method on a future within a logging or\n  assertion statement (e.g. `vlog(..., fut.get_exception(), ...)`). Instead,\n  assign the return value of `get_exception` in a variable and pass the variable.\n\n### Coroutines\n\nFollow [coroutines best practices](docs/writing-high-performance-coroutine-code.md) when using\ncoroutines.\n\n#### Lambda coroutines, coroutine argument capture, and deducing this\n\nWhen a lambda coroutine is passed to APIs like `seastar::future::then()`,\nthe lambda object is stored in managed memory that gets freed once the\ncontinuation returns—but the coroutine may still be suspended and later\naccess its captures, causing use-after-free. This happens because the\ncoroutine frame holds a reference to the lambda's capture storage, which\nbecomes dangling. The C++23 \"deducing this\" syntax\n`([captures...](this auto, args...))` solves this by moving the captures\ndirectly into the coroutine frame rather than referencing them through the\nlambda object, decoupling capture lifetime from the lambda's lifetime.\nThis is distinct from the recursive-lambda use case—here this auto is required\nfor memory safety in coroutines, not self-reference.\n\n### Exception handling\n\n- Read the \"Exceptions in coroutines\" section within [tutorial.md](external/+non_module_dependencies+seastar/doc/tutorial.md)\n  to best understand how exceptions should be handled in Redpanda.\n- `throw` and `std::rethrow_exception()` may have an effect on performance, especially in hot paths.\n  There are several `seastar` utilities that should be preferred to explicitly throwing exceptions.\n  Namely, `ss::coroutine::try_future()`, `ss::coroutine::as_future()`, `ss::coroutine::return_exception()`,\n  and `ss::coroutine::return_exception_ptr()`. This is only a brief summary of the content in the tutorial above,\n  which must be read to fully understand exception handling in Redpanda.\n\n### C++ coding style\n\n- Use snake_case for identifiers. Use CamelCase for concepts.\n- Use Doxygen comments with 3-slashes (///) for public APIs\n\n### Code comments\n\n- **Default to no comments** - clear code and good names are better\n- **Avoid comments that restate the code** - they become stale\n- **Prefer alternatives:**\n  - Better variable/function names\n  - Log lines (serve as documentation and debugging)\n- **Do add comments for:**\n  - Doc comments (`/// \\brief`) on public types explaining purpose/usage\n  - Complex algorithms or non-obvious \"gotchas\"\n  - Test comments explaining input format or test intent\n  - Links to external resources (specs, docs, issues)\n  - Mapping internal types/concepts to external formats (e.g., wire protocols, APIs)\n  - ASCII diagrams for complex state machines or data flows\n- **Avoid obvious branching comments** - `if (x)` rarely needs `// when x is true`\n\n### Benchmarking\n\n  - Run benchmarks like `bazel run --config=release //src/v/utils/tests:coro_rpbench`\n  - Get --help like: `bazel run --config=release //src/v/utils/tests:coro_rpbench -- --help`\n  - If running/writing benchmarks read this also: external/+non_module_dependencies+seastar/tests/perf/perf-tests.md\n\n### More C++-Specific References\n\n- [MODULE.bazel](https://github.com/redpanda-data/redpanda/blob/dev/MODULE.bazel)\n- [BUILD](https://github.com/redpanda-data/redpanda/blob/dev/BUILD)\n\n---\n\n## Python specific instructions\n\n### Instructions for python test code under tests/rptest\n\n- Avoid catching bare `except:` as this can hide system exceptions, including exceptions\n  raised by a signal when a test is being forcibly timed out. Instead use `except Exception:`.\n- After modifying files under `tests/rptest`, run type checking via\n  `tools/type-checking/type-check.sh check <files...>` (paths positional, relative to `tests/`)\n  to scope it to your changes. Do not invoke `type-check.py` directly.\n  See `tools/type-checking/README.md` for more details: the per-file\n  strictness levels in `type-check-strictness.json` and how to promote\n  files to stricter levels after adding type hints.\n\n### Instructions for type hints\n\n- Use modern style with `|` instead of `Union` or `Optional`\n\nFor further details, consult:\n- [README.md](https://github.com/redpanda-data/redpanda/blob/dev/README.md)\n- [CONTRIBUTING.md](https://github.com/redpanda-data/redpanda/blob/dev/CONTRIBUTING.md)\n- [Redpanda Documentation](https://redpanda.com/documentation)\n- CI/CD configs in `.github/workflows/` and `.buildkite/`\n\n---\n\n## Commit message instructions\n\nWhen writing or critiquing commit messages, follow these guidelines:\n\n**Format:**\n```\narea[/detail]: short description\n\n<optional body>\n```\n\n- Title: ≤72 chars\n- Body: wrapped at 72 chars\n\n**Check git history first** to match the style of the area you're changing:\n```bash\ngit log --oneline --no-merges -- path/to/changed/files | head -20\n```\n\n**Goal:** Make the commit easy to review and provide context for future readers (via `git blame`/`git log`)\n\n**Rules:**\n- Title: imperative mood, lowercase after colon, no period\n- Keep bodies concise (1-2 lines typical, trivial changes need only a title)\n- Don't reference GitHub issues/PRs or Jira tickets\n- Use the body to help reviewers and future readers understand:\n  - The \"why\": motivation, design choices, preparation for future work\n  - The \"what\": new abstractions introduced, non-obvious changes\n  - Integration tests: briefly note what behaviors are covered\n- Don't restate what's obvious from the diff, but duplicating a doc comment is fine if it helps reviewers understand the change faster.\n\n_Results from code search may be incomplete. For more C++ details, see the [repository code search](https://github.com/redpanda-data/redpanda/search?q=c%2B%2B)._\n","isInternal":false,"tokens":3303,"sizeBytes":13198},{"name":"copilot-instructions.md","path":".github/copilot-instructions.md","rawUrl":"https://raw.githubusercontent.com/redpanda-data/redpanda/HEAD/.github/copilot-instructions.md","title":"GitHub Copilot Instructions","category":"copilot-instructions","format":"markdown","content":"CLAUDE.md","isInternal":false,"tokens":4,"sizeBytes":9},{"name":"SKILL.md","path":".claude/skills/add-admin-v2-endpoint/SKILL.md","rawUrl":"https://raw.githubusercontent.com/redpanda-data/redpanda/HEAD/.claude/skills/add-admin-v2-endpoint/SKILL.md","title":"add-admin-v2-endpoint","category":"anthropic-skill","format":"markdown","content":"---\nname: add-admin-v2-endpoint\ndescription: Use when adding or modifying a Redpanda admin v2 ConnectRPC endpoint — authoring the protobuf under proto/redpanda/core/admin/v2/, wiring it into the Bazel build, implementing the C++ service handler under src/v/redpanda/admin/services/, registering it on the admin server, and regenerating Python bindings for ducktape integration tests. Covers both new RPCs on an existing service and brand-new services.\n---\n\n# Add an Admin v2 ConnectRPC Endpoint\n\nThis skill walks through every layer touched by a new admin v2 RPC: the `.proto`, the Bazel build, the C++ service implementation, registration on the admin server, and the Python bindings consumed by ducktape tests.\n\nFor generic protobuf authoring conventions (field options, message design, well-known types), the canonical reference is `proto/redpanda/README.md`. This skill assumes you've read that and focuses on what's specific to admin v2.\n\n## Reference example\n\nCommit `04d3160c3a` (`cluster: add manual upgrade finalization with admin v2 RPC`) is the cleanest minimal example — a new service with a single RPC. Skim these when unsure:\n\n- `proto/redpanda/core/admin/v2/features.proto` — the proto\n- `proto/redpanda/core/admin/v2/BUILD` — the BUILD entries (look at the `features_proto` / `features_redpanda_proto` pair)\n- `src/v/redpanda/admin/services/features.h` and `features.cc` — the C++ impl\n- `src/v/redpanda/admin/services/BUILD` — the `features` cc_library target\n- `src/v/redpanda/application_admin.cc` — the `add_service` registration\n\n`git show 04d3160c3a` will print the full diff.\n\n## Procedure\n\n### 1. Author the protobuf\n\nCreate or extend a `.proto` under `proto/redpanda/core/admin/v2/`. One file per service domain; the filename matches the dominant service (`features.proto` → `FeaturesService`).\n\nRequired header for a new file:\n\n```proto\n// (BSL copyright header — copy from features.proto:1-13)\nsyntax = \"proto3\";\n\npackage redpanda.core.admin.v2;\n\nimport \"proto/redpanda/core/pbgen/options.proto\";\nimport \"proto/redpanda/core/pbgen/rpc.proto\";\n\noption (pbgen.cpp_namespace) = \"proto::admin\";\n```\n\nNotes:\n\n- `package` is always `redpanda.core.admin.v2` for admin v2.\n- `option (pbgen.cpp_namespace) = \"proto::admin\";` is mandatory and always exactly this value for admin v2 — the short form (`pbgen.cpp_namespace`, not the FQN `redpanda.core.pbgen.cpp_namespace`) is what's used in the tree.\n- Import `rpc.proto` only when the file defines a `service`.\n\nService / RPC block:\n\n```proto\nservice MyService {\n    // One short paragraph explaining what the RPC does and any\n    // async/poll behavior the caller needs to know.\n    rpc MyMethod(MyMethodRequest) returns (MyMethodResponse) {\n        option (pbgen.rpc) = {\n            authz: SUPERUSER\n        };\n    }\n}\n\nmessage MyMethodRequest {}\nmessage MyMethodResponse {}\n```\n\n- Every RPC must carry `option (pbgen.rpc) = { authz: ... }`. Default to `SUPERUSER` for admin v2 — anything else (`USER`, `PUBLIC`) needs core-team approval (see `proto/redpanda/core/pbgen/rpc.proto`).\n- Optional `http_route: \"/v2/...\"` adds an alias HTTP path. The auto-generated `/<package>.<Service>/<Method>` route always works without it.\n- Naming: messages are `<RpcName>Request` / `<RpcName>Response`, one pair per RPC. Empty messages are fine.\n\nField-level options worth knowing (full reference in `proto/redpanda/core/pbgen/options.proto`):\n\n- `[(pbgen.iobuf) = true]` on `string` fields that may exceed 128 KiB — generates `iobuf` instead of `ss::sstring`. `bytes` is already iobuf-backed.\n- `[(pbgen.ptr) = true]` on message fields for indirection (recursive types or when you need to distinguish unset).\n- `[debug_redact = true]` on sensitive fields — suppresses them from `fmt::formatter` output (passwords, tokens, etc.).\n\nWell-known types (`google.protobuf.Duration`, `Timestamp`, `FieldMask`) are supported; importing them requires the corresponding BUILD deps in step 2.\n\n### 2. Wire up the Bazel build\n\nEach `.proto` needs **two stacked targets** in `proto/redpanda/core/admin/v2/BUILD`. Append (alphabetical order in the file):\n\n```python\nproto_library(\n    name = \"myservice_proto\",\n    srcs = [\"myservice.proto\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//proto/redpanda/core/pbgen:options_proto\",\n        \"//proto/redpanda/core/pbgen:rpc_proto\",\n        # Add per-import:\n        # \"@protobuf//:timestamp_proto\",\n        # \"@protobuf//:duration_proto\",\n        # \"@protobuf//:field_mask_proto\",\n        # \"@googleapis//google/api:field_info_proto\",\n        # \"@googleapis//google/api:field_behavior_proto\",\n        # \"@googleapis//google/api:resource_proto\",\n        # Cross-proto imports: e.g. \":kafka_connections_proto\"\n    ],\n)\n\nredpanda_proto_library(\n    name = \"myservice_redpanda_proto\",\n    protos = [\":myservice_proto\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        # Add when the proto uses Timestamp or Duration:\n        # \"@abseil-cpp//absl/time:time\",\n        # Add when the proto uses FieldMask:\n        # \"//src/v/serde/protobuf:field_mask\",\n        # For cross-proto deps the *_redpanda_proto sibling goes here:\n        # \":kafka_connections_redpanda_proto\"\n    ],\n)\n```\n\nRules:\n\n- `proto_library.deps` mirrors `import` statements in the `.proto` file.\n- `redpanda_proto_library.deps` carries C++ deps the *generated* code needs (e.g. abseil for timestamps), plus any sibling `_redpanda_proto` targets for cross-proto imports.\n- The pbgen plugin lives at `bazel/pbgen/`; it generates `<name>.proto.h` and `<name>.proto.cc` with the message types, formatters, and service stubs.\n\n### 3. Implement the C++ service\n\nIf extending an existing service (just adding a new RPC), open the matching `src/v/redpanda/admin/services/<name>.{h,cc}` and add the new handler. Skip to step 4 after.\n\nFor a brand-new service, create `src/v/redpanda/admin/services/<name>.h`:\n\n```cpp\n/*\n * Copyright 2026 Redpanda Data, Inc.\n * (BSL header — copy from features.h:1-10)\n */\n#pragma once\n\n#include \"cluster/fwd.h\"\n#include \"proto/redpanda/core/admin/v2/<name>.proto.h\"\n#include \"redpanda/admin/proxy/client.h\"\n\nnamespace admin {\n\nclass <name>_service_impl : public proto::admin::<name>_service {\npublic:\n    <name>_service_impl(\n      admin::proxy::client,\n      /* collaborators — e.g. cluster::controller*, ss::sharded<X>& */);\n\n    ss::future<proto::admin::<rpc>_response> <rpc>(\n      serde::pb::rpc::context, proto::admin::<rpc>_request) override;\n\nprivate:\n    admin::proxy::client _proxy_client;\n    /* members for collaborators */\n};\n\n} // namespace admin\n```\n\nNaming conventions (CamelCase → snake_case translation):\n\n- Service base class: `proto::admin::<service>_service` (e.g. `FeaturesService` → `features_service`)\n- Generated client (for inter-node proxying): `proto::admin::<service>_service_client`\n- Request / response types: `proto::admin::<rpc_name>_request` / `_response`\n- Handler method name: snake_case of the RPC name (e.g. `FinalizeUpgrade` → `finalize_upgrade`)\n- Implementation class: `admin::<service>_service_impl`, in namespace `admin` (NOT `proto::admin`)\n\nHandler signature is fixed: `ss::future<...response>` return type, `serde::pb::rpc::context` first arg, request second, `override`.\n\nThen `src/v/redpanda/admin/services/<name>.cc`:\n\n```cpp\n#include \"redpanda/admin/services/<name>.h\"\n#include \"redpanda/admin/services/utils.h\"\n#include \"serde/protobuf/rpc.h\"\n#include <seastar/core/coroutine.hh>\n\nnamespace admin {\nnamespace {\nss::logger <name>log{\"admin_api_server/<name>_service\"};\n} // namespace\n\n<name>_service_impl::<name>_service_impl(/* args */) : _proxy_client(...) {}\n\nss::future<proto::admin::<rpc>_response>\n<name>_service_impl::<rpc>(\n  serde::pb::rpc::context ctx, proto::admin::<rpc>_request req) {\n    vlog(<name>log.trace, \"<rpc>: {}\", req);\n    // ... validate, optionally redirect-to-leader, do work ...\n    co_return proto::admin::<rpc>_response{};\n}\n\n} // namespace admin\n```\n\nPatterns to know — read the existing service files for live examples:\n\n- **Validation failures throw typed exceptions** declared in `src/v/serde/protobuf/rpc.h:241-287`: `invalid_argument_exception`, `failed_precondition_exception`, `not_found_exception`, `already_exists_exception`, `permission_denied_exception`, `unavailable_exception`, `unauthenticated_exception`, etc. Each maps to a ConnectRPC/gRPC status code. Example: `features.cc:46`.\n- **Leader redirect**: `admin::utils::redirect_to_leader(...)` from `services/utils.h` returns an optional `node_id`; if set, proxy the call to that node using the generated `_service_client`. Pattern in `features.cc:51-64`.\n- **Proxying to peers**: `_proxy_client.make_client_for_node<proto::admin::<service>_service_client>(node)` for one target, or `.make_clients_for_other_nodes<...>()` for scatter-gather. Defined in `redpanda/admin/proxy/client.h`.\n- **License gating**: `admin::utils::check_license(_feature_table.local())` if the RPC is enterprise-gated. See `cluster.cc:51`.\n- **Avoid proxy loops** in scatter-gather handlers: gate with `proxy::is_proxied(ctx)` (example in `cluster.cc:56`).\n\nAdd the BUILD entry to `src/v/redpanda/admin/services/BUILD`:\n\n```python\nredpanda_cc_library(\n    name = \"<name>\",\n    srcs = [\"<name>.cc\"],\n    hdrs = [\"<name>.h\"],\n    implementation_deps = [\n        \":utils\",\n        # .cc-only collaborators\n    ],\n    deps = [\n        \"//proto/redpanda/core/admin/v2:<name>_redpanda_proto\",\n        \"//src/v/cluster:fwd\",\n        \"//src/v/redpanda/admin/proxy:client\",\n        \"//src/v/serde/protobuf:rpc\",\n        \"@seastar\",\n        # types that appear in <name>.h\n    ],\n)\n```\n\nRule: types that appear in the `.h` belong in `deps`; collaborators only used in the `.cc` belong in `implementation_deps`.\n\n### 4. Register the service\n\nTwo edits in `src/v/redpanda/`:\n\n**4a. `src/v/redpanda/BUILD`** — add the new services library to the top-level `redpanda` library's `deps` list (kept sorted):\n\n```python\n\"//src/v/redpanda/admin/services:<name>\",\n```\n\n**4b. `src/v/redpanda/application_admin.cc`** — two changes:\n\n1. Include the header (alphabetical with the other service includes):\n   ```cpp\n   #include \"redpanda/admin/services/<name>.h\"\n   ```\n2. Inside `_admin.invoke_on_all([this, node_id](admin_server& s) { ... })`, append an `add_service` call. `create_client()` is the local lambda defined at the top of the block; pass other collaborators via `controller.get()` or `std::ref(...)`:\n   ```cpp\n   s.add_service(\n     std::make_unique<admin::<name>_service_impl>(\n       create_client(), /* collaborators */));\n   ```\n\nThat's it for registration. `add_service` walks the service's routes and registers an HTTP POST handler per route at `route.path` with the auth wrapper keyed on `route.authz_level`.\n\n### 5. Build and format\n\n```bash\nbazel build //proto/redpanda/core/admin/v2:<name>_redpanda_proto\nbazel build //src/v/redpanda/admin/services:<name>\nbazel build //src/v/redpanda:application\nbazel run //tools:clang_format\n```\n\nFix compilation errors before moving on. The pbgen plugin's error messages are usually self-explanatory; common causes are missing BUILD deps from step 2 or imports in the `.proto` without matching `proto_library.deps`.\n\n### 6. Regenerate ducktape Python bindings\n\nDucktape tests consume admin v2 via committed-to-repo Python bindings under `tests/rptest/clients/admin/proto/`. They are regenerated by a script — CI (`.github/workflows/check-ducktape-protos.yml`) fails the PR if they're stale, so this step is mandatory whenever a `.proto` under `proto/redpanda/core/admin/**` changes.\n\nPrerequisite: `uv` installed (https://docs.astral.sh/uv/getting-started/installation/). The script aborts otherwise.\n\n```bash\nbazel fetch //proto/...          # one-time; populates the googleapis external dir\ntools/regenerate_ducktape_protos.sh\n```\n\nThe script wipes `tests/rptest/clients/admin/proto/**/*.py{,i}` and regenerates from the proto glob using `grpc_tools.protoc` + `mypy-protobuf` + `connect-python`, then runs `protoletariat` to rewrite imports to relative form. The glob picks up new `.proto` files automatically — **no manifest to update**.\n\nCommit the generated files. `.gitignore` does not exclude them.\n\n### 7. Add a ducktape client accessor (only for new services)\n\nIf you created a brand-new service, expose it from the `Admin` wrapper at `tests/rptest/clients/admin/v2.py`. Two edits:\n\n1. Add imports near the top (line ~9), alongside the existing service imports:\n   ```python\n   from rptest.clients.admin.proto.redpanda.core.admin.v2 import (\n       <name>_pb2,\n       <name>_pb2_connect,\n       # ... existing\n   )\n   ```\n   And add a convenience re-export (`<name>_pb = <name>_pb2`) in the re-export block (around line ~52).\n\n2. Add an accessor method on `Admin` matching the existing pattern (line ~133):\n   ```python\n   def <name>(self, **kwargs: Any) -> <name>_pb2_connect.<Name>ServiceClient:\n       return self._make_service(<name>_pb2_connect.<Name>ServiceClient, **kwargs)\n   ```\n\nExisting services (`broker`, `cluster`, `features`, `security`, `shadow_link`, `datalake`, `debug`, `breakglass`, `metastore`, `l0`, `internal_shadow_link`) show the shape. If you only added a new RPC to an existing service, no edit to `v2.py` is needed — `_make_service` returns the generated client which exposes every RPC as a method.\n\nA ducktape test uses the new RPC like this:\n\n```python\nfrom rptest.clients.admin.v2 import Admin as AdminV2, <name>_pb\n\nadmin_v2 = AdminV2(self.redpanda,\n                   auth=(self.superuser.username, self.superuser.password))\nreq = <name>_pb.<Rpc>Request(...)\nresp = admin_v2.<name>().<rpc>(req)\n```\n\n## Verification\n\nAfter all edits:\n\n```bash\nbazel build //...\nbazel run //tools:clang_format\ntools/regenerate_ducktape_protos.sh   # idempotent — confirms generated tree matches\ngit status                            # should show all expected files staged\n```\n\nManual smoke (optional, but the canonical way to confirm wiring):\n\n1. `bazel build //src/v/redpanda:application` or a debug-binary target.\n2. Start a local cluster; invoke the RPC via the ducktape `AdminV2` client, or `curl` against `/redpanda.core.admin.v2.<Service>/<Method>` with a protobuf-encoded body.\n\n## Out of scope\n\n**Go bindings for `rpk` consumption.** Admin v2 Go bindings are not generated in this repo — they're pulled from the Buf Schema Registry as a Go module (`buf.build/gen/go/redpandadata/core/...`). CI publishes to BSR automatically on merge to `dev` or a `v*` branch (see `.github/workflows/buf.yml`). Consuming a new RPC from `rpk` is a follow-up PR after the proto change has merged and the BSR push has run — out of scope for this skill.\n\nIf the user mentions needing `rpk` to consume the new RPC, surface this as a separate follow-up task rather than attempting it in the same change.\n\n## Rules and pitfalls\n\n- **Auth level defaults to `SUPERUSER`.** Don't use `USER` or `PUBLIC` without explicit approval — the proto README is clear on this.\n- **The proto's `option (pbgen.cpp_namespace)` is always `\"proto::admin\"` for admin v2.** Don't invent a per-service namespace.\n- **`add_service` registration order matters only for readability.** The existing block in `application_admin.cc` is not strictly sorted; match its grouping (regular services, then `if (cloud_topics_app)` services, then internal/security at the end).\n- **`bazel fetch //proto/...` is a prerequisite for `regenerate_ducktape_protos.sh`** — the script needs the googleapis external dir to exist. The script's error message is clear, but the failure mode (script aborts with \"googleapis directory not found\") is easy to misdiagnose if you skip it.\n- **CI enforces generated-file freshness.** `check-ducktape-protos` will fail if you forget step 6. The fix is always: re-run the script and commit the diff.\n- **Don't add comments that restate the code.** RPC docstrings in the `.proto` are valuable (they become the public contract); duplicating them in the C++ handler isn't.\n- **The handler signature `ss::future<R> name(serde::pb::rpc::context, Request)` is fixed** — context first, request second, both by value, return future. Deviating produces inscrutable pbgen-template errors.\n- **Validation errors must throw the typed exceptions from `src/v/serde/protobuf/rpc.h`.** Returning a default response, returning an `errc`, or throwing `std::runtime_error` will not surface a proper ConnectRPC status to the caller.\n","frontmatter":{"name":"add-admin-v2-endpoint","description":"Use when adding or modifying a Redpanda admin v2 ConnectRPC endpoint — authoring the protobuf under proto/redpanda/core/admin/v2/, wiring it into the Bazel build, implementing the C++ service handler under src/v/redpanda/admin/services/, registering it on the admin server, and regenerating Python bindings for ducktape integration tests. Covers both new RPCs on an existing service and brand-new services."},"isInternal":false,"tokens":4146,"sizeBytes":16448},{"name":"evals.json","path":".claude/skills/create-backport-branch/evals/evals.json","rawUrl":"https://raw.githubusercontent.com/redpanda-data/redpanda/HEAD/.claude/skills/create-backport-branch/evals/evals.json","title":"Evals Skill","category":"anthropic-skill","format":"json","content":"{\n  \"skill_name\": \"create-backport-branch\",\n  \"evals\": [\n    {\n      \"id\": 0,\n      \"name\": \"clean-cherry-pick\",\n      \"prompt\": \"/create-backport-branch 81d9e1af33 30160\",\n      \"expected_output\": \"Creates a backport branch from commit 81d9e1af33 (v25.3.x before PR 30160 was backported). The single commit from PR #30160 should cherry-pick cleanly with no conflicts and no empty result. Produces a structured report.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Branch was created with naming pattern ai-backport-pr-30160-81d9e1af33-{timestamp}\",\n        \"Cherry-pick command used the -x flag to record the source commit\",\n        \"Cherry-pick succeeded cleanly with no conflicts\",\n        \"Final report includes total commits count (1), conflicts resolved count (0), and commits skipped count (0)\",\n        \"Final report includes the backport branch name\",\n        \"The backport branch has exactly 1 new commit beyond the base\"\n      ]\n    },\n    {\n      \"id\": 1,\n      \"name\": \"multi-commit-conflict\",\n      \"prompt\": \"/create-backport-branch 5cdd00f4ca 30089\",\n      \"expected_output\": \"Creates a backport branch from commit 5cdd00f4ca (v26.1.x). Cherry-picks 8 commits from PR #30089. Should encounter conflict in feature_table.h and resolve it by adapting to the target branch's release_version values.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Branch was created with naming pattern ai-backport-pr-30089-5cdd00f4ca-{timestamp}\",\n        \"All 8 commits were fetched from the PR via gh api\",\n        \"Cherry-pick command used the -x flag\",\n        \"Either all conflicts were resolved and continued, OR agent aborted cleanly listing the specific commit SHA and conflicted files\",\n        \"If conflicts were resolved, the final report includes a per-conflict one-line explanation\",\n        \"No conflict markers (<<<<<<< or ======= or >>>>>>>) remain in any resolved files\"\n      ]\n    },\n    {\n      \"id\": 2,\n      \"name\": \"generated-files-modify-delete\",\n      \"prompt\": \"/create-backport-branch d0a6248434 30115\",\n      \"expected_output\": \"Creates a backport branch from commit d0a6248434 (v25.1.x). Cherry-picks 3 commits from PR #30115. Should encounter modify/delete conflict on buf.gen.yaml and abort cleanly per skill rules.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Branch was created with naming pattern ai-backport-pr-30115-d0a6248434-{timestamp}\",\n        \"Generated files (go.sum, MODULE.bazel, or similar) were identified\",\n        \"Cherry-pick command used the -x flag\",\n        \"Agent aborted cleanly on modify/delete conflict, listing the specific commit SHA and conflicted files\",\n        \"Abort report includes the exact cherry-pick command that was being attempted\",\n        \"Final report includes total commits count, conflicts resolved count, and commits skipped count\"\n      ]\n    },\n    {\n      \"id\": 3,\n      \"name\": \"already-present-empty\",\n      \"prompt\": \"/create-backport-branch ea6e2533c7 30160\",\n      \"expected_output\": \"Creates a backport branch from commit ea6e2533c7 (v25.3.x after PR 30160 was already backported). The cherry-pick should result in an empty commit since the change is already present. Should handle with git cherry-pick --skip and report as skipped.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Branch was created with naming pattern ai-backport-pr-30160-ea6e2533c7-{timestamp}\",\n        \"Cherry-pick command used the -x flag\",\n        \"Empty cherry-pick was handled with git cherry-pick --skip (not --abort)\",\n        \"Final report includes commits skipped count of 1\",\n        \"Final report correctly indicates the commit was already on the target branch\",\n        \"The backport branch was preserved (not deleted)\"\n      ]\n    }\n  ]\n}\n","frontmatter":{"skill_name":"create-backport-branch","evals":[{"id":0,"name":"clean-cherry-pick","prompt":"/create-backport-branch 81d9e1af33 30160","expected_output":"Creates a backport branch from commit 81d9e1af33 (v25.3.x before PR 30160 was backported). The single commit from PR #30160 should cherry-pick cleanly with no conflicts and no empty result. Produces a structured report.","files":[],"expectations":["Branch was created with naming pattern ai-backport-pr-30160-81d9e1af33-{timestamp}","Cherry-pick command used the -x flag to record the source commit","Cherry-pick succeeded cleanly with no conflicts","Final report includes total commits count (1), conflicts resolved count (0), and commits skipped count (0)","Final report includes the backport branch name","The backport branch has exactly 1 new commit beyond the base"]},{"id":1,"name":"multi-commit-conflict","prompt":"/create-backport-branch 5cdd00f4ca 30089","expected_output":"Creates a backport branch from commit 5cdd00f4ca (v26.1.x). Cherry-picks 8 commits from PR #30089. Should encounter conflict in feature_table.h and resolve it by adapting to the target branch's release_version values.","files":[],"expectations":["Branch was created with naming pattern ai-backport-pr-30089-5cdd00f4ca-{timestamp}","All 8 commits were fetched from the PR via gh api","Cherry-pick command used the -x flag","Either all conflicts were resolved and continued, OR agent aborted cleanly listing the specific commit SHA and conflicted files","If conflicts were resolved, the final report includes a per-conflict one-line explanation","No conflict markers (<<<<<<< or ======= or >>>>>>>) remain in any resolved files"]},{"id":2,"name":"generated-files-modify-delete","prompt":"/create-backport-branch d0a6248434 30115","expected_output":"Creates a backport branch from commit d0a6248434 (v25.1.x). Cherry-picks 3 commits from PR #30115. Should encounter modify/delete conflict on buf.gen.yaml and abort cleanly per skill rules.","files":[],"expectations":["Branch was created with naming pattern ai-backport-pr-30115-d0a6248434-{timestamp}","Generated files (go.sum, MODULE.bazel, or similar) were identified","Cherry-pick command used the -x flag","Agent aborted cleanly on modify/delete conflict, listing the specific commit SHA and conflicted files","Abort report includes the exact cherry-pick command that was being attempted","Final report includes total commits count, conflicts resolved count, and commits skipped count"]},{"id":3,"name":"already-present-empty","prompt":"/create-backport-branch ea6e2533c7 30160","expected_output":"Creates a backport branch from commit ea6e2533c7 (v25.3.x after PR 30160 was already backported). The cherry-pick should result in an empty commit since the change is already present. Should handle with git cherry-pick --skip and report as skipped.","files":[],"expectations":["Branch was created with naming pattern ai-backport-pr-30160-ea6e2533c7-{timestamp}","Cherry-pick command used the -x flag","Empty cherry-pick was handled with git cherry-pick --skip (not --abort)","Final report includes commits skipped count of 1","Final report correctly indicates the commit was already on the target branch","The backport branch was preserved (not deleted)"]}]},"isInternal":false,"tokens":919,"sizeBytes":3734},{"name":"SKILL.md","path":".claude/skills/create-backport-branch/SKILL.md","rawUrl":"https://raw.githubusercontent.com/redpanda-data/redpanda/HEAD/.claude/skills/create-backport-branch/SKILL.md","title":"create-backport-branch","category":"anthropic-skill","format":"markdown","content":"---\nname: create-backport-branch\ndescription: \"Create a new backport branch with prefix `ai-backport-` by cherry-picking all commits from a GitHub PR onto a target release branch or commit. Use when the user wants to backport a PR, create a backport branch, cherry-pick PR commits to a release branch, or mentions backporting changes from dev/main to a version branch (e.g. v25.1.x, v25.2.x). Requires two arguments: TARGET (the release branch or commit hash to backport onto) and PR_NUM (the GitHub PR number to backport).\"\nargument-hint: [target-branch-or-commit] [pr-number]\nallowed-tools: Bash(git *), Bash(gh *), Bash(date *), Bash(grep *), Bash(wc *), Read, Write, Edit, Glob, Grep, Agent\nmodel: opus[1m]\neffort: high\n---\n\n# Create Backport Branch\n\nCherry-pick all commits from a GitHub PR onto a target release branch, resolving conflicts along the way.\n\nArguments:\n- `$0` is target — a release branch (e.g. `v25.1.x`) or a commit hash (e.g. `81d9e1af33`)\n- `$1` is pr-number (e.g. `12345`)\n\n## Procedure\n\n### 1. Validate inputs and set up\n\n1. Both arguments must be provided. If either target-branch or pr-number is empty, abort with: \"Usage: /create-backport-branch <target-branch> <pr-number>\"\n2. Generate the backport-branch-name using the current Unix timestamp:\n   ```bash\n   date +%s\n   ```\n   backport-branch-name format: `ai-backport-pr-${pr_number}-${target_branch}-${unix_timestamp}`\n\n### 2. Create the backport branch\n\n```bash\ngit checkout -b $backport_branch_name $target_branch\n```\n\n### 3. Fetch PR commits\n\nGet the list of commit SHAs from the PR:\n\n```bash\ngh api \"repos/{owner}/{repo}/pulls/$pr_number/commits\" --paginate --jq '[.[].sha[0:10]]|join(\" \")'\n```\n\n`gh` resolves the `{owner}` and `{repo}` placeholders from the git remote of the current directory (see [`gh api` docs](https://cli.github.com/manual/gh_api)). Run this from a checkout of the repo that owns the PR.\n\nSave the full list of commits as `git_commits` and report how many commits were found.\n\n### 4. Cherry-pick commits\n\nRun the cherry-pick for all commits at once:\n\n```bash\ngit cherry-pick -x $git_commits\n```\n\nTrack these counters as you go:\n- **total_commits**: number of commits from the PR\n- **conflicts_resolved**: number of commits that needed conflict resolution\n- **commits_skipped**: number of commits skipped because they were already present on the target branch\n- **generated_files_touched**: list of generated files that were part of the cherry-picked commits (whether conflicted or not)\n\nIf the cherry-pick succeeds cleanly, check if any generated files were touched (see step 4a), then skip to step 6.\n\n### 4a. Check for generated files in cherry-picked commits\n\nAfter each successful cherry-pick (clean or after conflict resolution), check if any generated files were touched. Use the same generated file patterns from step 5.3. For clean cherry-picks, check the files changed in the commit(s) just applied:\n\n```bash\ngit diff --name-only HEAD~1 HEAD\n```\n\nMatch the output against the generated file patterns. Add any matches to `generated_files_touched`.\n\n### 5. Resolve conflicts\n\nIf the cherry-pick fails with merge conflicts, attempt resolution. For each conflicted state:\n\n1. Identify the commit that caused the conflict (the one currently being cherry-picked).\n2. List conflicted files: `git diff --name-only --diff-filter=U`\n3. Categorize each file:\n   - **Generated files** (accept theirs and move on): `*.pb.go`, `*.pb.h`, `*.pb.cc`, `*.pb.rs`, `*_pb2.py`, `go.sum`, `*.lock`, `MODULE.bazel.lock`, `package-lock.json`, `Cargo.lock`, `MODULE.bazel`\n     - For generated files: `git checkout --theirs -- $FILE && git add -- $FILE`\n     - Add each generated file to the `generated_files_touched` list\n   - **Modify/delete conflicts**: file deleted on one side but modified on the other. These require human judgment — **abort immediately** (see abort procedure below).\n   - **Content conflicts**: files with `<<<<<<<` / `=======` / `>>>>>>>` markers. Attempt resolution.\n\n4. For each content conflict:\n   a. Read the full conflicted file\n   b. Run `git log -p -1 $FAILING_COMMIT -- $FILE` to understand what the cherry-picked commit intended\n   c. Understand both sides:\n      - **HEAD side** (`<<<<<<<` to `=======`): what the target release branch has\n      - **Incoming side** (`=======` to `>>>>>>>`): what the cherry-picked commit brings\n   d. Read surrounding code if needed to understand context (imports, types, function signatures)\n   e. Determine the correct resolution:\n      - The goal is to apply the *intent* of the cherry-picked commit to the target branch\n      - Adapt to the target branch's patterns, names, and APIs where they diverge\n   f. Edit the file to remove all conflict markers and write the correct merged code\n   g. Stage: `git add -- $FILE`\n\n5. Verify the resolution is clean:\n   ```bash\n   grep -rn '<<<<<<< \\|=======$\\|>>>>>>> ' -- $RESOLVED_FILES\n   git diff --name-only --diff-filter=U\n   ```\n   Both commands should produce no output. If unmerged paths remain, something was missed.\n\n6. Continue the cherry-pick:\n   ```bash\n   git cherry-pick --continue --no-edit\n   ```\n   If `--continue` reports \"The previous cherry-pick is now empty\", the commit's changes are already on the target branch. Run `git cherry-pick --skip` to move on, and increment `commits_skipped`.\n\n7. Increment `conflicts_resolved` counter.\n\n8. If the next commit also conflicts, repeat from step 5.1.\n\n### 5a. Empty cherry-picks (no conflict)\n\nA cherry-pick can also be empty without any conflict — git will report \"The previous cherry-pick is now empty\" immediately. This happens when the commit's changes already exist on the target branch (e.g., from a prior backport or parallel fix). Run `git cherry-pick --skip` to move on and increment `commits_skipped`. This is normal and not an error.\n\n**Abort procedure** — If at any point you are uncertain how to resolve a conflict (ambiguous intent, modify/delete conflicts, architectural changes you don't understand, or anything that feels risky):\n\n1. Run `git cherry-pick --abort`\n2. Report an error with:\n   - The exact `git cherry-pick -x ...` command that was being attempted\n   - The specific commit SHA that caused the uncertain conflict\n   - Which files were conflicted and why you couldn't resolve them\n3. Stop immediately. Do NOT attempt further cherry-picks.\n\n### 6. Final report\n\nAfter all commits are cherry-picked successfully, resolve the source-PR URL dynamically:\n\n```bash\npr_url=$(gh pr view $pr_number --json url --jq .url)\n```\n\nThen emit this report to stdout:\n\n```\nBackport of PR ${pr_url}\n\n- Command: git cherry-pick -x ${git_commits}\n- Commits backported: ${total_commits}\n- Conflicts resolved: ${conflicts_resolved}\n- Commits skipped (already on target): ${commits_skipped}\n- Backport branch: ${backport_branch_name}\n\n## Conflict details\n\n- ${commit_sha} (${file}): one-line explanation of what conflicted and how it was resolved\n```\n\nFor each conflict resolved, include a one-line explanation covering what conflicted and how you resolved it. This helps PR reviewers understand the backport without reading every diff.\n\nIf `generated_files_touched` is non-empty, append this warning section:\n\n```\n## ⚠️ Generated files\n\nThe following files were cherry-picked and may need regeneration:\n\n- ${file_1}\n- ${file_2}\n\nThese files were accepted as-is from the source branch. Before merging,\nregenerate them on the target branch to ensure they're correct. For example:\n- MODULE.bazel.lock: run `bazel mod deps --lockfile_mode=update`\n- *.pb.go / *.pb.cc / *.pb.h: rebuild protobuf targets\n- go.sum: run `go mod tidy`\n```\n\n**Optional: machine-readable report.** If the `SKILL_REPORT_FILE` env var is set (callers like GitHub Actions workflows use this), write the exact same report text to that file path. The file content is designed to be usable verbatim as the body of the backport PR. Humans invoking the skill directly do not set this variable and simply read the report on stdout.\n\n## Rules\n\n- NEVER guess at conflict resolutions. If uncertain, abort immediately using the abort procedure.\n- NEVER silently drop code. Incoming changes that add new functionality must be preserved (adapted to the target branch's conventions).\n- Do NOT add comments to resolved code unless the original commit had them.\n- Preserve the formatting style of the target branch.\n- For generated files (protobuf, lockfiles, MODULE.bazel), accept theirs during the cherry-pick to unblock it. Two follow-ups are then required:\n  - List each touched generated file under the \"Generated files\" warning section in the PR body so the human reviewer regenerates and commits the correct version before merge.\n  - Do not describe these files as \"regenerated by the build\" in conflict-detail lines. Bazel/protoc/etc. update them in the local workspace only; CI does not push a corrected version back to the PR, so any regeneration must be committed manually.\n","isInternal":false,"tokens":2180,"sizeBytes":8930},{"name":"SKILL.md","path":".claude/skills/improve-coverage/SKILL.md","rawUrl":"https://raw.githubusercontent.com/redpanda-data/redpanda/HEAD/.claude/skills/improve-coverage/SKILL.md","title":"improve-coverage","category":"anthropic-skill","format":"markdown","content":"---\nname: improve-coverage\ndescription: Improve code coverage for a Bazel C++ test target or source file\nuser-invocable: true\n---\n\n# Improve Code Coverage\n\nImprove test coverage for a Bazel C++ test target or source file path.\n\n## Input Resolution\n\n1. **Bazel test target** (starts with `//`, e.g. `//src/v/bytes/tests:iobuf_test`):\n   use it directly.\n\n2. **Source file path** (e.g. `src/v/bytes/iobuf.cc`): find the test target:\n   ```bash\n   bazel query \"kind('.*_test', rdeps(//src/v/..., //$(dirname $FILE):$(basename $FILE .cc), 1))\" 2>/dev/null\n   ```\n   If multiple targets match, pick the one in the closest `tests/` directory.\n   If none match, tell the user and stop.\n\n## Workflow\n\n### Step 1: Baseline Coverage\n\nRun coverage and read the LLM report:\n\n```bash\ntools/run-cov <target> -r llm\n```\n\nNote the **total line coverage percentage** — this is the baseline to beat.\n\nRead the report and identify:\n- **Uncovered functions** — highest impact. One test can cover many lines.\n- **Partially covered functions** — uncovered branches indicate missing\n  error-path or edge-case tests.\n- **Zero-coverage files in scope** — may need new test cases entirely.\n\n### Step 2: Understand the Code\n\nRead the source files named in the report. Focus on:\n- Uncovered functions: what they do, inputs, preconditions.\n- The existing test file: patterns, fixtures, helpers.\n\nKey codebase patterns:\n- `redpanda_cc_gtest` targets use GTest (`TEST()`, `TEST_F()`).\n  `redpanda_cc_btest` targets use Seastar Boost test\n  (`SEASTAR_THREAD_TEST_CASE`). Match whichever the test file uses.\n- Many functions are `ss::future<>` coroutines — test with\n  `SEASTAR_THREAD_TEST_CASE` or the Seastar GTest runner.\n- Use `ss` namespace prefix for Seastar types.\n- Use `EXPECT_*` for most checks, `ASSERT_*` only when continuing would crash.\n\n### Step 3: Write Tests\n\nPrioritize by impact:\n1. **Uncovered functions** — call each one. A 20-line function = 20 lines covered.\n2. **Uncovered error paths** — trigger the uncovered branches in partially\n   covered functions.\n3. **Zero-coverage files** — add smoke tests if practical.\n\nRules:\n- Add tests to the **existing test file**. Don't create new files or BUILD\n  targets unless necessary.\n- Follow existing naming conventions in the file.\n- One logical behavior per test case.\n- Prefer deterministic tests — construct error conditions directly.\n- Do NOT add comments that restate the code.\n\n### Step 4: Verify Build\n\n```bash\nbazel build <target>\n```\n\nFix compilation errors before proceeding.\n\n### Step 5: Verify Coverage\n\nRe-run coverage:\n\n```bash\ntools/run-cov <target> -r llm\n```\n\nCompare to the baseline. Report:\n\n```\nCoverage: X.Y% → A.B% (+N.N%)\nNew lines covered: <count>\n```\n\nIf coverage did not improve, investigate:\n- Build errors preventing the new tests from running?\n- Code compiled out by preprocessor guards?\n- Template instantiations not triggered by test types?\n\n### Step 6: Summary\n\nReport:\n- Which functions/paths are now covered\n- What test cases were added\n- Before/after coverage numbers\n- Remaining significant gaps and suggestions\n","frontmatter":{"name":"improve-coverage","description":"Improve code coverage for a Bazel C++ test target or source file","user-invocable":true},"isInternal":false,"tokens":780,"sizeBytes":3102},{"name":"README.md","path":"bazel/clang_tidy/plugins/README.md","rawUrl":"https://raw.githubusercontent.com/redpanda-data/redpanda/HEAD/bazel/clang_tidy/plugins/README.md","title":"plugins Documentation","category":"plugin-manifest","format":"markdown","content":"# Redpanda clang-tidy plugins\n\nThis directory contains sources and bazel targets for custom out-of-tree clang-tidy checks.\nIt contains a single `redpanda_cc_binary` target, `plugins.so`, which contains a single clang-tidy\nmodule, `redpanda-module`.\n\n## Building\n\n```sh\n$ bazel build //bazel/clang_tidy/plugins:plugins.so\n```\n\n## Adding a new check\n\n1. Write your check in a separate file or files. e.g. `plugins/redpanda_noop_check.h`.\n2. Add your sources directly to `plugins.so.srcs` including any new dependencies.\n3. Register the check by name in `plugins/redpanda_tidy_module.h`.\n   - The name should start with \"redpanda-\".\n\nChecks introduced in this way are automatically loaded in `bazel build --config=clang-tidy //...`.\n\nIf you want to try your check on some source file in isolation, you can invoke the tool directly:\n\n```sh\n$ bazel build //bazel/clang_tidy/plugins:plugins.so\n$ ./tools/clang-tidy --checks=-*,redpanda-my-fancy-check -list-checks\n$ ./tools/clang-tidy --checks=-*,redpanda-my-fancy-check /path/to/some/foo.cc\n```\n","isInternal":false,"tokens":277,"sizeBytes":1040},{"name":"README.md","path":"tools/docker/README.md","rawUrl":"https://raw.githubusercontent.com/redpanda-data/redpanda/HEAD/tools/docker/README.md","title":"docker Documentation","category":"plugin-manifest","format":"markdown","content":"# Docker Image with Redpanda Toolchain\n\nCI bazel toolchain image (everything CI runs in this image).\n","isInternal":false,"tokens":24,"sizeBytes":101},{"name":"README.md","path":"tools/go-comment-pbgen/README.md","rawUrl":"https://raw.githubusercontent.com/redpanda-data/redpanda/HEAD/tools/go-comment-pbgen/README.md","title":"go-comment-pbgen Documentation","category":"plugin-manifest","format":"markdown","content":"# Go Protobuf Comment Generator\n\nCustom `protoc` plugin that extracts proto comments and makes them available at runtime for `rpk shadow config generate-template`.\n\n## Overview\n\nGenerates `comments.pb.go` files containing a simple `map[string]string` of proto field comments keyed by descriptor full names. Proto `.pb.go` files come from `buf.build/gen/go/redpandadata/core`.\n\n\n## Usage\n\nGenerate comment files:\n```bash\nbuf generate --path proto\n```\n\nAccess comments in code:\n```go\nimport v2comments \"github.com/redpanda-data/redpanda/src/go/rpk/gen/protocomments/admin/v2\"\n\ncomment := v2comments.GetCommentForField(fieldDescriptor)\n// Or by full name:\ncomment = v2comments.GetComment(\"redpanda.core.admin.v2.ShadowLink.uuid\")\n```\n\n## Generated Code\n\nEach package gets one `comments.pb.go`:\n```go\npackage v2\n\n// Comments maps descriptor full names to their leading comments\nvar Comments = map[string]string{\n    \"redpanda.core.admin.v2.ShadowLink.uuid\": \"The UUID of the shadow link\",\n    \"redpanda.core.admin.v2.ShadowLink.name\": `The name of the shadow link\nThis is a multi-line comment`,\n}\n\nfunc GetComment(fullName string) string\nfunc GetCommentForField(fd protoreflect.FieldDescriptor) string\nfunc GetCommentForMessage(md protoreflect.MessageDescriptor) string\nfunc GetCommentForEnum(ed protoreflect.EnumDescriptor) string\nfunc GetCommentForEnumValue(evd protoreflect.EnumValueDescriptor) string\n```\n\n**Multi-line comments**: Use raw string literals (backticks) to preserve actual newlines instead of `\\n` escape sequences.\n\n## Output Structure\n\n```\nsrc/go/rpk/gen/protocomments/\n├── admin/v2/comments.pb.go\n└── common/v1/comments.pb.go\n```\n\n## Development\n\nModify plugin:\n```bash\n# 1. Edit generator.go\n# 2. Regenerate\nbuf generate --path \"proto/redpanda/core/admin/v2/shadow_link.proto\",\"proto/redpanda/core/common/v1/acl.proto\",\"proto/redpanda/core/common/v1/tls.proto\"\n# 3. Test\ncd src/go/rpk && go build ./...\n```\n\nCI checks that generated files are up-to-date in `.github/workflows/rpk-build.yml`.\n","isInternal":false,"tokens":502,"sizeBytes":2021},{"name":"README.md","path":"tools/metrics-mock/README.md","rawUrl":"https://raw.githubusercontent.com/redpanda-data/redpanda/HEAD/tools/metrics-mock/README.md","title":"metrics-mock Documentation","category":"plugin-manifest","format":"markdown","content":"# Metrics Mock\n\nmetrics-mock.py is a script that starts an http server and serves\nredpanda-like metrics from /metrics and /public-metrics endpoints.\n\nSo it serves as a stand-in for Redpanda when testing things in the\nmetrics pipeline, as using a large clusters simply to generate large\nnumbers of metrics is wasteful.\n\n## Usage\n\nThe script has no dependencies and should be directly executable on\nPython 3.10 or later.\n\nThe primary functionality of metrics mock is accessble through `metrics-mock.py host`,\nwhich starts the HTTP server on port `9644` (configurable with `--port`):\n\n```\n./metrics-mock host\n```\n\nOn another terminal run:\n\n```\n$ curl -sL 'http://127.0.0.1:9644/metrics' | head\n```\n\nwhich should produce output like:\n\n```\n# HELP vectorized_alien_receive_batch_queue_length Current receive batch queue length\n# TYPE vectorized_alien_receive_batch_queue_length gauge\nvectorized_alien_receive_batch_queue_length{shard=\"0\"} 5923\n# HELP vectorized_alien_total_received_messages Total number of received messages\n# TYPE vectorized_alien_total_received_messages counter\nvectorized_alien_total_received_messages{shard=\"0\"} 1033\n# HELP vectorized_alien_total_sent_messages Total number of sent messages\n# TYPE vectorized_alien_total_sent_messages counter\nvectorized_alien_total_sent_messages{shard=\"0\"} 13440\n# HELP vectorized_application_build Redpanda build information\n```\n\nThere are several options to modify the behavior of the metrics endpoint,\nspecifically to emulate different sizes of Redpanda node and Redpanda cluster.\n\nE.g., to approximate a T7 cluster, use the following flags:\n\n\n```\n./metrics-mock.py print --partitions 50000 --topics 10 --shards 31 --nodes 9\n```\n\nEach `./metrics-mock.py` instance acts as a single node in a multi-node cluster:\nin the above example the mock will act as a single node in a 9 node cluster. This\nis important when deciding which metrics appear for resources which are allocated\nto a partitcular subset nodes, such as topic partitions. To emulate a differnet\nnode, pass `--node-id`. If you are emulating a multi-node cluster you should pass\na different node id to each mock, or else they all return the same metrics for\nper-topic, per-partition or per-group metrics, which will significantly underestimate\nthe total metrics count.\n\n\n\n\n","isInternal":false,"tokens":536,"sizeBytes":2284},{"name":"README.md","path":"tools/offline_log_viewer/README.md","rawUrl":"https://raw.githubusercontent.com/redpanda-data/redpanda/HEAD/tools/offline_log_viewer/README.md","title":"offline_log_viewer Documentation","category":"plugin-manifest","format":"markdown","content":"Offline tool to parse RedPanda disk logs.\nUsage: `python viewer.py -h`","isInternal":false,"tokens":19,"sizeBytes":70},{"name":"README.md","path":"tools/p/README.md","rawUrl":"https://raw.githubusercontent.com/redpanda-data/redpanda/HEAD/tools/p/README.md","title":"p Documentation","category":"plugin-manifest","format":"markdown","content":"The `p` exectuable script is a containerized drop-in replacement for `p`.\n\n```\ntools/p/p compile\n```\n","isInternal":false,"tokens":28,"sizeBytes":101},{"name":"README.md","path":"tools/rp_storage_tool/README.md","rawUrl":"https://raw.githubusercontent.com/redpanda-data/redpanda/HEAD/tools/rp_storage_tool/README.md","title":"rp_storage_tool Documentation","category":"plugin-manifest","format":"markdown","content":"# rp-storage-tool\n\n## Purpose\n\nThis low level tool is for offline use by Redpanda Engineering when diagnosing\nfaults.\n\nThis tool is **not** for everyday use on live clusters.\n\n## Quickstart\n\n    # Get a rust toolchain\n    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n\n    # Compile and run\n    cargo run --release -- --backend=<aws|gcp|azure> scan --source=<bucket name>\n\n## Installation\n\nTo run the tool outside a built tree, you may simply copy the statically linked binary.\n\n    # Compile\n    cargo build --release\n\n    # Grab the binary\n    cp target/release/rp-storage-tool /usr/local/bin\n\n## Usage\n\n### Typical usage\n\nThe following examples assume you are running on an AWS instance with IAM roles configured\nfor access to your bucket. If you need to specify static keys, see the \"Working with local object stores\"\nsection below.\n\n    # Scan the bucket, store metadata in a file, and report any metadata anomalies\n    $ rp-storage-tool --backend aws scan-metadata --source=my-bucket --meta-file my-bucket.json\n\n    # Scan the data within a topic of interest, and report any anomalies.  Use the metadata\n    # file from the last step to avoid re-scanning all objects in the bucket.\n    $ rp-storage-tool --filter kafka/my-topic/*_* --backend aws scan-data --source=my-bucket --meta-file my-bucket.json\n\n    # Having identified an issue, extract the data from a partition for further analysis offline\n    $ mkdir my-data-dump\n    $ rp-storage-tool --filter kafka/my-topic/13_63 --backend aws extract --source=my-bucket --meta-file my-bucket.json --sink ./my-data-dump/\n\n### Generic parameters\n\n* The `--backend` argument selects the cloud storage backend. This has a default (AWS) for convenience when working with\n  commands that don't use cloud storage, but ordinarily you should be specifying it.\n* The `--filter` argument controls which topics & partitions will be examined. When scanning metadata we\n  always scan all objects, but will only store+analyze metadata for partitions matching the filter.\n* If you are running on a node with authentication already set up (e.g. IAM Roles on AWS), this will Just Work.\n  Otherwise you may need to set the appropriate access key/secret environment variables for the cloud platform you are\n  connecting to.\n\n### Subcommands\n\n#### `scan-metadata`\n\nThis walks the objects in a bucket used by Redpanda Tiered Storage and reports on any inconsistencies.\n**Not all issues this tool reports are harmful**, for example `segments_outside_manifest` may contain objects harmlessly\nleft behind when Redpanda was restarted during an upload.\n\n#### `scan-data`\n\nThis walks the data within segments (you probably want to use `--filter` to only scan segments for certain NTPS)\nto check that it is readable, and that the metadata in the partition manifest is consistent with the data.\n\n#### `extract`\n\nFor NTPs matching the filter, copy all metadata and data objects into a local folder.\n\n## Working with local object stores\n\nThis tool uses environment variables for any special configuration of storage backends required for working outside of\nreal cloud environments.\n\nFor example, to use the tool with a bucket called `data` in a minio cluster at `aplite:9000`:\n\n    AWS_ALLOW_HTTP=1 AWS_ACCESS_KEY_ID=minioadmin AWS_SECRET_ACCESS_KEY=minioadmin AWS_REGION=us-east-1 \\\n      AWS_ENDPOINT=http://aplite:9000 cargo run --release -- --backend aws scan --source data\n\n### Building a portable binary\n\nIf you are building on a workstation and copying the binary to a remote machine, and your\nworkstation doesn't happen to run the same linux distro as the remote machine, it is\nuseful to build a statically linked binary that will work on any distro.\n\n    # A dependency requires this to build: adjust as needed if not on an RPM distro\n    sudo dnf install -y musl-gcc\n\n    # Install the Rust toolchain for musl (statically linked libc)\n    rustup target add x86_64-unknown-linux-musl\n\n    # Build with the musl toolchain\n    cargo build --release --target=x86_64-unknown-linux-musl\n\n    # Output in target/release/rp-storage-tool\n\n","isInternal":false,"tokens":936,"sizeBytes":4079},{"name":"README.md","path":"tools/type-checking/README.md","rawUrl":"https://raw.githubusercontent.com/redpanda-data/redpanda/HEAD/tools/type-checking/README.md","title":"type-checking Documentation","category":"plugin-manifest","format":"markdown","content":"# Ducktape Type Checking\n\nThis directory contains the infrastructure for running PEP-484 type checking on Redpanda's Python test suite (ducktape) using [pyright](https://github.com/microsoft/pyright).\n\n## Overview\n\nThe type checking system provides:\n- **Gradual type checking adoption** with per-file strictness levels\n- **Automated promotion detection** to identify files ready for stricter checking\n- **CI integration** to prevent type regressions\n- **Docker-based execution** for consistent environments\n\n## Files\n\n### `type-check.py`\n\nThe main type checking orchestration script. Provides multiple commands:\n\n- **`check`** - Run type checking on files and report errors\n- **`promotion-check`** - Identify files that can be promoted to stricter type checking levels\n- **`fruit`** - Show files with fewest errors at each target level (low-hanging fruit for type checking improvements)\n- **`ci`** - Run both `check` and `promotion-check` and decorates the results (used in CI)\n- **`check-sorted`** - Verify that `type-check-strictness.json` is sorted\n- **`pre-commit`** - Run `check` on changed files (intended for use as a pre-commit hook); accepts paths rooted at repo root (`tests/...`) and strips the prefix internally\n\n### `type-check-strictness.json`\n\nConfiguration file that maps Python files to their current type checking strictness level. Files not listed default to `strict` level.\n\n**Strictness levels** (from most to least strict):\n- `strict` - Full type checking (default for unlisted files)\n- `standard` - Standard type checking\n- `basic` - Basic type checking\n- `off` - Minimal checking (syntax, imports, etc.)\n- `skip` - No type checking at all\n\n### `Dockerfile`\nMulti-stage Docker image for running type checking in a consistent environment. Installs Python dependencies and pyright.\n\n### `type-check.sh`\nShell script that builds the Docker image and runs type checking inside a container.\n\n### `requirements.txt`\nPython dependencies for type checking (primarily pyright).\n\n## Usage\n\n### Local Development\n\n#### Using task\n\nThere is a type-check `task` which is a very thin wrapper around the scripts: this is probably the most convenient way to call it as it works\nanywhere in the repository. Call it with arguments for the script after `--`, like so:\n\n```\ntask rp:type-check -- --help\n```\n\n#### Using type-check.sh\n\nYou can also call the script as `tools/type-checking/type-check.sh --help`. This is essentially the same as the `task` invocation above. This runs the underlying python script inside a docker container, so it should \"just work\".\n\n#### Using type-check.py\n\nLocally you should also able to run the .py script directly, like `tools/type-checking/type-check.py --help`  if you have run [local_venv.sh](../../tests/local_venv.sh).\n\n### Examples\n\nThese examples all the `task` invocation method but work with any of the methods above.\n\n```bash\n# Run type checking on all files\ntask rp:type-check -- check\n\n# Check for files ready to be promoted to stricter levels\ntask rp:type-check -- promotion-check\n\n# Find low-hanging fruit - files with fewest errors at each target level\ntask rp:type-check -- fruit\n\n# Show top 25 fruit candidates\ntask rp:type-check -- fruit --top 25\n\n# Show detailed error information with verbose output\ntask rp:type-check -- fruit --verbose 2\n\n# Automatically promote eligible files\ntask rp:type-check -- promotion-check --update\n\n# Run CI checks (both check and promotion-check)\ntask rp:type-check -- ci\n\n# Check specific files (paths/globs are positional, relative to tests/)\ntask rp:type-check -- check rptest/tests/my_test.py\n\n# Force all files to be checked at a specific level\ntask rp:type-check -- check --force-level strict\n```\n\n## Strictness Level Management\n\nIn order to have type checking useful immediately, without needing to fix 1000s of existing type errors, the system uses a progressive approach to type checking adoption:\n\n1. **New files** default to `strict` type checking\n2. **Existing files** are assigned to the strictest level they currently pass\n3. **Promotion detection** automatically identifies when files can move to stricter levels\n4. **Automated updates** can promote files with `--update` flag\n\n### Adding Type Hints to Files\n\nWhen adding type hints to improve a file:\n\n1. Run `tools/type-checking/type-check.py promotion-check --update`\n2. This will automatically move the file to the strictest level it now passes\n3. Commit both your type hint changes and the updated `type-check-strictness.json`\n\n### Finding Low-Hanging Fruit\n\nThe `fruit` command helps identify files that are closest to being promotable to stricter type checking levels by showing files with the fewest errors at each target level:\n\n```bash\n# Show top 10 files with fewest errors per target level\ntask rp:type-check -- fruit\n\n# Show top 20 files with fewest errors per target level\ntask rp:type-check -- fruit --top 20\n\n# Show detailed error information with verbose output\ntask rp:type-check -- fruit --verbose 2\n```\n\nThis is useful for:\n- **Prioritizing type checking improvements** - start with files that need minimal fixes\n- **Setting realistic goals** - see how many errors need to be fixed for each promotion\n- **Understanding error distribution** - identify patterns in type checking issues\n\nThe command groups files by their potential promotion target (off→basic, basic→standard, etc.) and sorts them by error count, making it easy to find files that are closest to passing at the next strictness level.\n\n### Failed Promotion Check\n\nYou may be here because CI failed with a \"promotion check\" error. This means that the strictness level for some files are set too low, i.e., your change has improved the state of the Python code base and as a reward you can promote some files to higher strictness levels. Pat yourself on the back, then use the following command to automatically update the strictness file:\n\n```bash\n# Automatically update the configuration\ntask rp:type-check -- promotion-check --update\n\n# Commit the updated type-check-strictness.json\ngit add tools/type-checking/type-check-strictness.json\ngit commit -m \"chore: update type checking strictness\"\n```\n\n## Command Line Options\n\n- `--tests-root PATH` - Path to tests directory (auto-detected by default)\n- `--config PATH` - Path to pyrightconfig.json\n- `input_files` - Positional paths/globs of files to check, relative to `tests/` (default: `rptest/**/*.py`)\n- `--force-level LEVEL` - Override strictness levels and check all files at specified level\n- `--no-venv` - Don't use virtual environment when running pyright\n- `--verbose [LEVEL]` - Enable verbose output (0=off, 1=verbose, 2=very verbose)\n- `--update` - Update type-check-strictness.json in-place (for promotion-check)\n- `--top N` - Number of files to show per target level in fruit command (default: 10)\n\n## CI Integration\n\nThe type checking runs automatically on pull requests via GitHub Actions (`.github/workflows/python-type-check.yml`). The CI:\n\n1. Builds the Docker image from `Dockerfile`\n2. Runs `type-check.py ci` which executes both type checking and promotion detection\n3. Fails if there are type errors or files that should be promoted\n\nThis check tries to be as fast as possible, and usually runs in about 3 minutes if the docker image build is fully cached (usually the case), or a few minutes more if not.\n\n## Architecture\n\nThe system is designed around Microsoft's pyright type checker with these key features:\n\n- **Segmented checking**: Files are grouped by strictness level and checked separately\n- **Temporary configs**: Generates temporary pyrightconfig.json files for each strictness level\n- **Batch processing**: Efficiently checks multiple files at once when possible\n- **Virtual environment integration**: Uses `.venv` if available for consistent Python environment\n\n## Adding New Features\n\nThe `type-check.py` script is modular and can be extended with new commands by:\n\n1. Adding the command name to the `CMDS` list\n2. Adding documentation to `COMMAND_DOC`\n3. Implementing a method on the `TypeCheck` class with the command name (with hyphens converted to underscores)\n\nThe script follows the pattern where each command method returns `True` for success, `False` for failure, or `None` for success (which is treated as `True`).\n","isInternal":false,"tokens":1857,"sizeBytes":8252}],"systemPromptSnippet":"<yakaai_skills repo=\"redpanda-data/redpanda\">\n<!-- File: CLAUDE.md (Tokens: ~3303 | Category: claude-rule) -->\n# Copilot Coding Agent Onboarding Guide for `redpanda-data/redpanda`\n\n## High-Level Overview\n\n**What is Redpanda?**\n\nRedpanda is a high-performance, Apache Kafka®-compatible streaming data platform. It is written primarily in C++ for the core, with Go for CLI tooling (`rpk`), and some Python for auxiliary scripts and tests. Redpanda is designed to be lightweight, fast, and simple to operate, omitting ZooKeeper and the JVM.\n\nIt uses extensively the thread-per-core model and asynchronous (coroutines, futures) programming model.\n\n**Repository Characteristics:**\n- **Large multi-language codebase:** C++ (core), Go (CLI/tools), Python (testing/scripts), Bash and Bazel for builds.\n- **Build System:** Bazel (with Bazelisk) for core.\n- **Target Platforms:** Linux (primary), some support for macOS and Windows.\n- **Key Directories:**\n  - `src/v/`: Core C++ source code\n  - `src/go/`: Go CLI and tools\n  - `bazel/`, `BUILD`, `MODULE.bazel`: Bazel scripts and definitions\n  - `tools/`: Development and helper scripts\n  - `tests/`: Test suites\n  - `conf/`: Configuration files\n  - `proto/`: Protobuf definitions for Redpanda services and APIs\n  - `.github/`, `.buildkite/`: CI/workflow automation\n- **Documentation:** [Docs site](https://redpanda.com/documentation) and `docs/`.\n\n## Build, Test, and Validation Instructions\n\n### Prerequisites (Always Perform)\n\n1. **Install Bazelisk** (the required Bazel wrapper):\n   ```bash\n   wget -O ~/bin/bazel https://github.com/bazelbuild/bazelisk/releases/latest/download/bazelisk-linux-amd64\n   chmod +x ~/bin/bazel\n   export PATH=\"$HOME/bin:$PATH\"\n   ```\n2. **Install system dependencies**:\n   ```bash\n   sudo ./bazel/install-deps.sh\n   ```\n   _This must always be run before any Bazel build, especially on a fresh system or after dependency updates._\n\n### Core Build Steps\n\n- **Build all (fastbuild):**\n  ```bash\n  bazel build //...\n  ```\n- **Test all:**\n  ```bash\n  bazel test //...\n  ```\n- **Lint (C++):**\n  - Formatting and linting are enforced. Use:\n    ```bash\n    bazel run //tools:clang_format\n    ```\n  - Configs: `.clang-format`, `.clang-tidy`, etc.\n\n- **Go CLI (`rpk`) Build:**\n  ```bash\n  bazel build //:rpk\n  ```\n\nSee `.bazelrc` for more details on build settings and config modes\n\n### Validation and CI\n\n- **Pre-push/merge:** All changes are validated by CI (GitHub Actions, Buildkite) for build, test, and lint.\n- **Formatting and lint checks are enforced; run locally before PRs.**\n- **Target branch:** Always open PRs against `dev`.\n\n## Project Layout & Architectural Notes\n\n- **Main C++ source:** `src/v/`\n- **Go CLI:** `src/go/rpk/`\n- **Build configuration:** `.bazelrc`, `.bazelversion`, `BUILD`, `MODULE.bazel`, and `bazel/`\n- **CI configuration:** `.github/workflows/`, `.buildkite/`\n- **Testing:** `tests/`\n- **Config:** `conf/`\n- **Docker:** `tools/docker/`\n\n### Lint/Formatting Configs:\n- `.clang-format`, `.clang-tidy*`: C++ style\n- `tools/ruff`, `.ruff.toml`: Python formatting\n\n### CI/CD Checks\n\n- **Build, test, and lint are enforced by CI.** Use the same steps as above locally before PRs.\n\n### File Index (Root Level)\n- `.bazelignore`, `.bazelrc`, `.bazelversion`, `BUILD`, `MODULE.bazel`, `README.md`, `CONTRIBUTING.md`, `SECURITY.md`, `CODE_OF_CONDUCT.md`, `LICENSES/`, `bazel/`, `src/`, `tests/`, `conf/`, `tools/`, `.github/`, `.buildkite/`, etc.\n\n---\n\n## Protobuf-Specific Instructions\n\n### Protobuf Coding Guidelines\n\nFollow the guidelines provided in `proto/redpanda/README.md` for basic Protobuf standards. For admin v2 ConnectRPC endpoints (proto → C++ service impl → registration → ducktape Python bindings), use the `add-admin-v2-endpoint` skill.\n\n### Protobuf Build & Environment\n- **Primary Protobuf code lives in `proto/`.**\n- **Formatting:** `.clang-format` in the root directory is enforced. Always run `clang-format` before committing changes or submitting a PR:\n  ```bash\n  bazel run //tools:clang_format\n  ```\n\n## C++-Specific Instructions\n\n### C++ Build & Environment\n\n- **Primary C++ code lives in `src/v/`.**\n- **C++ build is managed by Bazel.** All dependencies and toolchains are configured via Bazel rules and the `MODULE.bazel` file. Do not manually install C++ dependencies unless explicitly instructed in documentation.\n- **Compiler Standard:** C++23 is required. Some SDK components (e.g., `src/transform-sdk/cpp/`) use C++23 and specific flags like `-Wall`, `-fno-exceptions`, and for some targets, `-stdlib=libc++`.\n- **Sanitizers:** Some components and test builds use sanitizers (address, leak, undefined) via `-fsanitize=address,leak,undefined` for both compile and link.\n- **Suppression Files:** Leak, undefined, and other sanitizer suppressions can be found in the root as `lsan_suppressions.txt`, `ubsan_suppressions.txt`.\n- **C++ Linting:** `.clang-format` and `.clang-tidy` in the root directory are enforced. Always run `clang-format` before committing changes or submitting a PR:\n  ```bash\n  bazel run //tools:clang_format\n  ```\n- **C++ Libraries:** Bazel dependencies are managed in `MODULE.bazel` (e.g., Boost, Abseil, fmt, protobuf, googletest, yaml-cpp, etc.).\n- **Testing:** C++ unit tests are run via Bazel.\n\n### Common C++ Pitfalls & Workarounds\n\n- **Always use Bazelisk and Bazel for building the core.** Using a plain Bazel binary may result in missing dependencies or incompatible flags.\n- **If you encounter build issues related to missing system libraries, rerun `sudo ./bazel/install-deps.sh`.**\n- **Do not attempt to manually install or update C++ dependencies unless specifically instructed.**\n- **Always run lint and formatter before pushing. CI will fail on formatting/lint discrepancies.**\n- **If building in CI or a containerized environment, ensure the correct toolchain is available as specified in `tools/docker/README.md` or CI scripts.**\n- **Check for additional build and compile flags in `BUILD`, `MODULE.bazel` and related files.**\n\n### C++ coding guidelines\n\nCheck that these guidelines are followed for new code.\n\n- Write [scalable code](docs/writing-scalable-and-high-performance-code.md).\n- Do not declare new `operator<<(ostream& os, type)` overloads, instead prefer to use a `format_to` member function inside `type` as described in\nsrc/v/base/format_to.h.\n- Prefer using latest C++ features (C++23).\n- Use `ss` namespace as a prefix for Seastar types (e.g. `ss::future`, `ss::promise`).\n- Use `vassert(cond, msg, msg_args...)` macro for assertions. It is\n  similar to `assert(cond)` but it is always enabled and it prints the message\n  to the log. Use `dassert` for assertions that are only enabled in debug mode.\n- Use `vlog(method, fmt, args...)` for logging. `method` is the method reference\n  for the logger to use. I.e. `vlog(stlog.info, \"Hello world\");`. Where `stlog`\n  is defined as `ss::logger stlog(\"storage\");`.\n- Do not use `std::vector` for containers that may grow very large, instead use `chunked_vector`.\n- Do not use `std::unordered_map` for containers that may grow very large, instead use `chunked_hash_map`.\n- Instead of long if-else chains for mapping string to values, use\n  `string_switch` mechanism defined in\n  [string_switch.h](./src/v/strings/string_switch.h).\n- Don't use `ss::parallel_for_each` with ranges that can grow large such as\n  partitions, topics or segments. Instead prefer `ss::max_concurrent_for_each` to\n  limit concurrency. Think about how much concurrency is needed to adequately hide\n  latency. See our\n  [docs](https://redpandadata.atlassian.net/wiki/x/AQBZTw#Managing-Concurrency-in-the-system)\n  for more background.\n- Do not call the `get_exception` method on a future within a logging or\n  assertion statement (e.g. `vlog(..., fut.get_exception(), ...)`). Instead,\n  assign the return value of `get_exception` in a variable and pass the variable.\n\n### Coroutines\n\nFollow [coroutines best practices](docs/writing-high-performance-coroutine-code.md) when using\ncoroutines.\n\n#### Lambda coroutines, coroutine argument capture, and deducing this\n\nWhen a lambda coroutine is passed to APIs like `seastar::future::then()`,\nthe lambda object is stored in managed memory that gets freed once the\ncontinuation returns—but the coroutine may still be suspended and later\naccess its captures, causing use-after-free. This happens because the\ncoroutine frame holds a reference to the lambda's capture storage, which\nbecomes dangling. The C++23 \"deducing this\" syntax\n`([captures...](this auto, args...))` solves this by moving the captures\ndirectly into the coroutine frame rather than referencing them through the\nlambda object, decoupling capture lifetime from the lambda's lifetime.\nThis is distinct from the recursive-lambda use case—here this auto is required\nfor memory safety in coroutines, not self-reference.\n\n### Exception handling\n\n- Read the \"Exceptions in coroutines\" section within [tutorial.md](external/+non_module_dependencies+seastar/doc/tutorial.md)\n  to best understand how exceptions should be handled in Redpanda.\n- `throw` and `std::rethrow_exception()` may have an effect on performance, especially in hot paths.\n  There are several `seastar` utilities that should be preferred to explicitly throwing exceptions.\n  Namely, `ss::coroutine::try_future()`, `ss::coroutine::as_future()`, `ss::coroutine::return_exception()`,\n  and `ss::coroutine::return_exception_ptr()`. This is only a brief summary of the content in the tutorial above,\n  which must be read to fully understand exception handling in Redpanda.\n\n### C++ coding style\n\n- Use snake_case for identifiers. Use CamelCase for concepts.\n- Use Doxygen comments with 3-slashes (///) for public APIs\n\n### Code comments\n\n- **Default to no comments** - clear code and good names are better\n- **Avoid comments that restate the code** - they become stale\n- **Prefer alternatives:**\n  - Better variable/function names\n  - Log lines (serve as documentation and debugging)\n- **Do add comments for:**\n  - Doc comments (`/// \\brief`) on public types explaining purpose/usage\n  - Complex algorithms or non-obvious \"gotchas\"\n  - Test comments explaining input format or test intent\n  - Links to external resources (specs, docs, issues)\n  - Mapping internal types/concepts to external formats (e.g., wire protocols, APIs)\n  - ASCII diagrams for complex state machines or data flows\n- **Avoid obvious branching comments** - `if (x)` rarely needs `// when x is true`\n\n### Benchmarking\n\n  - Run benchmarks like `bazel run --config=release //src/v/utils/tests:coro_rpbench`\n  - Get --help like: `bazel run --config=release //src/v/utils/tests:coro_rpbench -- --help`\n  - If running/writing benchmarks read this also: external/+non_module_dependencies+seastar/tests/perf/perf-tests.md\n\n### More C++-Specific References\n\n- [MODULE.bazel](https://github.com/redpanda-data/redpanda/blob/dev/MODULE.bazel)\n- [BUILD](https://github.com/redpanda-data/redpanda/blob/dev/BUILD)\n\n---\n\n## Python specific instructions\n\n### Instructions for python test code under tests/rptest\n\n- Avoid catching bare `except:` as this can hide system exceptions, including exceptions\n  raised by a signal when a test is being forcibly timed out. Instead use `except Exception:`.\n- After modifying files under `tests/rptest`, run type checking via\n  `tools/type-checking/type-check.sh check <files...>` (paths positional, relative to `tests/`)\n  to scope it to your changes. Do not invoke `type-check.py` directly.\n  See `tools/type-checking/README.md` for more details: the per-file\n  strictness levels in `type-check-strictness.json` and how to promote\n  files to stricter levels after adding type hints.\n\n### Instructions for type hints\n\n- Use modern style with `|` instead of `Union` or `Optional`\n\nFor further details, consult:\n- [README.md](https://github.com/redpanda-data/redpanda/blob/dev/README.md)\n- [CONTRIBUTING.md](https://github.com/redpanda-data/redpanda/blob/dev/CONTRIBUTING.md)\n- [Redpanda Documentation](https://redpanda.com/documentation)\n- CI/CD configs in `.github/workflows/` and `.buildkite/`\n\n---\n\n## Commit message instructions\n\nWhen writing or critiquing commit messages, follow these guidelines:\n\n**Format:**\n```\narea[/detail]: short description\n\n<optional body>\n```\n\n- Title: ≤72 chars\n- Body: wrapped at 72 chars\n\n**Check git history first** to match the style of the area you're changing:\n```bash\ngit log --oneline --no-merges -- path/to/changed/files | head -20\n```\n\n**Goal:** Make the commit easy to review and provide context for future readers (via `git blame`/`git log`)\n\n**Rules:**\n- Title: imperative mood, lowercase after colon, no period\n- Keep bodies concise (1-2 lines typical, trivial changes need only a title)\n- Don't reference GitHub issues/PRs or Jira tickets\n- Use the body to help reviewers and future readers understand:\n  - The \"why\": motivation, design choices, preparation for future work\n  - The \"what\": new abstractions introduced, non-obvious changes\n  - Integration tests: briefly note what behaviors are covered\n- Don't restate what's obvious from the diff, but duplicating a doc comment is fine if it helps reviewers understand the change faster.\n\n_Results from code search may be incomplete. For more C++ details, see the [repository code search](https://github.com/redpanda-data/redpanda/search?q=c%2B%2B)._\n\n<!-- File: .github/copilot-instructions.md (Tokens: ~4 | Category: copilot-instructions) -->\nCLAUDE.md\n\n<!-- File: .claude/skills/add-admin-v2-endpoint/SKILL.md (Tokens: ~4146 | Category: anthropic-skill) -->\n---\nname: add-admin-v2-endpoint\ndescription: Use when adding or modifying a Redpanda admin v2 ConnectRPC endpoint — authoring the protobuf under proto/redpanda/core/admin/v2/, wiring it into the Bazel build, implementing the C++ service handler under src/v/redpanda/admin/services/, registering it on the admin server, and regenerating Python bindings for ducktape integration tests. Covers both new RPCs on an existing service and brand-new services.\n---\n\n# Add an Admin v2 ConnectRPC Endpoint\n\nThis skill walks through every layer touched by a new admin v2 RPC: the `.proto`, the Bazel build, the C++ service implementation, registration on the admin server, and the Python bindings consumed by ducktape tests.\n\nFor generic protobuf authoring conventions (field options, message design, well-known types), the canonical reference is `proto/redpanda/README.md`. This skill assumes you've read that and focuses on what's specific to admin v2.\n\n## Reference example\n\nCommit `04d3160c3a` (`cluster: add manual upgrade finalization with admin v2 RPC`) is the cleanest minimal example — a new service with a single RPC. Skim these when unsure:\n\n- `proto/redpanda/core/admin/v2/features.proto` — the proto\n- `proto/redpanda/core/admin/v2/BUILD` — the BUILD entries (look at the `features_proto` / `features_redpanda_proto` pair)\n- `src/v/redpanda/admin/services/features.h` and `features.cc` — the C++ impl\n- `src/v/redpanda/admin/services/BUILD` — the `features` cc_library target\n- `src/v/redpanda/application_admin.cc` — the `add_service` registration\n\n`git show 04d3160c3a` will print the full diff.\n\n## Procedure\n\n### 1. Author the protobuf\n\nCreate or extend a `.proto` under `proto/redpanda/core/admin/v2/`. One file per service domain; the filename matches the dominant service (`features.proto` → `FeaturesService`).\n\nRequired header for a new file:\n\n```proto\n// (BSL copyright header — copy from features.proto:1-13)\nsyntax = \"proto3\";\n\npackage redpanda.core.admin.v2;\n\nimport \"proto/redpanda/core/pbgen/options.proto\";\nimport \"proto/redpanda/core/pbgen/rpc.proto\";\n\noption (pbgen.cpp_namespace) = \"proto::admin\";\n```\n\nNotes:\n\n- `package` is always `redpanda.core.admin.v2` for admin v2.\n- `option (pbgen.cpp_namespace) = \"proto::admin\";` is mandatory and always exactly this value for admin v2 — the short form (`pbgen.cpp_namespace`, not the FQN `redpanda.core.pbgen.cpp_namespace`) is what's used in the tree.\n- Import `rpc.proto` only when the file defines a `service`.\n\nService / RPC block:\n\n```proto\nservice MyService {\n    // One short paragraph explaining what the RPC does and any\n    // async/poll behavior the caller needs to know.\n    rpc MyMethod(MyMethodRequest) returns (MyMethodResponse) {\n        option (pbgen.rpc) = {\n            authz: SUPERUSER\n        };\n    }\n}\n\nmessage MyMethodRequest {}\nmessage MyMethodResponse {}\n```\n\n- Every RPC must carry `option (pbgen.rpc) = { authz: ... }`. Default to `SUPERUSER` for admin v2 — anything else (`USER`, `PUBLIC`) needs core-team approval (see `proto/redpanda/core/pbgen/rpc.proto`).\n- Optional `http_route: \"/v2/...\"` adds an alias HTTP path. The auto-generated `/<package>.<Service>/<Method>` route always works without it.\n- Naming: messages are `<RpcName>Request` / `<RpcName>Response`, one pair per RPC. Empty messages are fine.\n\nField-level options worth knowing (full reference in `proto/redpanda/core/pbgen/options.proto`):\n\n- `[(pbgen.iobuf) = true]` on `string` fields that may exceed 128 KiB — generates `iobuf` instead of `ss::sstring`. `bytes` is already iobuf-backed.\n- `[(pbgen.ptr) = true]` on message fields for indirection (recursive types or when you need to distinguish unset).\n- `[debug_redact = true]` on sensitive fields — suppresses them from `fmt::formatter` output (passwords, tokens, etc.).\n\nWell-known types (`google.protobuf.Duration`, `Timestamp`, `FieldMask`) are supported; importing them requires the corresponding BUILD deps in step 2.\n\n### 2. Wire up the Bazel build\n\nEach `.proto` needs **two stacked targets** in `proto/redpanda/core/admin/v2/BUILD`. Append (alphabetical order in the file):\n\n```python\nproto_library(\n    name = \"myservice_proto\",\n    srcs = [\"myservice.proto\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//proto/redpanda/core/pbgen:options_proto\",\n        \"//proto/redpanda/core/pbgen:rpc_proto\",\n        # Add per-import:\n        # \"@protobuf//:timestamp_proto\",\n        # \"@protobuf//:duration_proto\",\n        # \"@protobuf//:field_mask_proto\",\n        # \"@googleapis//google/api:field_info_proto\",\n        # \"@googleapis//google/api:field_behavior_proto\",\n        # \"@googleapis//google/api:resource_proto\",\n        # Cross-proto imports: e.g. \":kafka_connections_proto\"\n    ],\n)\n\nredpanda_proto_library(\n    name = \"myservice_redpanda_proto\",\n    protos = [\":myservice_proto\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        # Add when the proto uses Timestamp or Duration:\n        # \"@abseil-cpp//absl/time:time\",\n        # Add when the proto uses FieldMask:\n        # \"//src/v/serde/protobuf:field_mask\",\n        # For cross-proto deps the *_redpanda_proto sibling goes here:\n        # \":kafka_connections_redpanda_proto\"\n    ],\n)\n```\n\nRules:\n\n- `proto_library.deps` mirrors `import` statements in the `.proto` file.\n- `redpanda_proto_library.deps` carries C++ deps the *generated* code needs (e.g. abseil for timestamps), plus any sibling `_redpanda_proto` targets for cross-proto imports.\n- The pbgen plugin lives at `bazel/pbgen/`; it generates `<name>.proto.h` and `<name>.proto.cc` with the message types, formatters, and service stubs.\n\n### 3. Implement the C++ service\n\nIf extending an existing service (just adding a new RPC), open the matching `src/v/redpanda/admin/services/<name>.{h,cc}` and add the new handler. Skip to step 4 after.\n\nFor a brand-new service, create `src/v/redpanda/admin/services/<name>.h`:\n\n```cpp\n/*\n * Copyright 2026 Redpanda Data, Inc.\n * (BSL header — copy from features.h:1-10)\n */\n#pragma once\n\n#include \"cluster/fwd.h\"\n#include \"proto/redpanda/core/admin/v2/<name>.proto.h\"\n#include \"redpanda/admin/proxy/client.h\"\n\nnamespace admin {\n\nclass <name>_service_impl : public proto::admin::<name>_service {\npublic:\n    <name>_service_impl(\n      admin::proxy::client,\n      /* collaborators — e.g. cluster::controller*, ss::sharded<X>& */);\n\n    ss::future<proto::admin::<rpc>_response> <rpc>(\n      serde::pb::rpc::context, proto::admin::<rpc>_request) override;\n\nprivate:\n    admin::proxy::client _proxy_client;\n    /* members for collaborators */\n};\n\n} // namespace admin\n```\n\nNaming conventions (CamelCase → snake_case translation):\n\n- Service base class: `proto::admin::<service>_service` (e.g. `FeaturesService` → `features_service`)\n- Generated client (for inter-node proxying): `proto::admin::<service>_service_client`\n- Request / response types: `proto::admin::<rpc_name>_request` / `_response`\n- Handler method name: snake_case of the RPC name (e.g. `FinalizeUpgrade` → `finalize_upgrade`)\n- Implementation class: `admin::<service>_service_impl`, in namespace `admin` (NOT `proto::admin`)\n\nHandler signature is fixed: `ss::future<...response>` return type, `serde::pb::rpc::context` first arg, request second, `override`.\n\nThen `src/v/redpanda/admin/services/<name>.cc`:\n\n```cpp\n#include \"redpanda/admin/services/<name>.h\"\n#include \"redpanda/admin/services/utils.h\"\n#include \"serde/protobuf/rpc.h\"\n#include <seastar/core/coroutine.hh>\n\nnamespace admin {\nnamespace {\nss::logger <name>log{\"admin_api_server/<name>_service\"};\n} // namespace\n\n<name>_service_impl::<name>_service_impl(/* args */) : _proxy_client(...) {}\n\nss::future<proto::admin::<rpc>_response>\n<name>_service_impl::<rpc>(\n  serde::pb::rpc::context ctx, proto::admin::<rpc>_request req) {\n    vlog(<name>log.trace, \"<rpc>: {}\", req);\n    // ... validate, optionally redirect-to-leader, do work ...\n    co_return proto::admin::<rpc>_response{};\n}\n\n} // namespace admin\n```\n\nPatterns to know — read the existing service files for live examples:\n\n- **Validation failures throw typed exceptions** declared in `src/v/serde/protobuf/rpc.h:241-287`: `invalid_argument_exception`, `failed_precondition_exception`, `not_found_exception`, `already_exists_exception`, `permission_denied_exception`, `unavailable_exception`, `unauthenticated_exception`, etc. Each maps to a ConnectRPC/gRPC status code. Example: `features.cc:46`.\n- **Leader redirect**: `admin::utils::redirect_to_leader(...)` from `services/utils.h` returns an optional `node_id`; if set, proxy the call to that node using the generated `_service_client`. Pattern in `features.cc:51-64`.\n- **Proxying to peers**: `_proxy_client.make_client_for_node<proto::admin::<service>_service_client>(node)` for one target, or `.make_clients_for_other_nodes<...>()` for scatter-gather. Defined in `redpanda/admin/proxy/client.h`.\n- **License gating**: `admin::utils::check_license(_feature_table.local())` if the RPC is enterprise-gated. See `cluster.cc:51`.\n- **Avoid proxy loops** in scatter-gather handlers: gate with `proxy::is_proxied(ctx)` (example in `cluster.cc:56`).\n\nAdd the BUILD entry to `src/v/redpanda/admin/services/BUILD`:\n\n```python\nredpanda_cc_library(\n    name = \"<name>\",\n    srcs = [\"<name>.cc\"],\n    hdrs = [\"<name>.h\"],\n    implementation_deps = [\n        \":utils\",\n        # .cc-only collaborators\n    ],\n    deps = [\n        \"//proto/redpanda/core/admin/v2:<name>_redpanda_proto\",\n        \"//src/v/cluster:fwd\",\n        \"//src/v/redpanda/admin/proxy:client\",\n        \"//src/v/serde/protobuf:rpc\",\n        \"@seastar\",\n        # types that appear in <name>.h\n    ],\n)\n```\n\nRule: types that appear in the `.h` belong in `deps`; collaborators only used in the `.cc` belong in `implementation_deps`.\n\n### 4. Register the service\n\nTwo edits in `src/v/redpanda/`:\n\n**4a. `src/v/redpanda/BUILD`** — add the new services library to the top-level `redpanda` library's `deps` list (kept sorted):\n\n```python\n\"//src/v/redpanda/admin/services:<name>\",\n```\n\n**4b. `src/v/redpanda/application_admin.cc`** — two changes:\n\n1. Include the header (alphabetical with the other service includes):\n   ```cpp\n   #include \"redpanda/admin/services/<name>.h\"\n   ```\n2. Inside `_admin.invoke_on_all([this, node_id](admin_server& s) { ... })`, append an `add_service` call. `create_client()` is the local lambda defined at the top of the block; pass other collaborators via `controller.get()` or `std::ref(...)`:\n   ```cpp\n   s.add_service(\n     std::make_unique<admin::<name>_service_impl>(\n       create_client(), /* collaborators */));\n   ```\n\nThat's it for registration. `add_service` walks the service's routes and registers an HTTP POST handler per route at `route.path` with the auth wrapper keyed on `route.authz_level`.\n\n### 5. Build and format\n\n```bash\nbazel build //proto/redpanda/core/admin/v2:<name>_redpanda_proto\nbazel build //src/v/redpanda/admin/services:<name>\nbazel build //src/v/redpanda:application\nbazel run //tools:clang_format\n```\n\nFix compilation errors before moving on. The pbgen plugin's error messages are usually self-explanatory; common causes are missing BUILD deps from step 2 or imports in the `.proto` without matching `proto_library.deps`.\n\n### 6. Regenerate ducktape Python bindings\n\nDucktape tests consume admin v2 via committed-to-repo Python bindings under `tests/rptest/clients/admin/proto/`. They are regenerated by a script — CI (`.github/workflows/check-ducktape-protos.yml`) fails the PR if they're stale, so this step is mandatory whenever a `.proto` under `proto/redpanda/core/admin/**` changes.\n\nPrerequisite: `uv` installed (https://docs.astral.sh/uv/getting-started/installation/). The script aborts otherwise.\n\n```bash\nbazel fetch //proto/...          # one-time; populates the googleapis external dir\ntools/regenerate_ducktape_protos.sh\n```\n\nThe script wipes `tests/rptest/clients/admin/proto/**/*.py{,i}` and regenerates from the proto glob using `grpc_tools.protoc` + `mypy-protobuf` + `connect-python`, then runs `protoletariat` to rewrite imports to relative form. The glob picks up new `.proto` files automatically — **no manifest to update**.\n\nCommit the generated files. `.gitignore` does not exclude them.\n\n### 7. Add a ducktape client accessor (only for new services)\n\nIf you created a brand-new service, expose it from the `Admin` wrapper at `tests/rptest/clients/admin/v2.py`. Two edits:\n\n1. Add imports near the top (line ~9), alongside the existing service imports:\n   ```python\n   from rptest.clients.admin.proto.redpanda.core.admin.v2 import (\n       <name>_pb2,\n       <name>_pb2_connect,\n       # ... existing\n   )\n   ```\n   And add a convenience re-export (`<name>_pb = <name>_pb2`) in the re-export block (around line ~52).\n\n2. Add an accessor method on `Admin` matching the existing pattern (line ~133):\n   ```python\n   def <name>(self, **kwargs: Any) -> <name>_pb2_connect.<Name>ServiceClient:\n       return self._make_service(<name>_pb2_connect.<Name>ServiceClient, **kwargs)\n   ```\n\nExisting services (`broker`, `cluster`, `features`, `security`, `shadow_link`, `datalake`, `debug`, `breakglass`, `metastore`, `l0`, `internal_shadow_link`) show the shape. If you only added a new RPC to an existing service, no edit to `v2.py` is needed — `_make_service` returns the generated client which exposes every RPC as a method.\n\nA ducktape test uses the new RPC like this:\n\n```python\nfrom rptest.clients.admin.v2 import Admin as AdminV2, <name>_pb\n\nadmin_v2 = AdminV2(self.redpanda,\n                   auth=(self.superuser.username, self.superuser.password))\nreq = <name>_pb.<Rpc>Request(...)\nresp = admin_v2.<name>().<rpc>(req)\n```\n\n## Verification\n\nAfter all edits:\n\n```bash\nbazel build //...\nbazel run //tools:clang_format\ntools/regenerate_ducktape_protos.sh   # idempotent — confirms generated tree matches\ngit status                            # should show all expected files staged\n```\n\nManual smoke (optional, but the canonical way to confirm wiring):\n\n1. `bazel build //src/v/redpanda:application` or a debug-binary target.\n2. Start a local cluster; invoke the RPC via the ducktape `AdminV2` client, or `curl` against `/redpanda.core.admin.v2.<Service>/<Method>` with a protobuf-encoded body.\n\n## Out of scope\n\n**Go bindings for `rpk` consumption.** Admin v2 Go bindings are not generated in this repo — they're pulled from the Buf Schema Registry as a Go module (`buf.build/gen/go/redpandadata/core/...`). CI publishes to BSR automatically on merge to `dev` or a `v*` branch (see `.github/workflows/buf.yml`). Consuming a new RPC from `rpk` is a follow-up PR after the proto change has merged and the BSR push has run — out of scope for this skill.\n\nIf the user mentions needing `rpk` to consume the new RPC, surface this as a separate follow-up task rather than attempting it in the same change.\n\n## Rules and pitfalls\n\n- **Auth level defaults to `SUPERUSER`.** Don't use `USER` or `PUBLIC` without explicit approval — the proto README is clear on this.\n- **The proto's `option (pbgen.cpp_namespace)` is always `\"proto::admin\"` for admin v2.** Don't invent a per-service namespace.\n- **`add_service` registration order matters only for readability.** The existing block in `application_admin.cc` is not strictly sorted; match its grouping (regular services, then `if (cloud_topics_app)` services, then internal/security at the end).\n- **`bazel fetch //proto/...` is a prerequisite for `regenerate_ducktape_protos.sh`** — the script needs the googleapis external dir to exist. The script's error message is clear, but the failure mode (script aborts with \"googleapis directory not found\") is easy to misdiagnose if you skip it.\n- **CI enforces generated-file freshness.** `check-ducktape-protos` will fail if you forget step 6. The fix is always: re-run the script and commit the diff.\n- **Don't add comments that restate the code.** RPC docstrings in the `.proto` are valuable (they become the public contract); duplicating them in the C++ handler isn't.\n- **The handler signature `ss::future<R> name(serde::pb::rpc::context, Request)` is fixed** — context first, request second, both by value, return future. Deviating produces inscrutable pbgen-template errors.\n- **Validation errors must throw the typed exceptions from `src/v/serde/protobuf/rpc.h`.** Returning a default response, returning an `errc`, or throwing `std::runtime_error` will not surface a proper ConnectRPC status to the caller.\n\n<!-- File: .claude/skills/create-backport-branch/evals/evals.json (Tokens: ~919 | Category: anthropic-skill) -->\n{\n  \"skill_name\": \"create-backport-branch\",\n  \"evals\": [\n    {\n      \"id\": 0,\n      \"name\": \"clean-cherry-pick\",\n      \"prompt\": \"/create-backport-branch 81d9e1af33 30160\",\n      \"expected_output\": \"Creates a backport branch from commit 81d9e1af33 (v25.3.x before PR 30160 was backported). The single commit from PR #30160 should cherry-pick cleanly with no conflicts and no empty result. Produces a structured report.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Branch was created with naming pattern ai-backport-pr-30160-81d9e1af33-{timestamp}\",\n        \"Cherry-pick command used the -x flag to record the source commit\",\n        \"Cherry-pick succeeded cleanly with no conflicts\",\n        \"Final report includes total commits count (1), conflicts resolved count (0), and commits skipped count (0)\",\n        \"Final report includes the backport branch name\",\n        \"The backport branch has exactly 1 new commit beyond the base\"\n      ]\n    },\n    {\n      \"id\": 1,\n      \"name\": \"multi-commit-conflict\",\n      \"prompt\": \"/create-backport-branch 5cdd00f4ca 30089\",\n      \"expected_output\": \"Creates a backport branch from commit 5cdd00f4ca (v26.1.x). Cherry-picks 8 commits from PR #30089. Should encounter conflict in feature_table.h and resolve it by adapting to the target branch's release_version values.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Branch was created with naming pattern ai-backport-pr-30089-5cdd00f4ca-{timestamp}\",\n        \"All 8 commits were fetched from the PR via gh api\",\n        \"Cherry-pick command used the -x flag\",\n        \"Either all conflicts were resolved and continued, OR agent aborted cleanly listing the specific commit SHA and conflicted files\",\n        \"If conflicts were resolved, the final report includes a per-conflict one-line explanation\",\n        \"No conflict markers (<<<<<<< or ======= or >>>>>>>) remain in any resolved files\"\n      ]\n    },\n    {\n      \"id\": 2,\n      \"name\": \"generated-files-modify-delete\",\n      \"prompt\": \"/create-backport-branch d0a6248434 30115\",\n      \"expected_output\": \"Creates a backport branch from commit d0a6248434 (v25.1.x). Cherry-picks 3 commits from PR #30115. Should encounter modify/delete conflict on buf.gen.yaml and abort cleanly per skill rules.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Branch was created with naming pattern ai-backport-pr-30115-d0a6248434-{timestamp}\",\n        \"Generated files (go.sum, MODULE.bazel, or similar) were identified\",\n        \"Cherry-pick command used the -x flag\",\n        \"Agent aborted cleanly on modify/delete conflict, listing the specific commit SHA and conflicted files\",\n        \"Abort report includes the exact cherry-pick command that was being attempted\",\n        \"Final report includes total commits count, conflicts resolved count, and commits skipped count\"\n      ]\n    },\n    {\n      \"id\": 3,\n      \"name\": \"already-present-empty\",\n      \"prompt\": \"/create-backport-branch ea6e2533c7 30160\",\n      \"expected_output\": \"Creates a backport branch from commit ea6e2533c7 (v25.3.x after PR 30160 was already backported). The cherry-pick should result in an empty commit since the change is already present. Should handle with git cherry-pick --skip and report as skipped.\",\n      \"files\": [],\n      \"expectations\": [\n        \"Branch was created with naming pattern ai-backport-pr-30160-ea6e2533c7-{timestamp}\",\n        \"Cherry-pick command used the -x flag\",\n        \"Empty cherry-pick was handled with git cherry-pick --skip (not --abort)\",\n        \"Final report includes commits skipped count of 1\",\n        \"Final report correctly indicates the commit was already on the target branch\",\n        \"The backport branch was preserved (not deleted)\"\n      ]\n    }\n  ]\n}\n\n<!-- File: .claude/skills/create-backport-branch/SKILL.md (Tokens: ~2180 | Category: anthropic-skill) -->\n---\nname: create-backport-branch\ndescription: \"Create a new backport branch with prefix `ai-backport-` by cherry-picking all commits from a GitHub PR onto a target release branch or commit. Use when the user wants to backport a PR, create a backport branch, cherry-pick PR commits to a release branch, or mentions backporting changes from dev/main to a version branch (e.g. v25.1.x, v25.2.x). Requires two arguments: TARGET (the release branch or commit hash to backport onto) and PR_NUM (the GitHub PR number to backport).\"\nargument-hint: [target-branch-or-commit] [pr-number]\nallowed-tools: Bash(git *), Bash(gh *), Bash(date *), Bash(grep *), Bash(wc *), Read, Write, Edit, Glob, Grep, Agent\nmodel: opus[1m]\neffort: high\n---\n\n# Create Backport Branch\n\nCherry-pick all commits from a GitHub PR onto a target release branch, resolving conflicts along the way.\n\nArguments:\n- `$0` is target — a release branch (e.g. `v25.1.x`) or a commit hash (e.g. `81d9e1af33`)\n- `$1` is pr-number (e.g. `12345`)\n\n## Procedure\n\n### 1. Validate inputs and set up\n\n1. Both arguments must be provided. If either target-branch or pr-number is empty, abort with: \"Usage: /create-backport-branch <target-branch> <pr-number>\"\n2. Generate the backport-branch-name using the current Unix timestamp:\n   ```bash\n   date +%s\n   ```\n   backport-branch-name format: `ai-backport-pr-${pr_number}-${target_branch}-${unix_timestamp}`\n\n### 2. Create the backport branch\n\n```bash\ngit checkout -b $backport_branch_name $target_branch\n```\n\n### 3. Fetch PR commits\n\nGet the list of commit SHAs from the PR:\n\n```bash\ngh api \"repos/{owner}/{repo}/pulls/$pr_number/commits\" --paginate --jq '[.[].sha[0:10]]|join(\" \")'\n```\n\n`gh` resolves the `{owner}` and `{repo}` placeholders from the git remote of the current directory (see [`gh api` docs](https://cli.github.com/manual/gh_api)). Run this from a checkout of the repo that owns the PR.\n\nSave the full list of commits as `git_commits` and report how many commits were found.\n\n### 4. Cherry-pick commits\n\nRun the cherry-pick for all commits at once:\n\n```bash\ngit cherry-pick -x $git_commits\n```\n\nTrack these counters as you go:\n- **total_commits**: number of commits from the PR\n- **conflicts_resolved**: number of commits that needed conflict resolution\n- **commits_skipped**: number of commits skipped because they were already present on the target branch\n- **generated_files_touched**: list of generated files that were part of the cherry-picked commits (whether conflicted or not)\n\nIf the cherry-pick succeeds cleanly, check if any generated files were touched (see step 4a), then skip to step 6.\n\n### 4a. Check for generated files in cherry-picked commits\n\nAfter each successful cherry-pick (clean or after conflict resolution), check if any generated files were touched. Use the same generated file patterns from step 5.3. For clean cherry-picks, check the files changed in the commit(s) just applied:\n\n```bash\ngit diff --name-only HEAD~1 HEAD\n```\n\nMatch the output against the generated file patterns. Add any matches to `generated_files_touched`.\n\n### 5. Resolve conflicts\n\nIf the cherry-pick fails with merge conflicts, attempt resolution. For each conflicted state:\n\n1. Identify the commit that caused the conflict (the one currently being cherry-picked).\n2. List conflicted files: `git diff --name-only --diff-filter=U`\n3. Categorize each file:\n   - **Generated files** (accept theirs and move on): `*.pb.go`, `*.pb.h`, `*.pb.cc`, `*.pb.rs`, `*_pb2.py`, `go.sum`, `*.lock`, `MODULE.bazel.lock`, `package-lock.json`, `Cargo.lock`, `MODULE.bazel`\n     - For generated files: `git checkout --theirs -- $FILE && git add -- $FILE`\n     - Add each generated file to the `generated_files_touched` list\n   - **Modify/delete conflicts**: file deleted on one side but modified on the other. These require human judgment — **abort immediately** (see abort procedure below).\n   - **Content conflicts**: files with `<<<<<<<` / `=======` / `>>>>>>>` markers. Attempt resolution.\n\n4. For each content conflict:\n   a. Read the full conflicted file\n   b. Run `git log -p -1 $FAILING_COMMIT -- $FILE` to understand what the cherry-picked commit intended\n   c. Understand both sides:\n      - **HEAD side** (`<<<<<<<` to `=======`): what the target release branch has\n      - **Incoming side** (`=======` to `>>>>>>>`): what the cherry-picked commit brings\n   d. Read surrounding code if needed to understand context (imports, types, function signatures)\n   e. Determine the correct resolution:\n      - The goal is to apply the *intent* of the cherry-picked commit to the target branch\n      - Adapt to the target branch's patterns, names, and APIs where they diverge\n   f. Edit the file to remove all conflict markers and write the correct merged code\n   g. Stage: `git add -- $FILE`\n\n5. Verify the resolution is clean:\n   ```bash\n   grep -rn '<<<<<<< \\|=======$\\|>>>>>>> ' -- $RESOLVED_FILES\n   git diff --name-only --diff-filter=U\n   ```\n   Both commands should produce no output. If unmerged paths remain, something was missed.\n\n6. Continue the cherry-pick:\n   ```bash\n   git cherry-pick --continue --no-edit\n   ```\n   If `--continue` reports \"The previous cherry-pick is now empty\", the commit's changes are already on the target branch. Run `git cherry-pick --skip` to move on, and increment `commits_skipped`.\n\n7. Increment `conflicts_resolved` counter.\n\n8. If the next commit also conflicts, repeat from step 5.1.\n\n### 5a. Empty cherry-picks (no conflict)\n\nA cherry-pick can also be empty without any conflict — git will report \"The previous cherry-pick is now empty\" immediately. This happens when the commit's changes already exist on the target branch (e.g., from a prior backport or parallel fix). Run `git cherry-pick --skip` to move on and increment `commits_skipped`. This is normal and not an error.\n\n**Abort procedure** — If at any point you are uncertain how to resolve a conflict (ambiguous intent, modify/delete conflicts, architectural changes you don't understand, or anything that feels risky):\n\n1. Run `git cherry-pick --abort`\n2. Report an error with:\n   - The exact `git cherry-pick -x ...` command that was being attempted\n   - The specific commit SHA that caused the uncertain conflict\n   - Which files were conflicted and why you couldn't resolve them\n3. Stop immediately. Do NOT attempt further cherry-picks.\n\n### 6. Final report\n\nAfter all commits are cherry-picked successfully, resolve the source-PR URL dynamically:\n\n```bash\npr_url=$(gh pr view $pr_number --json url --jq .url)\n```\n\nThen emit this report to stdout:\n\n```\nBackport of PR ${pr_url}\n\n- Command: git cherry-pick -x ${git_commits}\n- Commits backported: ${total_commits}\n- Conflicts resolved: ${conflicts_resolved}\n- Commits skipped (already on target): ${commits_skipped}\n- Backport branch: ${backport_branch_name}\n\n## Conflict details\n\n- ${commit_sha} (${file}): one-line explanation of what conflicted and how it was resolved\n```\n\nFor each conflict resolved, include a one-line explanation covering what conflicted and how you resolved it. This helps PR reviewers understand the backport without reading every diff.\n\nIf `generated_files_touched` is non-empty, append this warning section:\n\n```\n## ⚠️ Generated files\n\nThe following files were cherry-picked and may need regeneration:\n\n- ${file_1}\n- ${file_2}\n\nThese files were accepted as-is from the source branch. Before merging,\nregenerate them on the target branch to ensure they're correct. For example:\n- MODULE.bazel.lock: run `bazel mod deps --lockfile_mode=update`\n- *.pb.go / *.pb.cc / *.pb.h: rebuild protobuf targets\n- go.sum: run `go mod tidy`\n```\n\n**Optional: machine-readable report.** If the `SKILL_REPORT_FILE` env var is set (callers like GitHub Actions workflows use this), write the exact same report text to that file path. The file content is designed to be usable verbatim as the body of the backport PR. Humans invoking the skill directly do not set this variable and simply read the report on stdout.\n\n## Rules\n\n- NEVER guess at conflict resolutions. If uncertain, abort immediately using the abort procedure.\n- NEVER silently drop code. Incoming changes that add new functionality must be preserved (adapted to the target branch's conventions).\n- Do NOT add comments to resolved code unless the original commit had them.\n- Preserve the formatting style of the target branch.\n- For generated files (protobuf, lockfiles, MODULE.bazel), accept theirs during the cherry-pick to unblock it. Two follow-ups are then required:\n  - List each touched generated file under the \"Generated files\" warning section in the PR body so the human reviewer regenerates and commits the correct version before merge.\n  - Do not describe these files as \"regenerated by the build\" in conflict-detail lines. Bazel/protoc/etc. update them in the local workspace only; CI does not push a corrected version back to the PR, so any regeneration must be committed manually.\n\n<!-- File: .claude/skills/improve-coverage/SKILL.md (Tokens: ~780 | Category: anthropic-skill) -->\n---\nname: improve-coverage\ndescription: Improve code coverage for a Bazel C++ test target or source file\nuser-invocable: true\n---\n\n# Improve Code Coverage\n\nImprove test coverage for a Bazel C++ test target or source file path.\n\n## Input Resolution\n\n1. **Bazel test target** (starts with `//`, e.g. `//src/v/bytes/tests:iobuf_test`):\n   use it directly.\n\n2. **Source file path** (e.g. `src/v/bytes/iobuf.cc`): find the test target:\n   ```bash\n   bazel query \"kind('.*_test', rdeps(//src/v/..., //$(dirname $FILE):$(basename $FILE .cc), 1))\" 2>/dev/null\n   ```\n   If multiple targets match, pick the one in the closest `tests/` directory.\n   If none match, tell the user and stop.\n\n## Workflow\n\n### Step 1: Baseline Coverage\n\nRun coverage and read the LLM report:\n\n```bash\ntools/run-cov <target> -r llm\n```\n\nNote the **total line coverage percentage** — this is the baseline to beat.\n\nRead the report and identify:\n- **Uncovered functions** — highest impact. One test can cover many lines.\n- **Partially covered functions** — uncovered branches indicate missing\n  error-path or edge-case tests.\n- **Zero-coverage files in scope** — may need new test cases entirely.\n\n### Step 2: Understand the Code\n\nRead the source files named in the report. Focus on:\n- Uncovered functions: what they do, inputs, preconditions.\n- The existing test file: patterns, fixtures, helpers.\n\nKey codebase patterns:\n- `redpanda_cc_gtest` targets use GTest (`TEST()`, `TEST_F()`).\n  `redpanda_cc_btest` targets use Seastar Boost test\n  (`SEASTAR_THREAD_TEST_CASE`). Match whichever the test file uses.\n- Many functions are `ss::future<>` coroutines — test with\n  `SEASTAR_THREAD_TEST_CASE` or the Seastar GTest runner.\n- Use `ss` namespace prefix for Seastar types.\n- Use `EXPECT_*` for most checks, `ASSERT_*` only when continuing would crash.\n\n### Step 3: Write Tests\n\nPrioritize by impact:\n1. **Uncovered functions** — call each one. A 20-line function = 20 lines covered.\n2. **Uncovered error paths** — trigger the uncovered branches in partially\n   covered functions.\n3. **Zero-coverage files** — add smoke tests if practical.\n\nRules:\n- Add tests to the **existing test file**. Don't create new files or BUILD\n  targets unless necessary.\n- Follow existing naming conventions in the file.\n- One logical behavior per test case.\n- Prefer deterministic tests — construct error conditions directly.\n- Do NOT add comments that restate the code.\n\n### Step 4: Verify Build\n\n```bash\nbazel build <target>\n```\n\nFix compilation errors before proceeding.\n\n### Step 5: Verify Coverage\n\nRe-run coverage:\n\n```bash\ntools/run-cov <target> -r llm\n```\n\nCompare to the baseline. Report:\n\n```\nCoverage: X.Y% → A.B% (+N.N%)\nNew lines covered: <count>\n```\n\nIf coverage did not improve, investigate:\n- Build errors preventing the new tests from running?\n- Code compiled out by preprocessor guards?\n- Template instantiations not triggered by test types?\n\n### Step 6: Summary\n\nReport:\n- Which functions/paths are now covered\n- What test cases were added\n- Before/after coverage numbers\n- Remaining significant gaps and suggestions\n\n<!-- File: bazel/clang_tidy/plugins/README.md (Tokens: ~277 | Category: plugin-manifest) -->\n# Redpanda clang-tidy plugins\n\nThis directory contains sources and bazel targets for custom out-of-tree clang-tidy checks.\nIt contains a single `redpanda_cc_binary` target, `plugins.so`, which contains a single clang-tidy\nmodule, `redpanda-module`.\n\n## Building\n\n```sh\n$ bazel build //bazel/clang_tidy/plugins:plugins.so\n```\n\n## Adding a new check\n\n1. Write your check in a separate file or files. e.g. `plugins/redpanda_noop_check.h`.\n2. Add your sources directly to `plugins.so.srcs` including any new dependencies.\n3. Register the check by name in `plugins/redpanda_tidy_module.h`.\n   - The name should start with \"redpanda-\".\n\nChecks introduced in this way are automatically loaded in `bazel build --config=clang-tidy //...`.\n\nIf you want to try your check on some source file in isolation, you can invoke the tool directly:\n\n```sh\n$ bazel build //bazel/clang_tidy/plugins:plugins.so\n$ ./tools/clang-tidy --checks=-*,redpanda-my-fancy-check -list-checks\n$ ./tools/clang-tidy --checks=-*,redpanda-my-fancy-check /path/to/some/foo.cc\n```\n\n<!-- File: tools/docker/README.md (Tokens: ~24 | Category: plugin-manifest) -->\n# Docker Image with Redpanda Toolchain\n\nCI bazel toolchain image (everything CI runs in this image).\n\n<!-- File: tools/go-comment-pbgen/README.md (Tokens: ~502 | Category: plugin-manifest) -->\n# Go Protobuf Comment Generator\n\nCustom `protoc` plugin that extracts proto comments and makes them available at runtime for `rpk shadow config generate-template`.\n\n## Overview\n\nGenerates `comments.pb.go` files containing a simple `map[string]string` of proto field comments keyed by descriptor full names. Proto `.pb.go` files come from `buf.build/gen/go/redpandadata/core`.\n\n\n## Usage\n\nGenerate comment files:\n```bash\nbuf generate --path proto\n```\n\nAccess comments in code:\n```go\nimport v2comments \"github.com/redpanda-data/redpanda/src/go/rpk/gen/protocomments/admin/v2\"\n\ncomment := v2comments.GetCommentForField(fieldDescriptor)\n// Or by full name:\ncomment = v2comments.GetComment(\"redpanda.core.admin.v2.ShadowLink.uuid\")\n```\n\n## Generated Code\n\nEach package gets one `comments.pb.go`:\n```go\npackage v2\n\n// Comments maps descriptor full names to their leading comments\nvar Comments = map[string]string{\n    \"redpanda.core.admin.v2.ShadowLink.uuid\": \"The UUID of the shadow link\",\n    \"redpanda.core.admin.v2.ShadowLink.name\": `The name of the shadow link\nThis is a multi-line comment`,\n}\n\nfunc GetComment(fullName string) string\nfunc GetCommentForField(fd protoreflect.FieldDescriptor) string\nfunc GetCommentForMessage(md protoreflect.MessageDescriptor) string\nfunc GetCommentForEnum(ed protoreflect.EnumDescriptor) string\nfunc GetCommentForEnumValue(evd protoreflect.EnumValueDescriptor) string\n```\n\n**Multi-line comments**: Use raw string literals (backticks) to preserve actual newlines instead of `\\n` escape sequences.\n\n## Output Structure\n\n```\nsrc/go/rpk/gen/protocomments/\n├── admin/v2/comments.pb.go\n└── common/v1/comments.pb.go\n```\n\n## Development\n\nModify plugin:\n```bash\n# 1. Edit generator.go\n# 2. Regenerate\nbuf generate --path \"proto/redpanda/core/admin/v2/shadow_link.proto\",\"proto/redpanda/core/common/v1/acl.proto\",\"proto/redpanda/core/common/v1/tls.proto\"\n# 3. Test\ncd src/go/rpk && go build ./...\n```\n\nCI checks that generated files are up-to-date in `.github/workflows/rpk-build.yml`.\n\n<!-- File: tools/metrics-mock/README.md (Tokens: ~536 | Category: plugin-manifest) -->\n# Metrics Mock\n\nmetrics-mock.py is a script that starts an http server and serves\nredpanda-like metrics from /metrics and /public-metrics endpoints.\n\nSo it serves as a stand-in for Redpanda when testing things in the\nmetrics pipeline, as using a large clusters simply to generate large\nnumbers of metrics is wasteful.\n\n## Usage\n\nThe script has no dependencies and should be directly executable on\nPython 3.10 or later.\n\nThe primary functionality of metrics mock is accessble through `metrics-mock.py host`,\nwhich starts the HTTP server on port `9644` (configurable with `--port`):\n\n```\n./metrics-mock host\n```\n\nOn another terminal run:\n\n```\n$ curl -sL 'http://127.0.0.1:9644/metrics' | head\n```\n\nwhich should produce output like:\n\n```\n# HELP vectorized_alien_receive_batch_queue_length Current receive batch queue length\n# TYPE vectorized_alien_receive_batch_queue_length gauge\nvectorized_alien_receive_batch_queue_length{shard=\"0\"} 5923\n# HELP vectorized_alien_total_received_messages Total number of received messages\n# TYPE vectorized_alien_total_received_messages counter\nvectorized_alien_total_received_messages{shard=\"0\"} 1033\n# HELP vectorized_alien_total_sent_messages Total number of sent messages\n# TYPE vectorized_alien_total_sent_messages counter\nvectorized_alien_total_sent_messages{shard=\"0\"} 13440\n# HELP vectorized_application_build Redpanda build information\n```\n\nThere are several options to modify the behavior of the metrics endpoint,\nspecifically to emulate different sizes of Redpanda node and Redpanda cluster.\n\nE.g., to approximate a T7 cluster, use the following flags:\n\n\n```\n./metrics-mock.py print --partitions 50000 --topics 10 --shards 31 --nodes 9\n```\n\nEach `./metrics-mock.py` instance acts as a single node in a multi-node cluster:\nin the above example the mock will act as a single node in a 9 node cluster. This\nis important when deciding which metrics appear for resources which are allocated\nto a partitcular subset nodes, such as topic partitions. To emulate a differnet\nnode, pass `--node-id`. If you are emulating a multi-node cluster you should pass\na different node id to each mock, or else they all return the same metrics for\nper-topic, per-partition or per-group metrics, which will significantly underestimate\nthe total metrics count.\n\n<!-- File: tools/offline_log_viewer/README.md (Tokens: ~19 | Category: plugin-manifest) -->\nOffline tool to parse RedPanda disk logs.\nUsage: `python viewer.py -h`\n\n<!-- File: tools/p/README.md (Tokens: ~28 | Category: plugin-manifest) -->\nThe `p` exectuable script is a containerized drop-in replacement for `p`.\n\n```\ntools/p/p compile\n```\n\n<!-- File: tools/rp_storage_tool/README.md (Tokens: ~936 | Category: plugin-manifest) -->\n# rp-storage-tool\n\n## Purpose\n\nThis low level tool is for offline use by Redpanda Engineering when diagnosing\nfaults.\n\nThis tool is **not** for everyday use on live clusters.\n\n## Quickstart\n\n    # Get a rust toolchain\n    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n\n    # Compile and run\n    cargo run --release -- --backend=<aws|gcp|azure> scan --source=<bucket name>\n\n## Installation\n\nTo run the tool outside a built tree, you may simply copy the statically linked binary.\n\n    # Compile\n    cargo build --release\n\n    # Grab the binary\n    cp target/release/rp-storage-tool /usr/local/bin\n\n## Usage\n\n### Typical usage\n\nThe following examples assume you are running on an AWS instance with IAM roles configured\nfor access to your bucket. If you need to specify static keys, see the \"Working with local object stores\"\nsection below.\n\n    # Scan the bucket, store metadata in a file, and report any metadata anomalies\n    $ rp-storage-tool --backend aws scan-metadata --source=my-bucket --meta-file my-bucket.json\n\n    # Scan the data within a topic of interest, and report any anomalies.  Use the metadata\n    # file from the last step to avoid re-scanning all objects in the bucket.\n    $ rp-storage-tool --filter kafka/my-topic/*_* --backend aws scan-data --source=my-bucket --meta-file my-bucket.json\n\n    # Having identified an issue, extract the data from a partition for further analysis offline\n    $ mkdir my-data-dump\n    $ rp-storage-tool --filter kafka/my-topic/13_63 --backend aws extract --source=my-bucket --meta-file my-bucket.json --sink ./my-data-dump/\n\n### Generic parameters\n\n* The `--backend` argument selects the cloud storage backend. This has a default (AWS) for convenience when working with\n  commands that don't use cloud storage, but ordinarily you should be specifying it.\n* The `--filter` argument controls which topics & partitions will be examined. When scanning metadata we\n  always scan all objects, but will only store+analyze metadata for partitions matching the filter.\n* If you are running on a node with authentication already set up (e.g. IAM Roles on AWS), this will Just Work.\n  Otherwise you may need to set the appropriate access key/secret environment variables for the cloud platform you are\n  connecting to.\n\n### Subcommands\n\n#### `scan-metadata`\n\nThis walks the objects in a bucket used by Redpanda Tiered Storage and reports on any inconsistencies.\n**Not all issues this tool reports are harmful**, for example `segments_outside_manifest` may contain objects harmlessly\nleft behind when Redpanda was restarted during an upload.\n\n#### `scan-data`\n\nThis walks the data within segments (you probably want to use `--filter` to only scan segments for certain NTPS)\nto check that it is readable, and that the metadata in the partition manifest is consistent with the data.\n\n#### `extract`\n\nFor NTPs matching the filter, copy all metadata and data objects into a local folder.\n\n## Working with local object stores\n\nThis tool uses environment variables for any special configuration of storage backends required for working outside of\nreal cloud environments.\n\nFor example, to use the tool with a bucket called `data` in a minio cluster at `aplite:9000`:\n\n    AWS_ALLOW_HTTP=1 AWS_ACCESS_KEY_ID=minioadmin AWS_SECRET_ACCESS_KEY=minioadmin AWS_REGION=us-east-1 \\\n      AWS_ENDPOINT=http://aplite:9000 cargo run --release -- --backend aws scan --source data\n\n### Building a portable binary\n\nIf you are building on a workstation and copying the binary to a remote machine, and your\nworkstation doesn't happen to run the same linux distro as the remote machine, it is\nuseful to build a statically linked binary that will work on any distro.\n\n    # A dependency requires this to build: adjust as needed if not on an RPM distro\n    sudo dnf install -y musl-gcc\n\n    # Install the Rust toolchain for musl (statically linked libc)\n    rustup target add x86_64-unknown-linux-musl\n\n    # Build with the musl toolchain\n    cargo build --release --target=x86_64-unknown-linux-musl\n\n    # Output in target/release/rp-storage-tool\n\n<!-- File: tools/type-checking/README.md (Tokens: ~1857 | Category: plugin-manifest) -->\n# Ducktape Type Checking\n\nThis directory contains the infrastructure for running PEP-484 type checking on Redpanda's Python test suite (ducktape) using [pyright](https://github.com/microsoft/pyright).\n\n## Overview\n\nThe type checking system provides:\n- **Gradual type checking adoption** with per-file strictness levels\n- **Automated promotion detection** to identify files ready for stricter checking\n- **CI integration** to prevent type regressions\n- **Docker-based execution** for consistent environments\n\n## Files\n\n### `type-check.py`\n\nThe main type checking orchestration script. Provides multiple commands:\n\n- **`check`** - Run type checking on files and report errors\n- **`promotion-check`** - Identify files that can be promoted to stricter type checking levels\n- **`fruit`** - Show files with fewest errors at each target level (low-hanging fruit for type checking improvements)\n- **`ci`** - Run both `check` and `promotion-check` and decorates the results (used in CI)\n- **`check-sorted`** - Verify that `type-check-strictness.json` is sorted\n- **`pre-commit`** - Run `check` on changed files (intended for use as a pre-commit hook); accepts paths rooted at repo root (`tests/...`) and strips the prefix internally\n\n### `type-check-strictness.json`\n\nConfiguration file that maps Python files to their current type checking strictness level. Files not listed default to `strict` level.\n\n**Strictness levels** (from most to least strict):\n- `strict` - Full type checking (default for unlisted files)\n- `standard` - Standard type checking\n- `basic` - Basic type checking\n- `off` - Minimal checking (syntax, imports, etc.)\n- `skip` - No type checking at all\n\n### `Dockerfile`\nMulti-stage Docker image for running type checking in a consistent environment. Installs Python dependencies and pyright.\n\n### `type-check.sh`\nShell script that builds the Docker image and runs type checking inside a container.\n\n### `requirements.txt`\nPython dependencies for type checking (primarily pyright).\n\n## Usage\n\n### Local Development\n\n#### Using task\n\nThere is a type-check `task` which is a very thin wrapper around the scripts: this is probably the most convenient way to call it as it works\nanywhere in the repository. Call it with arguments for the script after `--`, like so:\n\n```\ntask rp:type-check -- --help\n```\n\n#### Using type-check.sh\n\nYou can also call the script as `tools/type-checking/type-check.sh --help`. This is essentially the same as the `task` invocation above. This runs the underlying python script inside a docker container, so it should \"just work\".\n\n#### Using type-check.py\n\nLocally you should also able to run the .py script directly, like `tools/type-checking/type-check.py --help`  if you have run [local_venv.sh](../../tests/local_venv.sh).\n\n### Examples\n\nThese examples all the `task` invocation method but work with any of the methods above.\n\n```bash\n# Run type checking on all files\ntask rp:type-check -- check\n\n# Check for files ready to be promoted to stricter levels\ntask rp:type-check -- promotion-check\n\n# Find low-hanging fruit - files with fewest errors at each target level\ntask rp:type-check -- fruit\n\n# Show top 25 fruit candidates\ntask rp:type-check -- fruit --top 25\n\n# Show detailed error information with verbose output\ntask rp:type-check -- fruit --verbose 2\n\n# Automatically promote eligible files\ntask rp:type-check -- promotion-check --update\n\n# Run CI checks (both check and promotion-check)\ntask rp:type-check -- ci\n\n# Check specific files (paths/globs are positional, relative to tests/)\ntask rp:type-check -- check rptest/tests/my_test.py\n\n# Force all files to be checked at a specific level\ntask rp:type-check -- check --force-level strict\n```\n\n## Strictness Level Management\n\nIn order to have type checking useful immediately, without needing to fix 1000s of existing type errors, the system uses a progressive approach to type checking adoption:\n\n1. **New files** default to `strict` type checking\n2. **Existing files** are assigned to the strictest level they currently pass\n3. **Promotion detection** automatically identifies when files can move to stricter levels\n4. **Automated updates** can promote files with `--update` flag\n\n### Adding Type Hints to Files\n\nWhen adding type hints to improve a file:\n\n1. Run `tools/type-checking/type-check.py promotion-check --update`\n2. This will automatically move the file to the strictest level it now passes\n3. Commit both your type hint changes and the updated `type-check-strictness.json`\n\n### Finding Low-Hanging Fruit\n\nThe `fruit` command helps identify files that are closest to being promotable to stricter type checking levels by showing files with the fewest errors at each target level:\n\n```bash\n# Show top 10 files with fewest errors per target level\ntask rp:type-check -- fruit\n\n# Show top 20 files with fewest errors per target level\ntask rp:type-check -- fruit --top 20\n\n# Show detailed error information with verbose output\ntask rp:type-check -- fruit --verbose 2\n```\n\nThis is useful for:\n- **Prioritizing type checking improvements** - start with files that need minimal fixes\n- **Setting realistic goals** - see how many errors need to be fixed for each promotion\n- **Understanding error distribution** - identify patterns in type checking issues\n\nThe command groups files by their potential promotion target (off→basic, basic→standard, etc.) and sorts them by error count, making it easy to find files that are closest to passing at the next strictness level.\n\n### Failed Promotion Check\n\nYou may be here because CI failed with a \"promotion check\" error. This means that the strictness level for some files are set too low, i.e., your change has improved the state of the Python code base and as a reward you can promote some files to higher strictness levels. Pat yourself on the back, then use the following command to automatically update the strictness file:\n\n```bash\n# Automatically update the configuration\ntask rp:type-check -- promotion-check --update\n\n# Commit the updated type-check-strictness.json\ngit add tools/type-checking/type-check-strictness.json\ngit commit -m \"chore: update type checking strictness\"\n```\n\n## Command Line Options\n\n- `--tests-root PATH` - Path to tests directory (auto-detected by default)\n- `--config PATH` - Path to pyrightconfig.json\n- `input_files` - Positional paths/globs of files to check, relative to `tests/` (default: `rptest/**/*.py`)\n- `--force-level LEVEL` - Override strictness levels and check all files at specified level\n- `--no-venv` - Don't use virtual environment when running pyright\n- `--verbose [LEVEL]` - Enable verbose output (0=off, 1=verbose, 2=very verbose)\n- `--update` - Update type-check-strictness.json in-place (for promotion-check)\n- `--top N` - Number of files to show per target level in fruit command (default: 10)\n\n## CI Integration\n\nThe type checking runs automatically on pull requests via GitHub Actions (`.github/workflows/python-type-check.yml`). The CI:\n\n1. Builds the Docker image from `Dockerfile`\n2. Runs `type-check.py ci` which executes both type checking and promotion detection\n3. Fails if there are type errors or files that should be promoted\n\nThis check tries to be as fast as possible, and usually runs in about 3 minutes if the docker image build is fully cached (usually the case), or a few minutes more if not.\n\n## Architecture\n\nThe system is designed around Microsoft's pyright type checker with these key features:\n\n- **Segmented checking**: Files are grouped by strictness level and checked separately\n- **Temporary configs**: Generates temporary pyrightconfig.json files for each strictness level\n- **Batch processing**: Efficiently checks multiple files at once when possible\n- **Virtual environment integration**: Uses `.venv` if available for consistent Python environment\n\n## Adding New Features\n\nThe `type-check.py` script is modular and can be extended with new commands by:\n\n1. Adding the command name to the `CMDS` list\n2. Adding documentation to `COMMAND_DOC`\n3. Implementing a method on the `TypeCheck` class with the command name (with hyphens converted to underscores)\n\nThe script follows the pattern where each command method returns `True` for success, `False` for failure, or `None` for success (which is treated as `True`).\n</yakaai_skills>"}