fix(release): write release notes to file instead of capturing stdout#8086
fix(release): write release notes to file instead of capturing stdout#8086
Conversation
Use Claude's Write tool to write release notes to a temp file instead of capturing raw stdout with --output-format text. This eliminates formatting artifacts like extraneous markdown headers in the title or conversational preamble in the output. Also upgrades model from claude-opus-4-5 to claude-opus-4-6. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary of ChangesHello @jdx, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request refines the automated release note generation process by leveraging Claude's Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
The pull request effectively addresses the issue of formatting artifacts by switching Claude's output mechanism from stdout capture to direct file writing. The changes correctly update the prompt instructions for Claude, modify the command-line arguments for the claude CLI tool to use the Write tool with a scoped path, and adjust the script's logic for temporary file management and output validation. The model upgrade is also applied as described. The implementation is clean and directly achieves the stated goals of the pull request.
There was a problem hiding this comment.
Pull request overview
Updates the release-notes generation task to have Claude write markdown output to a file (via the Write tool) instead of relying on captured stdout, and upgrades the model identifier.
Changes:
- Instruct Claude to write release notes to
/tmp/release-notes-output.mdand read that file back for output - Allow the
Writetool scoped to the output file path and clean up temp artifacts viatrap - Change the model from
claude-opus-4-5-20251101toclaude-opus-4-6
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
xtasks/gen-release-notes
Outdated
| Write your output to the file: /tmp/release-notes-output.md | ||
| Do not output anything else — just write to the file. |
There was a problem hiding this comment.
Using a predictable, fixed filename in /tmp is vulnerable to symlink/hardlink tricks and can lead to clobbering or reading unexpected files. Use a unique temp path (e.g., via mktemp) for output_file and reference that same variable in the prompt so the Write tool is still constrained to exactly that file.
xtasks/gen-release-notes
Outdated
| output_file="/tmp/release-notes-output.md" | ||
| rm -f "$output_file" | ||
|
|
||
| stderr_file=$(mktemp) | ||
| trap 'rm -f "$stderr_file"' EXIT | ||
|
|
||
| if ! output=$( | ||
| printf '%s' "$prompt" | claude -p \ | ||
| --model claude-opus-4-5-20251101 \ | ||
| --permission-mode bypassPermissions \ | ||
| --output-format text \ | ||
| --allowedTools "Read,Grep,Glob" 2>"$stderr_file" | ||
| ); then | ||
| trap 'rm -f "$stderr_file" "$output_file"' EXIT |
There was a problem hiding this comment.
Using a predictable, fixed filename in /tmp is vulnerable to symlink/hardlink tricks and can lead to clobbering or reading unexpected files. Use a unique temp path (e.g., via mktemp) for output_file and reference that same variable in the prompt so the Write tool is still constrained to exactly that file.
xtasks/gen-release-notes
Outdated
| Write your output to the file: /tmp/release-notes-output.md | ||
| Do not output anything else — just write to the file. |
There was a problem hiding this comment.
The output path is duplicated (hard-coded in the prompt and separately in output_file). This can drift if one changes. Prefer constructing the prompt using the output_file variable so there’s a single source of truth.
xtasks/gen-release-notes
Outdated
| echo "Changelog length: ${#changelog} chars" >&2 | ||
|
|
||
| # Capture stderr separately to avoid polluting output | ||
| output_file="/tmp/release-notes-output.md" |
There was a problem hiding this comment.
The output path is duplicated (hard-coded in the prompt and separately in output_file). This can drift if one changes. Prefer constructing the prompt using the output_file variable so there’s a single source of truth.
xtasks/gen-release-notes
Outdated
| if ! printf '%s' "$prompt" | claude -p \ | ||
| --model claude-opus-4-6 \ | ||
| --permission-mode bypassPermissions \ | ||
| --allowedTools "Read,Grep,Glob,Write($output_file)" 2>"$stderr_file"; then |
There was a problem hiding this comment.
Now that stdout is no longer captured, any unexpected stdout from the claude CLI (preamble, warnings, formatting, etc.) will leak into this script’s stdout and corrupt the intended output. Since the release notes are read from $output_file, redirect Claude’s stdout away (e.g., to /dev/null) and rely solely on the file + captured stderr for diagnostics.
| --allowedTools "Read,Grep,Glob,Write($output_file)" 2>"$stderr_file"; then | |
| --allowedTools "Read,Grep,Glob,Write($output_file)" >/dev/null 2>"$stderr_file"; then |
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.9 x -- echo |
22.7 ± 0.6 | 21.4 | 28.3 | 1.00 ± 0.04 |
mise x -- echo |
22.6 ± 0.8 | 21.3 | 28.5 | 1.00 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.9 env |
21.4 ± 0.6 | 20.5 | 28.8 | 1.00 |
mise env |
22.1 ± 0.6 | 20.8 | 24.2 | 1.03 ± 0.04 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.9 hook-env |
22.2 ± 0.5 | 21.2 | 24.0 | 1.00 |
mise hook-env |
23.0 ± 1.0 | 21.5 | 34.7 | 1.04 ± 0.05 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.9 ls |
20.9 ± 0.5 | 19.6 | 23.4 | 1.00 |
mise ls |
21.3 ± 0.7 | 19.8 | 23.6 | 1.02 ± 0.04 |
xtasks/test/perf
| Command | mise-2026.2.9 | mise | Variance |
|---|---|---|---|
| install (cached) | 120ms | 119ms | +0% |
| ls (cached) | 74ms | 73ms | +1% |
| bin-paths (cached) | 78ms | 78ms | +0% |
| task-ls (cached) | 536ms | 538ms | +0% |
Address PR feedback: - Use mktemp instead of a fixed /tmp path to avoid symlink attacks - Redirect Claude's stdout to /dev/null since we read from the file - Reference output_file variable in prompt instead of hardcoding path Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| exit 1 | ||
| fi | ||
|
|
||
| output_file=$(mktemp /tmp/release-notes-XXXXXX.md) |
There was a problem hiding this comment.
mktemp suffix breaks on macOS BSD mktemp
Medium Severity
mktemp /tmp/release-notes-XXXXXX.md uses a template with characters after the trailing Xs, which is a GNU coreutils extension. macOS ships with BSD mktemp, which requires the Xs to be at the very end of the template. On macOS, this command will fail (or worse, create a file literally named release-notes-XXXXXX.md with no randomization). Since the script uses set -euo pipefail, it will abort immediately with a cryptic error on macOS. Dropping the .md suffix (e.g., mktemp /tmp/release-notes-XXXXXX) would be portable across both platforms.
### 🚀 Features - **(activate)** add shims directory as fallback when auto-install is enabled by @ctaintor in [#8106](#8106) - **(env)** add `tools` variable to tera template context by @jdx in [#8108](#8108) - **(set)** add --stdin flag for multiline environment variables by @jdx in [#8110](#8110) ### 🐛 Bug Fixes - **(backend)** improve conda patchelf and dependency resolution for complex packages by @jdx in [#8087](#8087) - **(ci)** fix validate-new-tools grep pattern for test field by @jdx in [#8100](#8100) - **(config)** make MISE_OFFLINE work correctly by gracefully skipping network calls by @jdx in [#8109](#8109) - **(github)** skip v prefix for "latest" version by @jdx in [#8105](#8105) - **(gitlab)** resolve tool options from config for aliased tools by @jdx in [#8084](#8084) - **(install)** use version_expr for Flutter to fix version resolution by @jdx in [#8081](#8081) - **(registry)** add Linux support for tuist by @fortmarek in [#8102](#8102) - **(release)** write release notes to file instead of capturing stdout by @jdx in [#8086](#8086) - **(upgrade)** tools are not uninstalled properly due to outdated symlink by @roele in [#8099](#8099) - **(upgrade)** ensure uninstallation failure does not leave invalid symlinks by @roele in [#8101](#8101) - SLSA for in-toto statement with no signatures by @gerhard in [#8094](#8094) - Vfox Plugin Auto-Installation for Environment Directives by @pose in [#8035](#8035) ### 📚 Documentation - use mise activate for PowerShell in getting-started by @rileychh in [#8112](#8112) ### 📦 Registry - add conda backend for mysql by @jdx in [#8080](#8080) - add conda backends for 10 asdf-only tools by @jdx in [#8083](#8083) - added podman-tui by @tony-sol in [#8098](#8098) ### Chore - sort settings.toml alphabetically and add test by @jdx in [#8111](#8111) ### New Contributors - @ctaintor made their first contribution in [#8106](#8106) - @rileychh made their first contribution in [#8112](#8112) - @fortmarek made their first contribution in [#8102](#8102) - @pose made their first contribution in [#8035](#8035) - @gerhard made their first contribution in [#8094](#8094) ## 📦 Aqua Registry Updates #### New Packages (2) - [`entireio/cli`](https://github.com/entireio/cli) - [`rmitchellscott/reManager`](https://github.com/rmitchellscott/reManager) #### Updated Packages (1) - [`atuinsh/atuin`](https://github.com/atuinsh/atuin)
### 🚀 Features - **(activate)** add shims directory as fallback when auto-install is enabled by @ctaintor in [#8106](#8106) - **(env)** add `tools` variable to tera template context by @jdx in [#8108](#8108) - **(set)** add --stdin flag for multiline environment variables by @jdx in [#8110](#8110) ### 🐛 Bug Fixes - **(backend)** improve conda patchelf and dependency resolution for complex packages by @jdx in [#8087](#8087) - **(ci)** fix validate-new-tools grep pattern for test field by @jdx in [#8100](#8100) - **(config)** make MISE_OFFLINE work correctly by gracefully skipping network calls by @jdx in [#8109](#8109) - **(github)** skip v prefix for "latest" version by @jdx in [#8105](#8105) - **(gitlab)** resolve tool options from config for aliased tools by @jdx in [#8084](#8084) - **(install)** use version_expr for Flutter to fix version resolution by @jdx in [#8081](#8081) - **(registry)** add Linux support for tuist by @fortmarek in [#8102](#8102) - **(release)** write release notes to file instead of capturing stdout by @jdx in [#8086](#8086) - **(upgrade)** tools are not uninstalled properly due to outdated symlink by @roele in [#8099](#8099) - **(upgrade)** ensure uninstallation failure does not leave invalid symlinks by @roele in [#8101](#8101) - SLSA for in-toto statement with no signatures by @gerhard in [#8094](#8094) - Vfox Plugin Auto-Installation for Environment Directives by @pose in [#8035](#8035) ### 📚 Documentation - use mise activate for PowerShell in getting-started by @rileychh in [#8112](#8112) ### 📦 Registry - add conda backend for mysql by @jdx in [#8080](#8080) - add conda backends for 10 asdf-only tools by @jdx in [#8083](#8083) - added podman-tui by @tony-sol in [#8098](#8098) ### Chore - sort settings.toml alphabetically and add test by @jdx in [#8111](#8111) ### New Contributors - @ctaintor made their first contribution in [#8106](#8106) - @rileychh made their first contribution in [#8112](#8112) - @fortmarek made their first contribution in [#8102](#8102) - @pose made their first contribution in [#8035](#8035) - @gerhard made their first contribution in [#8094](#8094) ## 📦 Aqua Registry Updates #### New Packages (2) - [`entireio/cli`](https://github.com/entireio/cli) - [`rmitchellscott/reManager`](https://github.com/rmitchellscott/reManager) #### Updated Packages (1) - [`atuinsh/atuin`](https://github.com/atuinsh/atuin)
…jdx#8086) ## Summary - Use Claude's `Write` tool to write release notes to a temp file instead of capturing raw stdout with `--output-format text`, eliminating formatting artifacts (extraneous `#` in titles, conversational preamble) - Scope the `Write` tool to only `/tmp/release-notes-output.md` using `Write($output_file)` path restriction - Upgrade model from `claude-opus-4-5` to `claude-opus-4-6` ## Test plan - [ ] Run `mise run gen-release-notes v2026.2.9 v2026.2.7` and verify clean output with no preamble or extraneous markdown headers 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: only adjusts the release-notes generation script flow and CLI invocation, with no impact to runtime behavior beyond how output is captured/validated. > > **Overview** > Switches `xtasks/gen-release-notes` to have Claude write release notes to a temporary markdown file via the scoped `Write($output_file)` tool, instead of capturing stdout, to avoid formatting artifacts. > > Updates the prompt to instruct file output, upgrades the model to `claude-opus-4-6`, redirects Claude stdout to `/dev/null`, and validates the temp file exists and is non-empty before printing it; temp files are cleaned up via a single trap. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 1b7e6ba. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
### 🚀 Features - **(activate)** add shims directory as fallback when auto-install is enabled by @ctaintor in [jdx#8106](jdx#8106) - **(env)** add `tools` variable to tera template context by @jdx in [jdx#8108](jdx#8108) - **(set)** add --stdin flag for multiline environment variables by @jdx in [jdx#8110](jdx#8110) ### 🐛 Bug Fixes - **(backend)** improve conda patchelf and dependency resolution for complex packages by @jdx in [jdx#8087](jdx#8087) - **(ci)** fix validate-new-tools grep pattern for test field by @jdx in [jdx#8100](jdx#8100) - **(config)** make MISE_OFFLINE work correctly by gracefully skipping network calls by @jdx in [jdx#8109](jdx#8109) - **(github)** skip v prefix for "latest" version by @jdx in [jdx#8105](jdx#8105) - **(gitlab)** resolve tool options from config for aliased tools by @jdx in [jdx#8084](jdx#8084) - **(install)** use version_expr for Flutter to fix version resolution by @jdx in [jdx#8081](jdx#8081) - **(registry)** add Linux support for tuist by @fortmarek in [jdx#8102](jdx#8102) - **(release)** write release notes to file instead of capturing stdout by @jdx in [jdx#8086](jdx#8086) - **(upgrade)** tools are not uninstalled properly due to outdated symlink by @roele in [jdx#8099](jdx#8099) - **(upgrade)** ensure uninstallation failure does not leave invalid symlinks by @roele in [jdx#8101](jdx#8101) - SLSA for in-toto statement with no signatures by @gerhard in [jdx#8094](jdx#8094) - Vfox Plugin Auto-Installation for Environment Directives by @pose in [jdx#8035](jdx#8035) ### 📚 Documentation - use mise activate for PowerShell in getting-started by @rileychh in [jdx#8112](jdx#8112) ### 📦 Registry - add conda backend for mysql by @jdx in [jdx#8080](jdx#8080) - add conda backends for 10 asdf-only tools by @jdx in [jdx#8083](jdx#8083) - added podman-tui by @tony-sol in [jdx#8098](jdx#8098) ### Chore - sort settings.toml alphabetically and add test by @jdx in [jdx#8111](jdx#8111) ### New Contributors - @ctaintor made their first contribution in [jdx#8106](jdx#8106) - @rileychh made their first contribution in [jdx#8112](jdx#8112) - @fortmarek made their first contribution in [jdx#8102](jdx#8102) - @pose made their first contribution in [jdx#8035](jdx#8035) - @gerhard made their first contribution in [jdx#8094](jdx#8094) ## 📦 Aqua Registry Updates #### New Packages (2) - [`entireio/cli`](https://github.com/entireio/cli) - [`rmitchellscott/reManager`](https://github.com/rmitchellscott/reManager) #### Updated Packages (1) - [`atuinsh/atuin`](https://github.com/atuinsh/atuin)
### 🚀 Features - **(activate)** add shims directory as fallback when auto-install is enabled by @ctaintor in [jdx#8106](jdx#8106) - **(env)** add `tools` variable to tera template context by @jdx in [jdx#8108](jdx#8108) - **(set)** add --stdin flag for multiline environment variables by @jdx in [jdx#8110](jdx#8110) ### 🐛 Bug Fixes - **(backend)** improve conda patchelf and dependency resolution for complex packages by @jdx in [jdx#8087](jdx#8087) - **(ci)** fix validate-new-tools grep pattern for test field by @jdx in [jdx#8100](jdx#8100) - **(config)** make MISE_OFFLINE work correctly by gracefully skipping network calls by @jdx in [jdx#8109](jdx#8109) - **(github)** skip v prefix for "latest" version by @jdx in [jdx#8105](jdx#8105) - **(gitlab)** resolve tool options from config for aliased tools by @jdx in [jdx#8084](jdx#8084) - **(install)** use version_expr for Flutter to fix version resolution by @jdx in [jdx#8081](jdx#8081) - **(registry)** add Linux support for tuist by @fortmarek in [jdx#8102](jdx#8102) - **(release)** write release notes to file instead of capturing stdout by @jdx in [jdx#8086](jdx#8086) - **(upgrade)** tools are not uninstalled properly due to outdated symlink by @roele in [jdx#8099](jdx#8099) - **(upgrade)** ensure uninstallation failure does not leave invalid symlinks by @roele in [jdx#8101](jdx#8101) - SLSA for in-toto statement with no signatures by @gerhard in [jdx#8094](jdx#8094) - Vfox Plugin Auto-Installation for Environment Directives by @pose in [jdx#8035](jdx#8035) ### 📚 Documentation - use mise activate for PowerShell in getting-started by @rileychh in [jdx#8112](jdx#8112) ### 📦 Registry - add conda backend for mysql by @jdx in [jdx#8080](jdx#8080) - add conda backends for 10 asdf-only tools by @jdx in [jdx#8083](jdx#8083) - added podman-tui by @tony-sol in [jdx#8098](jdx#8098) ### Chore - sort settings.toml alphabetically and add test by @jdx in [jdx#8111](jdx#8111) ### New Contributors - @ctaintor made their first contribution in [jdx#8106](jdx#8106) - @rileychh made their first contribution in [jdx#8112](jdx#8112) - @fortmarek made their first contribution in [jdx#8102](jdx#8102) - @pose made their first contribution in [jdx#8035](jdx#8035) - @gerhard made their first contribution in [jdx#8094](jdx#8094) ## 📦 Aqua Registry Updates #### New Packages (2) - [`entireio/cli`](https://github.com/entireio/cli) - [`rmitchellscott/reManager`](https://github.com/rmitchellscott/reManager) #### Updated Packages (1) - [`atuinsh/atuin`](https://github.com/atuinsh/atuin)


Summary
Writetool to write release notes to a temp file instead of capturing raw stdout with--output-format text, eliminating formatting artifacts (extraneous#in titles, conversational preamble)Writetool to only/tmp/release-notes-output.mdusingWrite($output_file)path restrictionclaude-opus-4-5toclaude-opus-4-6Test plan
mise run gen-release-notes v2026.2.9 v2026.2.7and verify clean output with no preamble or extraneous markdown headers🤖 Generated with Claude Code
Note
Low Risk
Low risk: only adjusts the release-notes generation script flow and CLI invocation, with no impact to runtime behavior beyond how output is captured/validated.
Overview
Switches
xtasks/gen-release-notesto have Claude write release notes to a temporary markdown file via the scopedWrite($output_file)tool, instead of capturing stdout, to avoid formatting artifacts.Updates the prompt to instruct file output, upgrades the model to
claude-opus-4-6, redirects Claude stdout to/dev/null, and validates the temp file exists and is non-empty before printing it; temp files are cleaned up via a single trap.Written by Cursor Bugbot for commit 1b7e6ba. This will update automatically on new commits. Configure here.