workflows(release): remove non-patch release tasks from patch release issues (#4051)

When creating a patch release tracking issue (e.g. from /backport
comments or manually), .0-only tasks such as 'Prepare Release' and
'Create Release branch' were incorrectly retained in the template.

Update load_release_tracking_template to filter out .0-only tasks
('Prepare Release', 'Create Release branch', and 'Tag RC*') for patch
releases, and update tests to load the real issue template via runfiles.
diff --git a/.github/ISSUE_TEMPLATE/BUILD.bazel b/.github/ISSUE_TEMPLATE/BUILD.bazel
new file mode 100644
index 0000000..436dbca
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/BUILD.bazel
@@ -0,0 +1,5 @@
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+exports_files(glob(["*"]))
diff --git a/RELEASING.md b/RELEASING.md
index f740cb3..d160dcf 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -190,9 +190,8 @@
     workflow.
     *   This will automatically create a standard Release Tracking Issue for
         each target version (e.g., `Release 1.7.1`, `Release 1.8.1`, etc.).
-    *   For patch releases, the created release tracking issues will have `Tag
-        RC` tasks automatically removed, as release candidates are not
-        required for patch releases.
+    *   For patch releases, the created release tracking issue will have
+        non-patch release tasks removed.
     *   The backport PR will be automatically added to the checklist of each
         created release tracking issue.
 
diff --git a/tests/tools/private/release/BUILD.bazel b/tests/tools/private/release/BUILD.bazel
index dc02394..c930d19 100644
--- a/tests/tools/private/release/BUILD.bazel
+++ b/tests/tools/private/release/BUILD.bazel
@@ -18,8 +18,10 @@
     name = "release_test_helper",
     testonly = True,
     srcs = ["release_test_helper.py"],
+    data = ["//.github/ISSUE_TEMPLATE:release_tracking_template.md"],
     target_compatible_with = NOT_WINDOWS,
     deps = [
+        "//python/runfiles",
         "//tools/private/release:mock_gh",
         "//tools/private/release:release_lib",
         "@pypi//pytest",
diff --git a/tests/tools/private/release/add_backports_test.py b/tests/tools/private/release/add_backports_test.py
index b4f2efc..5a33d6d 100644
--- a/tests/tools/private/release/add_backports_test.py
+++ b/tests/tools/private/release/add_backports_test.py
@@ -72,6 +72,9 @@
     assert "- [ ] #124" in body
     assert "- [ ] Sync Changelog #124" in body
     assert "Tag RC" not in body
+    assert "Prepare Release" not in body
+    assert "Create Release branch" not in body
+    assert "- [ ] Tag Final" in body
 
 
 def test_add_backports_patch_release_no_rc_added(mock_gh):
diff --git a/tests/tools/private/release/backport_create_releases_test.py b/tests/tools/private/release/backport_create_releases_test.py
index 81dc7e0..1842a75 100644
--- a/tests/tools/private/release/backport_create_releases_test.py
+++ b/tests/tools/private/release/backport_create_releases_test.py
@@ -44,14 +44,23 @@
     assert 1002 in gh.issues
     assert 1003 in gh.issues
 
-    # 1.7.2 (patch) should not have Tag RC0
+    # 1.7.2 (patch) should not have Tag RC0, Prepare Release, or Create Release branch
     assert gh.issues[1001]["title"] == "Release 1.7.2"
     assert "Tag RC0" not in gh.issues[1001]["body"]
-    assert "## Backports\n- [ ] #456" in gh.issues[1001]["body"]
+    assert "Prepare Release" not in gh.issues[1001]["body"]
+    assert "Create Release branch" not in gh.issues[1001]["body"]
+    assert "Tag Final" in gh.issues[1001]["body"]
+    assert "- [ ] #456" in gh.issues[1001]["body"]
+    assert "- [ ] Sync Changelog #456" in gh.issues[1001]["body"]
 
-    # 1.9.0 (minor) should have Tag RC0
+    # 1.9.0 (minor) should have Tag RC0, Prepare Release, and Create Release branch
     assert gh.issues[1003]["title"] == "Release 1.9.0"
     assert "Tag RC0" in gh.issues[1003]["body"]
+    assert "Prepare Release" in gh.issues[1003]["body"]
+    assert "Create Release branch" in gh.issues[1003]["body"]
+    assert "Tag Final" in gh.issues[1003]["body"]
+    assert "- [ ] #456" in gh.issues[1003]["body"]
+    assert "- [ ] Sync Changelog #456" in gh.issues[1003]["body"]
 
     # Verify backport issue updated
     expected_updated_backport_body = """* PR: #456
diff --git a/tests/tools/private/release/release_issue_test.py b/tests/tools/private/release/release_issue_test.py
index 64fa2b5..b95601c 100644
--- a/tests/tools/private/release/release_issue_test.py
+++ b/tests/tools/private/release/release_issue_test.py
@@ -181,10 +181,12 @@
     assert "- [ ] Tag RC0" in full_template
     assert "- [ ] Tag RC1" in full_template
 
-    # Patch release version (strips RC tasks)
+    # Patch release version (strips .0-only release tasks: Prepare Release, Create Release branch, Tag RC)
     patch_template = load_release_tracking_template(
         version="1.2.1", template_path=template_file
     )
     assert "Tag RC" not in patch_template
-    assert "- [ ] Prepare Release" in patch_template
+    assert "Prepare Release" not in patch_template
+    assert "Create Release branch" not in patch_template
     assert "- [ ] Tag Final" in patch_template
+    assert "## Backports" in patch_template
diff --git a/tests/tools/private/release/release_test_helper.py b/tests/tools/private/release/release_test_helper.py
index 0b1c4e0..c1d3143 100644
--- a/tests/tools/private/release/release_test_helper.py
+++ b/tests/tools/private/release/release_test_helper.py
@@ -1,9 +1,11 @@
 import dataclasses
+import shutil
 from pathlib import Path
 from unittest.mock import MagicMock, patch
 
 import pytest
 
+from python.runfiles import runfiles
 from tools.private.release.mock_gh import MockGitHub
 
 
@@ -20,14 +22,17 @@
     github_output_file: Path
 
 
-DEFAULT_RELEASE_TEMPLATE_CONTENT = (
-    "template content\n"
-    "- [ ] Prepare Release\n"
-    "- [ ] Tag RC0\n"
-    "- [ ] Tag Final\n"
-    "\n"
-    "## Backports\n"
-)
+def _find_real_template_path() -> Path:
+    r = runfiles.Create()
+    path = r.Rlocation(
+        "rules_python/.github/ISSUE_TEMPLATE/release_tracking_template.md"
+    )
+    if not path or not Path(path).is_file():
+        raise FileNotFoundError(
+            "Could not locate .github/ISSUE_TEMPLATE/release_tracking_template.md"
+            f" in runfiles: {path}"
+        )
+    return Path(path)
 
 
 @pytest.fixture(name="mock_git")
@@ -52,11 +57,12 @@
 @pytest.fixture(name="release_tool_env")
 def fixture_release_tool_env(tmp_path, monkeypatch):
     """Fixture providing a temp cwd with release template set up."""
+    source_template = _find_real_template_path()
     monkeypatch.chdir(tmp_path)
     template_dir = tmp_path / ".github" / "ISSUE_TEMPLATE"
     template_dir.mkdir(parents=True, exist_ok=True)
     template_file = template_dir / "release_tracking_template.md"
-    template_file.write_text(DEFAULT_RELEASE_TEMPLATE_CONTENT, encoding="utf-8")
+    shutil.copy2(source_template, template_file)
     github_output_file = tmp_path / "github_output"
     monkeypatch.setenv("GITHUB_OUTPUT", str(github_output_file))
     yield ReleaseToolEnv(git_root=tmp_path, github_output_file=github_output_file)
diff --git a/tools/private/release/release_issue.py b/tools/private/release/release_issue.py
index c8f2f9d..7a3716f 100644
--- a/tools/private/release/release_issue.py
+++ b/tools/private/release/release_issue.py
@@ -7,11 +7,12 @@
     version: str | None = None,
     template_path: pathlib.Path | None = None,
 ) -> str:
-    """Loads the release tracking issue template, stripping RC tasks for patch releases.
+    """Loads the release tracking issue template, stripping non-patch tasks for patch releases.
 
     Args:
         version: Optional version string (e.g. '1.2.1'). If provided and represents a
-            patch release (i.e. does not end in '.0'), strips Tag RC tasks from the template.
+            patch release (i.e. does not end in '.0'), strips .0-only release tasks
+            ('Prepare Release', 'Create Release branch', and 'Tag RC' tasks) from the template.
         template_path: Optional path to the template file. Defaults to
             .github/ISSUE_TEMPLATE/release_tracking_template.md.
 
@@ -29,8 +30,19 @@
     is_patch = version is not None and not version.endswith(".0")
     if is_patch:
         lines = template_content.splitlines()
-        lines = [line for line in lines if not re.search(r"Tag RC\d+", line)]
-        template_content = "\n".join(lines)
+        filtered_lines = []
+        for line in lines:
+            parsed = parse_metadata_line(line)
+            if parsed:
+                name_lower = parsed["name"].lower()
+                if "prepare release" in name_lower:
+                    continue
+                if "create release branch" in name_lower:
+                    continue
+                if re.match(r"^tag rc\d+", name_lower):
+                    continue
+            filtered_lines.append(line)
+        template_content = "\n".join(filtered_lines)
         if not template_content.endswith("\n"):
             template_content += "\n"