Skip to content

Post-release changes#543

Merged
KenVanHoeylandt merged 3 commits into
mainfrom
post-release-changes
Jul 2, 2026
Merged

Post-release changes#543
KenVanHoeylandt merged 3 commits into
mainfrom
post-release-changes

Conversation

@KenVanHoeylandt

@KenVanHoeylandt KenVanHoeylandt commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added tools to download GitHub Actions artifacts and generate release notes from commits and pull requests.
    • Added a release-summary workflow that produces a markdown release draft automatically.
  • Documentation

    • Updated the release process with clearer steps for CDN verification, firmware testing, app updates, and publishing release docs.
  • Chores

    • Updated the project version to 0.8.0-dev.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This pull request adds three new Python build scripts under Buildscripts/: gh-download-artifacts.py for downloading GitHub Actions workflow artifacts, gh-release-summary.py for generating commit and pull request markdown between the last two git tags, and release-summary.py for producing a Claude-generated release summary from those markdown files. Documentation/releasing.md is updated to describe the revised release process incorporating these scripts, and version.txt is bumped from 0.7.0 to 0.8.0-dev.

Changes

Cohort / File(s) Summary
Buildscripts/gh-download-artifacts.py New script: resolves token/repo, paginates GitHub Actions artifacts API, downloads and optionally extracts .bin artifacts
Buildscripts/gh-release-summary.py New script: resolves repo, selects last two tags, collects commits, writes commits and PR markdown files
Buildscripts/release-summary.py New script: runs gh-release-summary.py, builds a prompt, invokes claude CLI, writes release-summary.md
Documentation/releasing.md Updated release procedure steps referencing new scripts and CDN/TactilityApps/TactilityDocs workflow
version.txt Version bumped from 0.7.0 to 0.8.0-dev

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant GhDownload as gh-download-artifacts.py
  participant GhSummary as gh-release-summary.py
  participant ReleaseSummary as release-summary.py
  participant Claude as claude CLI

  User->>GhDownload: run with run_id, token, repo
  GhDownload->>GhDownload: download & extract artifacts

  User->>ReleaseSummary: run with repo, out
  ReleaseSummary->>GhSummary: invoke as subprocess
  GhSummary-->>ReleaseSummary: release-commits.md, release-pull-requests.md
  ReleaseSummary->>Claude: send prompt + commits/PRs
  Claude-->>ReleaseSummary: summary text
  ReleaseSummary-->>User: release-summary.md
Loading

Related issues: None referenced in the provided information.

Related PRs: None referenced in the provided information.

Suggested labels: tooling, documentation, build-scripts

Suggested reviewers: None determinable from the provided information.

Poem
A rabbit taps keys in the build-script glen,
Fetching artifacts, tagging them again,
Commits turn to markdown, PRs line up neat,
Claude hums a summary, tidy and complete,
Version ticks forward, dev-bound and spry —
Hop along, release day is nigh! 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is related to the changeset and accurately signals post-release tooling, docs, and version updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch post-release-changes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@KenVanHoeylandt KenVanHoeylandt merged commit 35fd7dd into main Jul 2, 2026
1 of 14 checks passed
@KenVanHoeylandt KenVanHoeylandt deleted the post-release-changes branch July 2, 2026 18:52

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (4)
Buildscripts/gh-download-artifacts.py (1)

77-95: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Artifact name is used unsanitized to build a filesystem path.

out_dir / f"{name}.zip" (Line 91) derives directly from the API response's artifact["name"]. If a workflow artifact were ever named with path separators (e.g. via a compromised job), this could write outside out_dir. Given artifacts come from the target repo's own Actions run, sanitizing the name defensively is cheap insurance.

Source: Linters/SAST tools

Buildscripts/gh-release-summary.py (1)

76-89: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

PR number extraction can mistake issue references for PR numbers.

PR_REF_RE matches any #<digits> in a commit subject, so a commit message referencing an issue (e.g. Fixes #42``) would be listed under "Pull Requests" even though it isn't one. Given this only feeds documentation/summary generation (not a blocking release gate), it's a minor cosmetic gap.

Buildscripts/release-summary.py (2)

42-42: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Typo: "don't like pull request" → "don't link the pull request".


22-26: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

run_gh_release_summary propagates raw errors on missing gh-release-summary.py exit.

Consistent with the comment on run_claude above: subprocess.run(cmd, check=True) will raise an unhandled CalledProcessError traceback rather than a clean message if the child script fails. Minor, since the child script's own sys.exit message would still surface in stderr, but the outer traceback adds noise.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fe2cb9d4-11fe-4f0a-a133-630a2c150c66

📥 Commits

Reviewing files that changed from the base of the PR and between cd07c64 and 24ab5a4.

📒 Files selected for processing (5)
  • Buildscripts/gh-download-artifacts.py
  • Buildscripts/gh-release-summary.py
  • Buildscripts/release-summary.py
  • Documentation/releasing.md
  • version.txt

url = f"{API_URL}/repos/{repo}/actions/runs/{run_id}/artifacts"
artifacts = []
while url:
resp = requests.get(url, headers=headers, params={"per_page": 100})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add timeouts to requests.get calls.

Neither the artifact-list request nor the artifact-download request specifies a timeout, so a hung GitHub API/CDN response will block the script indefinitely.

🌐 Proposed fix
-        resp = requests.get(url, headers=headers, params={"per_page": 100})
+        resp = requests.get(url, headers=headers, params={"per_page": 100}, timeout=30)
-        resp = requests.get(download_url, headers=headers, stream=True)
+        resp = requests.get(download_url, headers=headers, stream=True, timeout=30)

Also applies to: 88-88

🧰 Tools
🪛 Ruff (0.15.20)

[error] 67-67: Probable use of requests call without timeout

(S113)

Source: Linters/SAST tools

Comment on lines +96 to +99
if name.endswith(".bin"):
with zipfile.ZipFile(zip_path) as zf:
zf.extractall(out_dir)
zip_path.unlink()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Validate zip member paths before extracting (Zip Slip).

zf.extractall(out_dir) trusts every entry name in the downloaded archive. A malicious or compromised artifact containing a path like ../../evil could write outside out_dir. Since the artifact content originates from the GitHub Actions API for the target run, this is a real (if lower-likelihood) supply-chain risk worth guarding against.

🔒 Proposed fix
         if name.endswith(".bin"):
             with zipfile.ZipFile(zip_path) as zf:
-                zf.extractall(out_dir)
+                for member in zf.namelist():
+                    member_path = (out_dir / member).resolve()
+                    if not member_path.is_relative_to(out_dir.resolve()):
+                        sys.exit(f"Unsafe path in archive: {member}")
+                zf.extractall(out_dir)
             zip_path.unlink()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if name.endswith(".bin"):
with zipfile.ZipFile(zip_path) as zf:
zf.extractall(out_dir)
zip_path.unlink()
if name.endswith(".bin"):
with zipfile.ZipFile(zip_path) as zf:
for member in zf.namelist():
member_path = (out_dir / member).resolve()
if not member_path.is_relative_to(out_dir.resolve()):
sys.exit(f"Unsafe path in archive: {member}")
zf.extractall(out_dir)
zip_path.unlink()
🧰 Tools
🪛 ast-grep (0.44.0)

[error] 97-97: Calling extractall() on a zipfile.ZipFile or tarfile archive without validating member paths lets a crafted entry (e.g. "../../etc/passwd") write outside the destination directory (Zip Slip). Validate each member resolves inside the target directory, or pass a safe filter (tarfile: filter="data" / tarfile.data_filter).
Context: zf.extractall(out_dir)
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(archive-extractall-path-traversal-python)

Source: Linters/SAST tools

Comment on lines +29 to +45
def build_prompt() -> str:
section_list = "\n".join(f"- {s}" for s in SECTIONS)
return (
"You are given a list of commits (with GitHub commit links) and a list of pull "
"request links from a software release. Write a release summary in Markdown, "
"organizing changes into these sections (omit a section if it has no relevant "
"changes):\n\n"
f"{section_list}\n\n"
"Use concise bullet points. Base every bullet strictly on the given commits and "
"pull requests, referencing the pull request link where applicable. Output only "
"the Markdown, no commentary before or after it."
"If a pull request or commit has multiple changes add them as separate entries."
"Ignore irrelevant changes: documentation, minor fixes, non-descript fixes, scripts, github workflows."
"For 'New Devices': only mention the device name, don't like pull request."
"For 'Improvements & Fixes': Avoid non-descript entries, write specifics."
"For 'TactilitySDK': Avoid generic changes, be specific. Mention API changes."
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Missing spaces/separators between concatenated prompt strings.

The adjacent string literals in build_prompt are implicitly concatenated by Python without a separating space, so the emitted prompt runs sentences together, e.g. "...after it.If a pull request...". This degrades prompt readability/quality sent to Claude.

✏️ Proposed fix
         "Use concise bullet points. Base every bullet strictly on the given commits and "
         "pull requests, referencing the pull request link where applicable. Output only "
-        "the Markdown, no commentary before or after it."
-        "If a pull request or commit has multiple changes add them as separate entries."
-        "Ignore irrelevant changes: documentation, minor fixes, non-descript fixes, scripts, github workflows."
-        "For 'New Devices': only mention the device name, don't like pull request."
-        "For 'Improvements & Fixes': Avoid non-descript entries, write specifics."
-        "For 'TactilitySDK': Avoid generic changes, be specific. Mention API changes."
+        "the Markdown, no commentary before or after it. "
+        "If a pull request or commit has multiple changes add them as separate entries. "
+        "Ignore irrelevant changes: documentation, minor fixes, non-descript fixes, scripts, github workflows. "
+        "For 'New Devices': only mention the device name, don't link the pull request. "
+        "For 'Improvements & Fixes': Avoid non-descript entries, write specifics. "
+        "For 'TactilitySDK': Avoid generic changes, be specific. Mention API changes."
     )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def build_prompt() -> str:
section_list = "\n".join(f"- {s}" for s in SECTIONS)
return (
"You are given a list of commits (with GitHub commit links) and a list of pull "
"request links from a software release. Write a release summary in Markdown, "
"organizing changes into these sections (omit a section if it has no relevant "
"changes):\n\n"
f"{section_list}\n\n"
"Use concise bullet points. Base every bullet strictly on the given commits and "
"pull requests, referencing the pull request link where applicable. Output only "
"the Markdown, no commentary before or after it."
"If a pull request or commit has multiple changes add them as separate entries."
"Ignore irrelevant changes: documentation, minor fixes, non-descript fixes, scripts, github workflows."
"For 'New Devices': only mention the device name, don't like pull request."
"For 'Improvements & Fixes': Avoid non-descript entries, write specifics."
"For 'TactilitySDK': Avoid generic changes, be specific. Mention API changes."
)
def build_prompt() -> str:
section_list = "\n".join(f"- {s}" for s in SECTIONS)
return (
"You are given a list of commits (with GitHub commit links) and a list of pull "
"request links from a software release. Write a release summary in Markdown, "
"organizing changes into these sections (omit a section if it has no relevant "
"changes):\n\n"
f"{section_list}\n\n"
"Use concise bullet points. Base every bullet strictly on the given commits and "
"pull requests, referencing the pull request link where applicable. Output only "
"the Markdown, no commentary before or after it. "
"If a pull request or commit has multiple changes add them as separate entries. "
"Ignore irrelevant changes: documentation, minor fixes, non-descript fixes, scripts, github workflows. "
"For 'New Devices': only mention the device name, don't link the pull request. "
"For 'Improvements & Fixes': Avoid non-descript entries, write specifics. "
"For 'TactilitySDK': Avoid generic changes, be specific. Mention API changes."
)

Comment on lines +48 to +58
def run_claude(commits_md: str, prs_md: str) -> str:
prompt = build_prompt()
stdin_content = f"## Commits\n\n{commits_md}\n\n## Pull Requests\n\n{prs_md}"
result = subprocess.run(
["claude", "-p", prompt],
input=stdin_content,
capture_output=True,
text=True,
check=True,
)
return result.stdout

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

No timeout and no friendly error handling around the claude subprocess.

Unlike get_token/get_repo in the sibling scripts, which catch FileNotFoundError/CalledProcessError and print an actionable message, run_claude lets an unhandled CalledProcessError/FileNotFoundError (e.g. claude CLI missing) propagate as a raw traceback. It also has no timeout, so a hung/rate-limited claude call can block the release pipeline indefinitely.

🛠️ Proposed fix
 def run_claude(commits_md: str, prs_md: str) -> str:
     prompt = build_prompt()
     stdin_content = f"## Commits\n\n{commits_md}\n\n## Pull Requests\n\n{prs_md}"
-    result = subprocess.run(
-        ["claude", "-p", prompt],
-        input=stdin_content,
-        capture_output=True,
-        text=True,
-        check=True,
-    )
-    return result.stdout
+    try:
+        result = subprocess.run(
+            ["claude", "-p", prompt],
+            input=stdin_content,
+            capture_output=True,
+            text=True,
+            check=True,
+            timeout=300,
+        )
+    except FileNotFoundError:
+        sys.exit("`claude` CLI not found. Install it and ensure it's on PATH.")
+    except subprocess.TimeoutExpired:
+        sys.exit("`claude` call timed out.")
+    except subprocess.CalledProcessError as e:
+        sys.exit(f"`claude` failed: {e.stderr}")
+    return result.stdout
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def run_claude(commits_md: str, prs_md: str) -> str:
prompt = build_prompt()
stdin_content = f"## Commits\n\n{commits_md}\n\n## Pull Requests\n\n{prs_md}"
result = subprocess.run(
["claude", "-p", prompt],
input=stdin_content,
capture_output=True,
text=True,
check=True,
)
return result.stdout
def run_claude(commits_md: str, prs_md: str) -> str:
prompt = build_prompt()
stdin_content = f"## Commits\n\n{commits_md}\n\n## Pull Requests\n\n{prs_md}"
try:
result = subprocess.run(
["claude", "-p", prompt],
input=stdin_content,
capture_output=True,
text=True,
check=True,
timeout=300,
)
except FileNotFoundError:
sys.exit("`claude` CLI not found. Install it and ensure it's on PATH.")
except subprocess.TimeoutExpired:
sys.exit("`claude` call timed out.")
except subprocess.CalledProcessError as e:
sys.exit(f"`claude` failed: {e.stderr}")
return result.stdout
🧰 Tools
🪛 ast-grep (0.44.0)

[error] 50-56: Command coming from incoming request
Context: subprocess.run(
["claude", "-p", prompt],
input=stdin_content,
capture_output=True,
text=True,
check=True,
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)

🪛 Ruff (0.15.20)

[error] 51-51: subprocess call: check for execution of untrusted input

(S603)


[error] 52-52: Starting a process with a partial executable path

(S607)

4. Wait for the SDK and firmwares to be published on the CDN.
5. Test the firmwares on all devices with the web installer
6. Run `Buildscripts/gh-download-artifacts.py [actionId] --token [token]` and let it run in the background.
Get a token from [here](https://github.com/settings/personal-access-tokens)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Replace the generic link text.

[here] is opaque and triggers the markdownlint MD059 warning. Use descriptive text so the step is self-explanatory.

💡 Suggested fix
-   Get a token from [here](https://github.com/settings/personal-access-tokens)
+   Get a token from [GitHub personal access tokens](https://github.com/settings/personal-access-tokens)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Get a token from [here](https://github.com/settings/personal-access-tokens)
Get a token from [GitHub personal access tokens](https://github.com/settings/personal-access-tokens)
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 11-11: Link text should be descriptive

(MD059, descriptive-link-text)

Source: Linters/SAST tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant