Conversation
📝 WalkthroughWalkthroughIntroduces automated CI/CD workflows for triggering downstream service updates across repositories, adds a Python script for selective NuGet package version bumping, creates a GitHub dispatch targets configuration file, and integrates an external widget script into the docfx template layout. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~35 minutes Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (4)
.github/scripts/bump-nuget.py (2)
25-26: Module-level environment reads make unit testing harder.
TRIGGER_SOURCEandTRIGGER_VERSIONare read at import time, meaning tests cannot easily override them without patchingos.environbefore import or reloading the module. Consider reading them insidemain()and passing them as parameters tois_triggered_package. This is minor given the script's CI-only usage.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/scripts/bump-nuget.py around lines 25 - 26, The module currently reads TRIGGER_SOURCE and TRIGGER_VERSION at import time which hinders tests; move the os.environ.get(...) calls into main() (or a new initializer) and pass the retrieved values as parameters into is_triggered_package (and any other functions that currently rely on the module-level TRIGGER_SOURCE/TRIGGER_VERSION), update is_triggered_package signature to accept trigger_source and trigger_version, and adjust callers to supply those values so tests can control environment without reloading the module.
129-129: Uselessif-else— both branches return0.This was also flagged by Ruff (RUF034). The conditional is dead code. Simplify to a plain
return 0and keep the intent in the comment.♻️ Proposed fix
- return 0 if changes else 0 # Return 0 even if no changes (not an error) + return 0 # Always succeed — no changes is not an error🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/scripts/bump-nuget.py at line 129, The conditional return "return 0 if changes else 0" is dead code; replace it with a simple "return 0" (keep the existing comment about returning 0 even if no changes) so the function in bump-nuget.py no longer contains the useless if-else expression..github/workflows/service-update.yml (1)
100-105:dry_rungate is only effective forworkflow_dispatch— intended but worth a comment.For
repository_dispatchevents,github.event.inputs.dry_runis always empty, so the PR-creation path always runs. If dry-run support is ever needed for dispatched events, it would require adding adry_runfield toclient_payload. A brief inline comment would clarify this design choice.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/service-update.yml around lines 100 - 105, Add an inline comment near the two workflow steps that gate on github.event.inputs.dry_run (the lines using if: ${{ github.event.inputs.dry_run == 'true' }} and if: ${{ github.event.inputs.dry_run != 'true' }}) explaining that this dry_run input is only populated for workflow_dispatch events and will be empty for repository_dispatch events, and note that to support dry-run behavior for dispatched events you'd need to pass a dry_run field in client_payload of the repository_dispatch call; keep the comment short and factual for future maintainers..docfx/templates/savvyio/layout/_master.tmpl (1)
137-138: Loading third-party script without integrity checks—explore versioning alternatives with Context7.Loading JavaScript from an external domain presents a supply chain risk. Context7's official documentation does not provide SRI hashes or versioned URLs (e.g.,
/widget.vX.Y.Z.js), so adding a self-generated SRI hash would break whenever Context7 updates the script. Instead:
- Contact Context7 support to request versioned or immutable URLs and published SRI hashes per release.
- If Context7 cannot provide stability guarantees, consider whether self-hosting a pinned version of the widget is feasible for your documentation workflow.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.docfx/templates/savvyio/layout/_master.tmpl around lines 137 - 138, The template currently loads the third‑party script directly via the script tag src "https://context7.com/widget.js" without integrity or versioning; replace this by either (A) requesting a versioned/immutable URL and published SRI from Context7 and then updating the script tag to use that versioned URL with an integrity and crossorigin attribute, or (B) if Context7 cannot provide versioned releases/SRI, download and self‑host a pinned copy of the widget and change the script src to the self‑hosted path (and add integrity/crossorigin for the pinned file); locate the script inclusion in _master.tmpl and update the tag accordingly after obtaining the versioned file or pinned asset.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/service-update.yml:
- Around line 70-75: The pipeline setting TFM (TFM=$(grep -m1 "^Availability:"
"$f" | sed 's/Availability: //' || echo "...")) never falls back because sed
returns 0; fix by capturing grep output first and then applying the fallback:
run grep -m1 "^Availability:" "$f" into a temp variable (e.g., raw_avail), check
if raw_avail is empty and if so set TFM to the default ".NET 10, .NET 9 and .NET
Standard 2.0", otherwise strip the "Availability: " prefix (using sed or
parameter expansion) and assign that to TFM; update the loop body that
references TFM accordingly.
- Around line 44-51: The CURRENT extraction can be empty which makes awk produce
invalid version strings; update the step that computes CURRENT/NEW/BRANCH to
guard for an empty CURRENT by checking if CURRENT is non-empty and if not either
set CURRENT to a sane default like "0.0.0" or fail early with an error and exit
1; then compute NEW using the same awk expression on the validated CURRENT and
derive BRANCH from NEW (symbols: CURRENT, NEW, BRANCH, the grep/awk pipeline
used to parse CHANGELOG.md). Ensure the chosen behavior writes the correct
values to $GITHUB_OUTPUT (current, new, branch) and stops the workflow or uses
the default when CHANGELOG has no matching entries.
- Around line 37-41: The run step currently injects user-controlled expressions
directly into the shell via "${{ github.event.client_payload.source_repo ||
github.event.inputs.source_repo }}" and "${{
github.event.client_payload.source_version || github.event.inputs.source_version
}}", creating a script-injection risk; fix it by moving those expressions into
environment variables (set SOURCE and VERSION via an env: mapping using the same
${{ ... }} expressions) and then reference the safe env vars inside the run
block when writing to $GITHUB_OUTPUT (use printf/echo with the env vars SOURCE
and VERSION rather than inlining the expressions). Ensure you handle both
fallbacks (client_payload vs inputs) in the env assignments and only use the env
vars inside the run.
- Around line 108-139: The run block directly interpolates step outputs (NEW,
BRANCH, SOURCE, SRC_VER) which can lead to shell injection; change the step to
declare these as step-level env variables (env: NEW: ${{
steps.newver.outputs.new }}, BRANCH: ${{ steps.newver.outputs.branch }}, SOURCE:
${{ steps.trigger.outputs.source }}, SRC_VER: ${{ steps.trigger.outputs.version
}}) and then reference the env vars inside the run script (use "$NEW",
"$BRANCH", "$SOURCE", "$SRC_VER") instead of ${ { steps.*.outputs.* } } inline;
additionally add basic validation/sanitization in the script (e.g., strip
newlines, reject/control dangerous characters or use an allowlist for
BRANCH/NEW) before using values in git/gh commands and ensure all expansions are
quoted, especially for git checkout -b "$BRANCH" and gh pr create arguments.
In @.github/workflows/trigger-downstream.yml:
- Around line 32-35: The workflow currently interpolates user-controlled
github.event.release.tag_name directly into the shell run block (VERSION="${{
github.event.release.tag_name }}"), creating a script-injection risk; change it
to pass the tag through an environment variable (e.g., set RELEASE_TAG: ${{
github.event.release.tag_name }} in the job/env) and then in the run block
reference that safe env var (e.g., use VERSION="${RELEASE_TAG#v}" and/or use
printf '%s' to avoid word-splitting) before writing to $GITHUB_OUTPUT; update
the lines that set and mutate VERSION to use RELEASE_TAG instead of direct
template interpolation.
- Around line 57-74: The dispatch loop currently calls
urllib.request.urlopen(req) inside the for repo in targets loop and the first
HTTPError aborts the whole script; wrap the body of each iteration (the
creation/use of req and the urllib.request.urlopen(req) call and print of
r.status) in a try/except that catches urllib.error.HTTPError and
urllib.error.URLError, log/print a clear failure message including repo and the
exception, continue to the next repo, and record a failure flag/counter so the
script can return a non-zero exit code after the loop if any dispatches failed;
reference the loop variables repo, req, targets and the response r when adding
the try/except and failure tracking.
---
Nitpick comments:
In @.docfx/templates/savvyio/layout/_master.tmpl:
- Around line 137-138: The template currently loads the third‑party script
directly via the script tag src "https://context7.com/widget.js" without
integrity or versioning; replace this by either (A) requesting a
versioned/immutable URL and published SRI from Context7 and then updating the
script tag to use that versioned URL with an integrity and crossorigin
attribute, or (B) if Context7 cannot provide versioned releases/SRI, download
and self‑host a pinned copy of the widget and change the script src to the
self‑hosted path (and add integrity/crossorigin for the pinned file); locate the
script inclusion in _master.tmpl and update the tag accordingly after obtaining
the versioned file or pinned asset.
In @.github/scripts/bump-nuget.py:
- Around line 25-26: The module currently reads TRIGGER_SOURCE and
TRIGGER_VERSION at import time which hinders tests; move the os.environ.get(...)
calls into main() (or a new initializer) and pass the retrieved values as
parameters into is_triggered_package (and any other functions that currently
rely on the module-level TRIGGER_SOURCE/TRIGGER_VERSION), update
is_triggered_package signature to accept trigger_source and trigger_version, and
adjust callers to supply those values so tests can control environment without
reloading the module.
- Line 129: The conditional return "return 0 if changes else 0" is dead code;
replace it with a simple "return 0" (keep the existing comment about returning 0
even if no changes) so the function in bump-nuget.py no longer contains the
useless if-else expression.
In @.github/workflows/service-update.yml:
- Around line 100-105: Add an inline comment near the two workflow steps that
gate on github.event.inputs.dry_run (the lines using if: ${{
github.event.inputs.dry_run == 'true' }} and if: ${{ github.event.inputs.dry_run
!= 'true' }}) explaining that this dry_run input is only populated for
workflow_dispatch events and will be empty for repository_dispatch events, and
note that to support dry-run behavior for dispatched events you'd need to pass a
dry_run field in client_payload of the repository_dispatch call; keep the
comment short and factual for future maintainers.
| run: | | ||
| SOURCE="${{ github.event.client_payload.source_repo || github.event.inputs.source_repo }}" | ||
| VERSION="${{ github.event.client_payload.source_version || github.event.inputs.source_version }}" | ||
| echo "source=$SOURCE" >> $GITHUB_OUTPUT | ||
| echo "version=$VERSION" >> $GITHUB_OUTPUT |
There was a problem hiding this comment.
Script injection risk: client_payload and inputs are user-controlled.
source_repo and source_version are interpolated directly into the shell via ${{ }}. For workflow_dispatch, a user can supply arbitrary strings. Pipe through environment variables to neutralize shell metacharacters.
🛡️ Proposed fix
- name: Resolve trigger inputs
id: trigger
run: |
- SOURCE="${{ github.event.client_payload.source_repo || github.event.inputs.source_repo }}"
- VERSION="${{ github.event.client_payload.source_version || github.event.inputs.source_version }}"
+ SOURCE="${DISPATCH_SOURCE:-$INPUT_SOURCE}"
+ VERSION="${DISPATCH_VERSION:-$INPUT_VERSION}"
echo "source=$SOURCE" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
+ env:
+ DISPATCH_SOURCE: ${{ github.event.client_payload.source_repo }}
+ DISPATCH_VERSION: ${{ github.event.client_payload.source_version }}
+ INPUT_SOURCE: ${{ github.event.inputs.source_repo }}
+ INPUT_VERSION: ${{ github.event.inputs.source_version }}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/service-update.yml around lines 37 - 41, The run step
currently injects user-controlled expressions directly into the shell via "${{
github.event.client_payload.source_repo || github.event.inputs.source_repo }}"
and "${{ github.event.client_payload.source_version ||
github.event.inputs.source_version }}", creating a script-injection risk; fix it
by moving those expressions into environment variables (set SOURCE and VERSION
via an env: mapping using the same ${{ ... }} expressions) and then reference
the safe env vars inside the run block when writing to $GITHUB_OUTPUT (use
printf/echo with the env vars SOURCE and VERSION rather than inlining the
expressions). Ensure you handle both fallbacks (client_payload vs inputs) in the
env assignments and only use the env vars inside the run.
| id: newver | ||
| run: | | ||
| CURRENT=$(grep -oP '(?<=## \[)[\d.]+(?=\])' CHANGELOG.md | head -1) | ||
| NEW=$(echo "$CURRENT" | awk -F. '{printf "%s.%s.%d", $1, $2, $3+1}') | ||
| BRANCH="v${NEW}/service-update" | ||
| echo "current=$CURRENT" >> $GITHUB_OUTPUT | ||
| echo "new=$NEW" >> $GITHUB_OUTPUT | ||
| echo "branch=$BRANCH" >> $GITHUB_OUTPUT |
There was a problem hiding this comment.
Version parsing produces garbage if CHANGELOG.md has no matching entries.
If grep finds no ## [x.y.z] entry, CURRENT is empty and the awk on Line 47 yields "..1", creating an invalid branch name v..1/service-update. Add a guard.
🐛 Proposed fix
- name: Determine new version for this repo
id: newver
run: |
CURRENT=$(grep -oP '(?<=## \[)[\d.]+(?=\])' CHANGELOG.md | head -1)
+ if [ -z "$CURRENT" ]; then
+ echo "::error::No version entry found in CHANGELOG.md"
+ exit 1
+ fi
NEW=$(echo "$CURRENT" | awk -F. '{printf "%s.%s.%d", $1, $2, $3+1}')
BRANCH="v${NEW}/service-update"📝 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.
| id: newver | |
| run: | | |
| CURRENT=$(grep -oP '(?<=## \[)[\d.]+(?=\])' CHANGELOG.md | head -1) | |
| NEW=$(echo "$CURRENT" | awk -F. '{printf "%s.%s.%d", $1, $2, $3+1}') | |
| BRANCH="v${NEW}/service-update" | |
| echo "current=$CURRENT" >> $GITHUB_OUTPUT | |
| echo "new=$NEW" >> $GITHUB_OUTPUT | |
| echo "branch=$BRANCH" >> $GITHUB_OUTPUT | |
| id: newver | |
| run: | | |
| CURRENT=$(grep -oP '(?<=## \[)[\d.]+(?=\])' CHANGELOG.md | head -1) | |
| if [ -z "$CURRENT" ]; then | |
| echo "::error::No version entry found in CHANGELOG.md" | |
| exit 1 | |
| fi | |
| NEW=$(echo "$CURRENT" | awk -F. '{printf "%s.%s.%d", $1, $2, $3+1}') | |
| BRANCH="v${NEW}/service-update" | |
| echo "current=$CURRENT" >> $GITHUB_OUTPUT | |
| echo "new=$NEW" >> $GITHUB_OUTPUT | |
| echo "branch=$BRANCH" >> $GITHUB_OUTPUT |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/service-update.yml around lines 44 - 51, The CURRENT
extraction can be empty which makes awk produce invalid version strings; update
the step that computes CURRENT/NEW/BRANCH to guard for an empty CURRENT by
checking if CURRENT is non-empty and if not either set CURRENT to a sane default
like "0.0.0" or fail early with an error and exit 1; then compute NEW using the
same awk expression on the validated CURRENT and derive BRANCH from NEW
(symbols: CURRENT, NEW, BRANCH, the grep/awk pipeline used to parse
CHANGELOG.md). Ensure the chosen behavior writes the correct values to
$GITHUB_OUTPUT (current, new, branch) and stops the workflow or uses the default
when CHANGELOG has no matching entries.
| for f in .nuget/*/PackageReleaseNotes.txt; do | ||
| [ -f "$f" ] || continue | ||
| TFM=$(grep -m1 "^Availability:" "$f" | sed 's/Availability: //' || echo ".NET 10, .NET 9 and .NET Standard 2.0") | ||
| ENTRY="Version: ${NEW}\nAvailability: ${TFM}\n \n# ALM\n- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)\n \n" | ||
| { printf "$ENTRY"; cat "$f"; } > "$f.tmp" && mv "$f.tmp" "$f" | ||
| done |
There was a problem hiding this comment.
TFM fallback never triggers — sed exits 0 even when grep finds no match.
On Line 72, grep -m1 … | sed … forms a pipeline whose exit status is that of sed, which is 0 even on empty input. The || echo "…" default is never reached, so TFM will be empty when the file has no Availability: line.
Use a separate assignment or grep alone with a conditional:
🐛 Proposed fix
- TFM=$(grep -m1 "^Availability:" "$f" | sed 's/Availability: //' || echo ".NET 10, .NET 9 and .NET Standard 2.0")
+ TFM=$(grep -m1 "^Availability:" "$f" | sed 's/Availability: //')
+ TFM="${TFM:-.NET 10, .NET 9 and .NET Standard 2.0}"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/service-update.yml around lines 70 - 75, The pipeline
setting TFM (TFM=$(grep -m1 "^Availability:" "$f" | sed 's/Availability: //' ||
echo "...")) never falls back because sed returns 0; fix by capturing grep
output first and then applying the fallback: run grep -m1 "^Availability:" "$f"
into a temp variable (e.g., raw_avail), check if raw_avail is empty and if so
set TFM to the default ".NET 10, .NET 9 and .NET Standard 2.0", otherwise strip
the "Availability: " prefix (using sed or parameter expansion) and assign that
to TFM; update the loop body that references TFM accordingly.
| run: | | ||
| NEW="${{ steps.newver.outputs.new }}" | ||
| BRANCH="${{ steps.newver.outputs.branch }}" | ||
| SOURCE="${{ steps.trigger.outputs.source }}" | ||
| SRC_VER="${{ steps.trigger.outputs.version }}" | ||
|
|
||
| git config user.name "codebelt-aicia[bot]" | ||
| git config user.email "codebelt-aicia[bot]@users.noreply.github.com" | ||
| git checkout -b "$BRANCH" | ||
| git add -A | ||
| git diff --cached --quiet && echo "Nothing changed - skipping PR." && exit 0 | ||
| git commit -m "V${NEW}/service update" | ||
| git push origin "$BRANCH" | ||
|
|
||
| echo "This is a service update that focuses on package dependencies." > pr_body.txt | ||
| echo "" >> pr_body.txt | ||
| echo "Automated changes:" >> pr_body.txt | ||
| echo "- Codebelt/Cuemon package versions bumped to latest compatible" >> pr_body.txt | ||
| echo "- PackageReleaseNotes.txt updated for v${NEW}" >> pr_body.txt | ||
| echo "- CHANGELOG.md entry added for v${NEW}" >> pr_body.txt | ||
| echo "" >> pr_body.txt | ||
| echo "Note: Third-party packages (Microsoft.Extensions.*, BenchmarkDotNet, etc.) are not auto-updated." >> pr_body.txt | ||
| echo "Use Dependabot or manual updates for those." >> pr_body.txt | ||
| echo "" >> pr_body.txt | ||
| echo "Generated by codebelt-aicia" >> pr_body.txt | ||
| if [ -n "$SOURCE" ] && [ -n "$SRC_VER" ]; then | ||
| echo "Triggered by: ${SOURCE} @ ${SRC_VER}" >> pr_body.txt | ||
| else | ||
| echo "Triggered by: manual workflow dispatch" >> pr_body.txt | ||
| fi | ||
|
|
||
| gh pr create --title "V${NEW}/service update" --body-file pr_body.txt --base main --head "$BRANCH" --assignee gimlichael |
There was a problem hiding this comment.
Steps that interpolate steps.*.outputs.* into run: blocks are also injection vectors.
Lines 109–112 interpolate outputs that ultimately derive from user-controlled input (source_repo, source_version, and the tag parsed from CHANGELOG.md). The same env-var pattern should be applied here to prevent injection through crafted version strings or repo names that flow into shell commands and gh pr create.
🛡️ Proposed fix (partial — apply env-var indirection for all interpolated outputs)
- name: Create branch and open PR
if: ${{ github.event.inputs.dry_run != 'true' }}
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
+ NEW: ${{ steps.newver.outputs.new }}
+ BRANCH: ${{ steps.newver.outputs.branch }}
+ SOURCE: ${{ steps.trigger.outputs.source }}
+ SRC_VER: ${{ steps.trigger.outputs.version }}
run: |
- NEW="${{ steps.newver.outputs.new }}"
- BRANCH="${{ steps.newver.outputs.branch }}"
- SOURCE="${{ steps.trigger.outputs.source }}"
- SRC_VER="${{ steps.trigger.outputs.version }}"
-
git config user.name "codebelt-aicia[bot]"📝 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.
| run: | | |
| NEW="${{ steps.newver.outputs.new }}" | |
| BRANCH="${{ steps.newver.outputs.branch }}" | |
| SOURCE="${{ steps.trigger.outputs.source }}" | |
| SRC_VER="${{ steps.trigger.outputs.version }}" | |
| git config user.name "codebelt-aicia[bot]" | |
| git config user.email "codebelt-aicia[bot]@users.noreply.github.com" | |
| git checkout -b "$BRANCH" | |
| git add -A | |
| git diff --cached --quiet && echo "Nothing changed - skipping PR." && exit 0 | |
| git commit -m "V${NEW}/service update" | |
| git push origin "$BRANCH" | |
| echo "This is a service update that focuses on package dependencies." > pr_body.txt | |
| echo "" >> pr_body.txt | |
| echo "Automated changes:" >> pr_body.txt | |
| echo "- Codebelt/Cuemon package versions bumped to latest compatible" >> pr_body.txt | |
| echo "- PackageReleaseNotes.txt updated for v${NEW}" >> pr_body.txt | |
| echo "- CHANGELOG.md entry added for v${NEW}" >> pr_body.txt | |
| echo "" >> pr_body.txt | |
| echo "Note: Third-party packages (Microsoft.Extensions.*, BenchmarkDotNet, etc.) are not auto-updated." >> pr_body.txt | |
| echo "Use Dependabot or manual updates for those." >> pr_body.txt | |
| echo "" >> pr_body.txt | |
| echo "Generated by codebelt-aicia" >> pr_body.txt | |
| if [ -n "$SOURCE" ] && [ -n "$SRC_VER" ]; then | |
| echo "Triggered by: ${SOURCE} @ ${SRC_VER}" >> pr_body.txt | |
| else | |
| echo "Triggered by: manual workflow dispatch" >> pr_body.txt | |
| fi | |
| gh pr create --title "V${NEW}/service update" --body-file pr_body.txt --base main --head "$BRANCH" --assignee gimlichael | |
| - name: Create branch and open PR | |
| if: ${{ github.event.inputs.dry_run != 'true' }} | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| NEW: ${{ steps.newver.outputs.new }} | |
| BRANCH: ${{ steps.newver.outputs.branch }} | |
| SOURCE: ${{ steps.trigger.outputs.source }} | |
| SRC_VER: ${{ steps.trigger.outputs.version }} | |
| run: | | |
| git config user.name "codebelt-aicia[bot]" | |
| git config user.email "codebelt-aicia[bot]@users.noreply.github.com" | |
| git checkout -b "$BRANCH" | |
| git add -A | |
| git diff --cached --quiet && echo "Nothing changed - skipping PR." && exit 0 | |
| git commit -m "V${NEW}/service update" | |
| git push origin "$BRANCH" | |
| echo "This is a service update that focuses on package dependencies." > pr_body.txt | |
| echo "" >> pr_body.txt | |
| echo "Automated changes:" >> pr_body.txt | |
| echo "- Codebelt/Cuemon package versions bumped to latest compatible" >> pr_body.txt | |
| echo "- PackageReleaseNotes.txt updated for v${NEW}" >> pr_body.txt | |
| echo "- CHANGELOG.md entry added for v${NEW}" >> pr_body.txt | |
| echo "" >> pr_body.txt | |
| echo "Note: Third-party packages (Microsoft.Extensions.*, BenchmarkDotNet, etc.) are not auto-updated." >> pr_body.txt | |
| echo "Use Dependabot or manual updates for those." >> pr_body.txt | |
| echo "" >> pr_body.txt | |
| echo "Generated by codebelt-aicia" >> pr_body.txt | |
| if [ -n "$SOURCE" ] && [ -n "$SRC_VER" ]; then | |
| echo "Triggered by: ${SOURCE} @ ${SRC_VER}" >> pr_body.txt | |
| else | |
| echo "Triggered by: manual workflow dispatch" >> pr_body.txt | |
| fi | |
| gh pr create --title "V${NEW}/service update" --body-file pr_body.txt --base main --head "$BRANCH" --assignee gimlichael |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/service-update.yml around lines 108 - 139, The run block
directly interpolates step outputs (NEW, BRANCH, SOURCE, SRC_VER) which can lead
to shell injection; change the step to declare these as step-level env variables
(env: NEW: ${{ steps.newver.outputs.new }}, BRANCH: ${{
steps.newver.outputs.branch }}, SOURCE: ${{ steps.trigger.outputs.source }},
SRC_VER: ${{ steps.trigger.outputs.version }}) and then reference the env vars
inside the run script (use "$NEW", "$BRANCH", "$SOURCE", "$SRC_VER") instead of
${ { steps.*.outputs.* } } inline; additionally add basic
validation/sanitization in the script (e.g., strip newlines, reject/control
dangerous characters or use an allowlist for BRANCH/NEW) before using values in
git/gh commands and ensure all expansions are quoted, especially for git
checkout -b "$BRANCH" and gh pr create arguments.
| run: | | ||
| VERSION="${{ github.event.release.tag_name }}" | ||
| VERSION="${VERSION#v}" | ||
| echo "version=$VERSION" >> $GITHUB_OUTPUT |
There was a problem hiding this comment.
Script injection risk: github.event.release.tag_name is user-controlled input.
Interpolating ${{ github.event.release.tag_name }} directly into a run: shell block allows arbitrary command execution if the tag contains shell metacharacters. Use an environment variable instead.
🛡️ Proposed fix
- name: Extract version from release tag
if: steps.check.outputs.has_targets == 'true'
id: version
run: |
- VERSION="${{ github.event.release.tag_name }}"
+ VERSION="${TAG_NAME}"
VERSION="${VERSION#v}"
echo "version=$VERSION" >> $GITHUB_OUTPUT
+ env:
+ TAG_NAME: ${{ github.event.release.tag_name }}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/trigger-downstream.yml around lines 32 - 35, The workflow
currently interpolates user-controlled github.event.release.tag_name directly
into the shell run block (VERSION="${{ github.event.release.tag_name }}"),
creating a script-injection risk; change it to pass the tag through an
environment variable (e.g., set RELEASE_TAG: ${{ github.event.release.tag_name
}} in the job/env) and then in the run block reference that safe env var (e.g.,
use VERSION="${RELEASE_TAG#v}" and/or use printf '%s' to avoid word-splitting)
before writing to $GITHUB_OUTPUT; update the lines that set and mutate VERSION
to use RELEASE_TAG instead of direct template interpolation.
| for repo in targets: | ||
| url = f'https://api.github.com/repos/codebeltnet/{repo}/dispatches' | ||
| payload = json.dumps({ | ||
| 'event_type': 'codebelt-service-update', | ||
| 'client_payload': { | ||
| 'source_repo': source, | ||
| 'source_version': version | ||
| } | ||
| }).encode() | ||
| req = urllib.request.Request(url, data=payload, method='POST', headers={ | ||
| 'Authorization': f'Bearer {token}', | ||
| 'Accept': 'application/vnd.github+json', | ||
| 'Content-Type': 'application/json', | ||
| 'X-GitHub-Api-Version': '2022-11-28' | ||
| }) | ||
| with urllib.request.urlopen(req) as r: | ||
| print(f'✓ Dispatched to {repo}: HTTP {r.status}') | ||
| EOF |
There was a problem hiding this comment.
Dispatch loop aborts on first HTTP error — remaining targets are skipped.
urllib.request.urlopen raises urllib.error.HTTPError on non-2xx responses. If one target repo fails (e.g., 404 for a deleted repo), the remaining repos never receive their dispatch. Wrap each iteration in a try/except and track failures.
🔧 Proposed fix
+ failed = []
for repo in targets:
- url = f'https://api.github.com/repos/codebeltnet/{repo}/dispatches'
- payload = json.dumps({
- 'event_type': 'codebelt-service-update',
- 'client_payload': {
- 'source_repo': source,
- 'source_version': version
- }
- }).encode()
- req = urllib.request.Request(url, data=payload, method='POST', headers={
- 'Authorization': f'Bearer {token}',
- 'Accept': 'application/vnd.github+json',
- 'Content-Type': 'application/json',
- 'X-GitHub-Api-Version': '2022-11-28'
- })
- with urllib.request.urlopen(req) as r:
- print(f'✓ Dispatched to {repo}: HTTP {r.status}')
+ try:
+ url = f'https://api.github.com/repos/codebeltnet/{repo}/dispatches'
+ payload = json.dumps({
+ 'event_type': 'codebelt-service-update',
+ 'client_payload': {
+ 'source_repo': source,
+ 'source_version': version
+ }
+ }).encode()
+ req = urllib.request.Request(url, data=payload, method='POST', headers={
+ 'Authorization': f'Bearer {token}',
+ 'Accept': 'application/vnd.github+json',
+ 'Content-Type': 'application/json',
+ 'X-GitHub-Api-Version': '2022-11-28'
+ })
+ with urllib.request.urlopen(req) as r:
+ print(f'✓ Dispatched to {repo}: HTTP {r.status}')
+ except Exception as e:
+ print(f'✗ Failed to dispatch to {repo}: {e}')
+ failed.append(repo)
+
+ if failed:
+ print(f'::error::Failed to dispatch to: {", ".join(failed)}')
+ sys.exit(1)📝 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.
| for repo in targets: | |
| url = f'https://api.github.com/repos/codebeltnet/{repo}/dispatches' | |
| payload = json.dumps({ | |
| 'event_type': 'codebelt-service-update', | |
| 'client_payload': { | |
| 'source_repo': source, | |
| 'source_version': version | |
| } | |
| }).encode() | |
| req = urllib.request.Request(url, data=payload, method='POST', headers={ | |
| 'Authorization': f'Bearer {token}', | |
| 'Accept': 'application/vnd.github+json', | |
| 'Content-Type': 'application/json', | |
| 'X-GitHub-Api-Version': '2022-11-28' | |
| }) | |
| with urllib.request.urlopen(req) as r: | |
| print(f'✓ Dispatched to {repo}: HTTP {r.status}') | |
| EOF | |
| failed = [] | |
| for repo in targets: | |
| try: | |
| url = f'https://api.github.com/repos/codebeltnet/{repo}/dispatches' | |
| payload = json.dumps({ | |
| 'event_type': 'codebelt-service-update', | |
| 'client_payload': { | |
| 'source_repo': source, | |
| 'source_version': version | |
| } | |
| }).encode() | |
| req = urllib.request.Request(url, data=payload, method='POST', headers={ | |
| 'Authorization': f'Bearer {token}', | |
| 'Accept': 'application/vnd.github+json', | |
| 'Content-Type': 'application/json', | |
| 'X-GitHub-Api-Version': '2022-11-28' | |
| }) | |
| with urllib.request.urlopen(req) as r: | |
| print(f'✓ Dispatched to {repo}: HTTP {r.status}') | |
| except Exception as e: | |
| print(f'✗ Failed to dispatch to {repo}: {e}') | |
| failed.append(repo) | |
| if failed: | |
| print(f'::error::Failed to dispatch to: {", ".join(failed)}') | |
| sys.exit(1) | |
| EOF |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/trigger-downstream.yml around lines 57 - 74, The dispatch
loop currently calls urllib.request.urlopen(req) inside the for repo in targets
loop and the first HTTPError aborts the whole script; wrap the body of each
iteration (the creation/use of req and the urllib.request.urlopen(req) call and
print of r.status) in a try/except that catches urllib.error.HTTPError and
urllib.error.URLError, log/print a clear failure message including repo and the
exception, continue to the next repo, and record a failure flag/counter so the
script can return a non-zero exit code after the loop if any dispatches failed;
reference the loop variables repo, req, targets and the response r when adding
the try/except and failure tracking.
There was a problem hiding this comment.
Pull request overview
Adds GitHub Actions–based automation to propagate “service update” dependency bumps across Codebelt repos, plus a small DocFX template change for the documentation site.
Changes:
- Introduces a release-triggered workflow to dispatch
repository_dispatchevents to downstream repositories based on.github/dispatch-targets.json. - Adds a downstream handler workflow that bumps in-house NuGet package versions, updates release notes/changelog, and opens an automated PR.
- Adds a Python helper to selectively bump package versions in
Directory.Packages.props, plus an external widget script to the DocFX template.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
.github/workflows/trigger-downstream.yml |
Dispatches a service-update event to downstream repos on non-prerelease releases. |
.github/workflows/service-update.yml |
Receives dispatch/manual triggers, bumps deps + release notes/changelog, and opens a PR. |
.github/scripts/bump-nuget.py |
Updates Directory.Packages.props package versions based on trigger source/version. |
.github/dispatch-targets.json |
Defines downstream repo targets for dispatch. |
.docfx/templates/savvyio/layout/_master.tmpl |
Loads an external Context7 widget script on documentation pages. |
| required: false | ||
| default: '' |
There was a problem hiding this comment.
source_version is optional/default empty, but bump-nuget.py requires TRIGGER_VERSION and will fail the workflow if it’s not provided. Either mark this input required for manual dispatch, or guard the bump step when the resolved version is empty.
| required: false | |
| default: '' | |
| required: true |
| echo "- Codebelt/Cuemon package versions bumped to latest compatible" >> pr_body.txt | ||
| echo "- PackageReleaseNotes.txt updated for v${NEW}" >> pr_body.txt | ||
| echo "- CHANGELOG.md entry added for v${NEW}" >> pr_body.txt | ||
| echo "" >> pr_body.txt | ||
| echo "Note: Third-party packages (Microsoft.Extensions.*, BenchmarkDotNet, etc.) are not auto-updated." >> pr_body.txt | ||
| echo "Use Dependabot or manual updates for those." >> pr_body.txt |
There was a problem hiding this comment.
The PR body claims "Codebelt/Cuemon package versions" are bumped and that only third-party packages are skipped, but bump-nuget.py only bumps packages belonging to the triggering source repo’s prefixes. Consider adjusting the wording to reflect "packages from ${SOURCE}" (and avoid calling other in-house packages "third-party").
| echo "- Codebelt/Cuemon package versions bumped to latest compatible" >> pr_body.txt | |
| echo "- PackageReleaseNotes.txt updated for v${NEW}" >> pr_body.txt | |
| echo "- CHANGELOG.md entry added for v${NEW}" >> pr_body.txt | |
| echo "" >> pr_body.txt | |
| echo "Note: Third-party packages (Microsoft.Extensions.*, BenchmarkDotNet, etc.) are not auto-updated." >> pr_body.txt | |
| echo "Use Dependabot or manual updates for those." >> pr_body.txt | |
| echo "- Package versions from the triggering source repository bumped to latest compatible" >> pr_body.txt | |
| echo "- PackageReleaseNotes.txt updated for v${NEW}" >> pr_body.txt | |
| echo "- CHANGELOG.md entry added for v${NEW}" >> pr_body.txt | |
| echo "" >> pr_body.txt | |
| echo "Note: Only packages whose IDs match the triggering source repository's prefixes are auto-updated." >> pr_body.txt | |
| echo "Other package families (e.g., Microsoft.Extensions.*, BenchmarkDotNet) are not auto-updated by this workflow; use Dependabot or manual updates for those." >> pr_body.txt |
| Only updates packages published by the triggering source repo. | ||
| Does NOT update Microsoft.Extensions.*, BenchmarkDotNet, or other third-party packages. | ||
| Does NOT parse TFM conditions - only bumps Codebelt/Cuemon/Savvyio packages to the triggering version. |
There was a problem hiding this comment.
Docstring says it "only bumps Codebelt/Cuemon/Savvyio packages", but the implementation bumps only packages whose IDs match the triggering repo’s configured prefixes (SOURCE_PACKAGE_MAP). Update the docstring (or broaden the implementation) so documentation matches behavior.
| if skipped_third_party: | ||
| print() | ||
| print(f"Skipped {len(skipped_third_party)} third-party package(s):") | ||
| print("\n".join(skipped_third_party[:5])) # Show first 5 | ||
| if len(skipped_third_party) > 5: |
There was a problem hiding this comment.
The script prints "Skipped … third-party package(s)" for every non-matching package, but non-matching packages can still be in-house (just owned by a different source repo). Consider renaming the variable/message to something like "skipped (not from trigger source)" and reserve "third-party" for an explicit classification.
| import sys | ||
| from typing import Dict, List | ||
|
|
||
| TRIGGER_SOURCE = os.environ.get("TRIGGER_SOURCE", "") |
There was a problem hiding this comment.
TRIGGER_SOURCE is used as a case-sensitive key into SOURCE_PACKAGE_MAP. If the dispatch payload uses different casing, no packages will be updated (but the workflow can still open a PR). Normalize TRIGGER_SOURCE (e.g., .strip().lower()) before looking it up.
| TRIGGER_SOURCE = os.environ.get("TRIGGER_SOURCE", "") | |
| TRIGGER_SOURCE = os.environ.get("TRIGGER_SOURCE", "").strip().lower() |
| </div> | ||
| </footer> | ||
|
|
||
| <script async src="https://context7.com/widget.js" data-library="/codebeltnet/savvyio"></script> |
There was a problem hiding this comment.
This template now loads third-party JS from https://context7.com/widget.js on every documentation page. Because this executes with full page privileges, consider making it opt-in via DocFX metadata/config (so it can be disabled) and documenting security/privacy implications (and any CSP strategy) to reduce supply-chain risk.
| <script async src="https://context7.com/widget.js" data-library="/codebeltnet/savvyio"></script> | |
| {{^_disableContext7Script}} | |
| <script async src="https://context7.com/widget.js" data-library="{{_context7Library}}{{^_context7Library}}/codebeltnet/savvyio{{/_context7Library}}"></script> | |
| {{/_disableContext7Script}} |
| - name: Generate codebelt-aicia token | ||
| id: app-token | ||
| uses: actions/create-github-app-token@v1 | ||
| with: | ||
| app-id: ${{ vars.CODEBELT_AICIA_APP_ID }} |
There was a problem hiding this comment.
A GitHub App token is generated, but later git push origin "$BRANCH" will still authenticate using the credentials from actions/checkout (defaults to GITHUB_TOKEN). If the intent is that the App identity owns the branch/commits (or if GITHUB_TOKEN push is restricted), configure checkout/remote so git operations use steps.app-token.outputs.token too.
| required: false | ||
| default: '' | ||
| source_version: | ||
| description: 'Version released by source (e.g. 10.3.0)' | ||
| required: false |
There was a problem hiding this comment.
source_repo is optional/default empty, but later the workflow always runs bump-nuget.py, which exits non-zero when either trigger variable is missing. Consider making this input required for workflow_dispatch, or add an early validation/skip path (especially for dry_run).
| required: false | |
| default: '' | |
| source_version: | |
| description: 'Version released by source (e.g. 10.3.0)' | |
| required: false | |
| required: true | |
| default: '' | |
| source_version: | |
| description: 'Version released by source (e.g. 10.3.0)' | |
| required: true |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #48 +/- ##
=======================================
Coverage 79.11% 79.11%
=======================================
Files 177 177
Lines 3711 3711
Branches 365 365
=======================================
Hits 2936 2936
Misses 774 774
Partials 1 1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|




This pull request introduces a new automated workflow for managing service update propagation across repositories, focusing on dependency updates and release coordination. It adds scripts and workflows to trigger and handle downstream package version bumps, update changelogs, and automate pull request creation, while ensuring only relevant in-house packages are updated. Additionally, a small enhancement is made to the documentation site template.
Automation for Service Update Propagation:
Added a new workflow
.github/workflows/trigger-downstream.ymlto automatically trigger service update events in downstream repositories when a new release is published, using a list of targets from.github/dispatch-targets.json. This workflow securely dispatches events with the relevant version and source repository information. [1] [2]Introduced
.github/workflows/service-update.ymlto handle incoming service update events or manual triggers. This workflow bumps in-house package dependencies, updates release notes and changelogs, and creates a pull request for the changes. It supports dry-run mode and uses a GitHub App token for authentication.Dependency Management Improvements:
.github/scripts/bump-nuget.py, a Python script to selectively update only Codebelt/Cuemon/Savvyio (in-house) package versions inDirectory.Packages.propsbased on trigger source and version, while skipping third-party packages. The script prints a summary of changes and skipped packages.Documentation Enhancement:
.docfx/templates/savvyio/layout/_master.tmplto asynchronously load an external widget for documentation improvements.Summary by CodeRabbit
New Features
Chores