Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .docfx/templates/shared-kernel/layout/_master.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@
</div>
</div>
</footer>
<script async src="https://context7.com/widget.js" data-library="/codebeltnet/shared-kernel"></script>
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

A third-party JavaScript widget from context7.com is being loaded without any documentation, security review notes, or explanation of its purpose in the PR description. Loading external scripts can introduce security and privacy risks, including potential XSS vulnerabilities, data leakage, or dependency on external service availability. Consider documenting the purpose of this widget, conducting a security review, implementing a Content Security Policy (CSP), and adding a Subresource Integrity (SRI) hash to ensure the script hasn't been tampered with.

Suggested change
<script async src="https://context7.com/widget.js" data-library="/codebeltnet/shared-kernel"></script>
<!-- Context7 widget script removed pending security and privacy review. -->

Copilot uses AI. Check for mistakes.
</body>
{{/redirect_url}}
</html>
1 change: 1 addition & 0 deletions .github/dispatch-targets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[ ]
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The dispatch-targets.json file contains an empty array, which means no downstream repositories will receive service update notifications. While this is valid JSON and the workflow correctly handles this case by checking the count, it raises the question of whether this file should be committed in its current state. If the intention is to have no downstream targets initially, consider adding a comment in the PR or in accompanying documentation explaining this is intentional and will be populated later.

Copilot uses AI. Check for mistakes.
133 changes: 133 additions & 0 deletions .github/scripts/bump-nuget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#!/usr/bin/env python3
"""
Simplified package bumping for Codebelt service updates (Option B).

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.

Usage:
TRIGGER_SOURCE=cuemon TRIGGER_VERSION=10.3.0 python3 bump-nuget.py

Behavior:
- If TRIGGER_SOURCE is "cuemon" and TRIGGER_VERSION is "10.3.0":
- Cuemon.Core: 10.2.1 → 10.3.0
- Cuemon.Extensions.IO: 10.2.1 → 10.3.0
- Microsoft.Extensions.Hosting: 9.0.13 → UNCHANGED (not a Codebelt package)
- BenchmarkDotNet: 0.15.8 → UNCHANGED (not a Codebelt package)
"""

import re
import os
import sys
from typing import Dict, List

TRIGGER_SOURCE = os.environ.get("TRIGGER_SOURCE", "")
TRIGGER_VERSION = os.environ.get("TRIGGER_VERSION", "")

# Map of source repos to their package ID prefixes
SOURCE_PACKAGE_MAP: Dict[str, List[str]] = {
"cuemon": ["Cuemon."],
"xunit": ["Codebelt.Extensions.Xunit"],
"benchmarkdotnet": ["Codebelt.Extensions.BenchmarkDotNet"],
"bootstrapper": ["Codebelt.Bootstrapper"],
"newtonsoft-json": [
"Codebelt.Extensions.Newtonsoft.Json",
"Codebelt.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft",
],
"aws-signature-v4": ["Codebelt.Extensions.AspNetCore.Authentication.AwsSignature"],
"unitify": ["Codebelt.Unitify"],
"yamldotnet": [
"Codebelt.Extensions.YamlDotNet",
"Codebelt.Extensions.AspNetCore.Mvc.Formatters.Text.Yaml",
],
"globalization": ["Codebelt.Extensions.Globalization"],
"asp-versioning": ["Codebelt.Extensions.Asp.Versioning"],
"swashbuckle-aspnetcore": ["Codebelt.Extensions.Swashbuckle"],
"savvyio": ["Savvyio."],
"shared-kernel": [],
}


Comment on lines +48 to +51
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The SOURCE_PACKAGE_MAP includes an entry for "shared-kernel" with an empty list of package prefixes. This means if TRIGGER_SOURCE is "shared-kernel", the is_triggered_package function will always return False for any package name since there are no prefixes to match against. This could lead to unexpected behavior where no packages are updated when triggered by shared-kernel releases. Either remove this entry if shared-kernel doesn't publish packages, or add the appropriate package prefixes if it does.

Suggested change
"shared-kernel": [],
}
}

Copilot uses AI. Check for mistakes.
def is_triggered_package(package_name: str) -> bool:
"""Check if package is published by the triggering source repo."""
if not TRIGGER_SOURCE:
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The is_triggered_package function has a type annotation indicating it returns bool, but when TRIGGER_SOURCE is not set, the function returns False without logging or warning about this condition. This is a silent failure mode that could make debugging difficult when the script unexpectedly skips all packages. Consider adding a debug print statement or logging when TRIGGER_SOURCE is empty to help diagnose configuration issues.

Suggested change
if not TRIGGER_SOURCE:
if not TRIGGER_SOURCE:
print(
"Debug: TRIGGER_SOURCE is not set; is_triggered_package will always return False."
)

Copilot uses AI. Check for mistakes.
return False
prefixes = SOURCE_PACKAGE_MAP.get(TRIGGER_SOURCE, [])
return any(package_name.startswith(prefix) for prefix in prefixes)


def main():
if not TRIGGER_SOURCE or not TRIGGER_VERSION:
print(
"Error: TRIGGER_SOURCE and TRIGGER_VERSION environment variables required"
)
print(f" TRIGGER_SOURCE={TRIGGER_SOURCE}")
print(f" TRIGGER_VERSION={TRIGGER_VERSION}")
sys.exit(1)

# Strip 'v' prefix if present in version
target_version = TRIGGER_VERSION.lstrip("v")

print(f"Trigger: {TRIGGER_SOURCE} @ {target_version}")
print(f"Only updating packages from: {TRIGGER_SOURCE}")
print()

try:
with open("Directory.Packages.props", "r") as f:
content = f.read()
except FileNotFoundError:
print("Error: Directory.Packages.props not found")
sys.exit(1)

changes = []
skipped_third_party = []

def replace_version(m: re.Match) -> str:
pkg = m.group(1)
current = m.group(2)

if not is_triggered_package(pkg):
skipped_third_party.append(f" {pkg} (skipped - not from {TRIGGER_SOURCE})")
return m.group(0)

if target_version != current:
changes.append(f" {pkg}: {current} → {target_version}")
return m.group(0).replace(
f'Version="{current}"', f'Version="{target_version}"'
)
Comment on lines +94 to +98
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The script does not validate whether the target version is greater than the current version before performing updates. This could lead to downgrading packages or setting them to the same version, which may not be the intended behavior for a service update. Consider adding a version comparison check to ensure the target version is actually newer than the current version before making changes.

Copilot uses AI. Check for mistakes.

return m.group(0)

# Match PackageVersion elements (handles multiline)
pattern = re.compile(
r"<PackageVersion\b"
r'(?=[^>]*\bInclude="([^"]+)")'
r'(?=[^>]*\bVersion="([^"]+)")'
r"[^>]*>",
re.DOTALL,
)
new_content = pattern.sub(replace_version, content)

# Show results
if changes:
print(f"Updated {len(changes)} package(s) from {TRIGGER_SOURCE}:")
print("\n".join(changes))
else:
print(f"No packages from {TRIGGER_SOURCE} needed updating.")

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:
print(f" ... and {len(skipped_third_party) - 5} more")

with open("Directory.Packages.props", "w") as f:
f.write(new_content)

return 0 if changes else 0 # Return 0 even if no changes (not an error)
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The return statement is redundant as it returns 0 in both branches of the ternary expression. This appears to be dead code or incomplete logic. Either remove the ternary operator and simply return 0, or implement proper exit code logic where the script returns a non-zero code when no changes are made if that's the desired behavior for downstream workflow logic.

Suggested change
return 0 if changes else 0 # Return 0 even if no changes (not an error)
return 0 # Return 0 even if no changes (not an error)

Copilot uses AI. Check for mistakes.


if __name__ == "__main__":
sys.exit(main())
139 changes: 139 additions & 0 deletions .github/workflows/service-update.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
name: Service Update
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The PR title "Docfx/context7 chat" does not accurately reflect the primary changes in this pull request. The majority of the changes introduce service update automation workflows and NuGet package version management, while the context7 widget is a minor addition to a documentation template. The title should be more descriptive of the main functionality being added, such as "Add service update automation and downstream triggering workflows" or similar. The current title may confuse reviewers about the scope and purpose of these changes.

Copilot uses AI. Check for mistakes.

on:
repository_dispatch:
types: [codebelt-service-update]
workflow_dispatch:
inputs:
source_repo:
description: 'Triggering source repo name (e.g. cuemon)'
required: false
default: ''
source_version:
description: 'Version released by source (e.g. 10.3.0)'
required: false
default: ''
dry_run:
type: boolean
description: 'Dry run — show changes but do not commit or open PR'
default: false

permissions:
contents: write
pull-requests: write

jobs:
service-update:
runs-on: ubuntu-24.04

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- 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 }}"
echo "source=$SOURCE" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT

Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The workflow does not validate that TRIGGER_SOURCE and TRIGGER_VERSION are set before proceeding with subsequent steps. When triggered via repository_dispatch or workflow_dispatch without inputs, these could be empty strings, causing the workflow to run but perform no meaningful updates. The bump-nuget.py script will fail with a sys.exit(1), but other steps will continue. Consider adding early validation in the workflow to check if these values are set and skip the workflow run or fail fast if they're missing.

Suggested change
- name: Validate trigger inputs
run: |
SOURCE="${{ steps.trigger.outputs.source }}"
VERSION="${{ steps.trigger.outputs.version }}"
if [ -z "$SOURCE" ] || [ -z "$VERSION" ]; then
echo "Error: TRIGGER_SOURCE and TRIGGER_VERSION must be set. Got SOURCE='$SOURCE', VERSION='$VERSION'."
echo "Ensure repository_dispatch or workflow_dispatch provides non-empty 'source_repo' and 'source_version'."
exit 1
fi

Copilot uses AI. Check for mistakes.
- name: Determine new version for this repo
id: newver
run: |
CURRENT=$(grep -oP '(?<=## \[)[\d.]+(?=\])' CHANGELOG.md | head -1)
NEW=$(echo "$CURRENT" | awk -F. '{printf "%s.%s.%d", $1, $2, $3+1}')
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The version determination logic using grep and awk assumes the CHANGELOG.md format is consistent and that the first match is always a valid semantic version. If the CHANGELOG.md is empty, malformed, or doesn't contain a version in the expected format, this step will fail or produce incorrect results. Consider adding validation to ensure CURRENT contains a valid version before attempting the awk transformation, and handle the case where no version is found.

Suggested change
NEW=$(echo "$CURRENT" | awk -F. '{printf "%s.%s.%d", $1, $2, $3+1}')
if ! echo "$CURRENT" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "ERROR: Unable to determine current semantic version from CHANGELOG.md (got: '$CURRENT')." >&2
echo "Ensure CHANGELOG.md contains a heading like '## [1.2.3]'." >&2
exit 1
fi
NEW=$(awk -F. '{printf "%s.%s.%d", $1, $2, $3+1}' <<< "$CURRENT")

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The awk command for version bumping only increments the patch version (third component). This means the workflow always creates patch releases (e.g., 0.5.2 → 0.5.3) and cannot handle minor or major version bumps. For service updates this might be intentional, but it's inflexible and will cause issues if a major or minor version bump is needed for breaking changes or significant updates. Consider adding workflow inputs or logic to support different version bump strategies (major, minor, patch).

Copilot uses AI. Check for mistakes.
BRANCH="v${NEW}/service-update"
echo "current=$CURRENT" >> $GITHUB_OUTPUT
echo "new=$NEW" >> $GITHUB_OUTPUT
echo "branch=$BRANCH" >> $GITHUB_OUTPUT

- name: Generate codebelt-aicia token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ vars.CODEBELT_AICIA_APP_ID }}
private-key: ${{ secrets.CODEBELT_AICIA_PRIVATE_KEY }}
owner: codebeltnet

- name: Bump NuGet packages
run: python3 .github/scripts/bump-nuget.py
env:
TRIGGER_SOURCE: ${{ steps.trigger.outputs.source }}
TRIGGER_VERSION: ${{ steps.trigger.outputs.version }}

- name: Update PackageReleaseNotes.txt
run: |
NEW="${{ steps.newver.outputs.new }}"
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")
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The TFM (Target Framework Moniker) extraction using grep relies on the "Availability:" line being present and correctly formatted in the existing PackageReleaseNotes.txt file. If this line is missing or malformed, the sed command will fail silently and TFM will be set to the fallback value, potentially using incorrect framework information for new releases. Consider adding validation or error handling to ensure the extracted TFM value is reasonable before using it.

Suggested change
TFM=$(grep -m1 "^Availability:" "$f" | sed 's/Availability: //' || echo ".NET 10, .NET 9 and .NET Standard 2.0")
RAW_AVAIL_LINE=$(grep -m1 "^Availability:" "$f" || true)
if [ -n "$RAW_AVAIL_LINE" ]; then
TFM=${RAW_AVAIL_LINE#Availability:}
TFM=$(echo "$TFM" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
else
TFM=""
fi
if [ -z "$TFM" ]; then
echo "Warning: Using fallback TFM for $f because Availability line is missing or malformed."
TFM=".NET 10, .NET 9 and .NET Standard 2.0"
fi

Copilot uses AI. Check for mistakes.
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
Comment on lines +70 to +75
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The PackageReleaseNotes.txt update step uses a shell loop with 'continue' to skip missing files, but the logic doesn't validate whether any files were actually found and updated. If the .nuget/*/PackageReleaseNotes.txt pattern matches no files (or all files fail the conditional check), the step will succeed silently without updating any release notes. Consider adding validation after the loop to ensure at least one file was processed, or logging a message when no files are found.

Suggested change
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
PROCESSED=0
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" && PROCESSED=1
done
if [ "$PROCESSED" -eq 0 ]; then
echo "No PackageReleaseNotes.txt files found to update under .nuget/*/PackageReleaseNotes.txt"
fi

Copilot uses AI. Check for mistakes.

- name: Update CHANGELOG.md
run: |
python3 - <<'EOF'
import os, re
from datetime import date
new_ver = os.environ['NEW_VERSION']
today = date.today().isoformat()
entry = f"## [{new_ver}] - {today}\n\nThis is a service update that focuses on package dependencies.\n\n"
with open("CHANGELOG.md") as f:
content = f.read()
idx = content.find("## [")
content = (content[:idx] + entry + content[idx:]) if idx != -1 else (content + entry)
with open("CHANGELOG.md", "w") as f:
f.write(content)
print(f"CHANGELOG updated for v{new_ver}")
EOF
env:
NEW_VERSION: ${{ steps.newver.outputs.new }}

# Note: Docker image bumps removed in favor of manual updates
# The automated selection was picking wrong variants (e.g., mono-* instead of standard)
# TODO: Move to hosted service for smarter image selection

- name: Show diff (dry run)
if: ${{ github.event.inputs.dry_run == 'true' }}
run: git diff

- name: Create branch and open PR
if: ${{ github.event.inputs.dry_run != 'true' }}
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
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
Comment on lines +104 to +139
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The workflow creates a branch and attempts to create a PR but does not check if a PR or branch with the same name already exists. If the workflow runs multiple times (e.g., due to multiple releases or manual triggers), it will fail with 'git push' or 'gh pr create' errors when trying to create duplicate branches or PRs. Consider adding logic to check for existing branches/PRs and either skip creation, update the existing branch, or use unique identifiers in the branch name.

Copilot uses AI. Check for mistakes.
78 changes: 78 additions & 0 deletions .github/workflows/trigger-downstream.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: Trigger Downstream Service Updates

on:
release:
types: [published]

jobs:
dispatch:
if: github.event.release.prerelease == false
runs-on: ubuntu-24.04
permissions:
contents: read

steps:
- name: Checkout (to read dispatch-targets.json)
uses: actions/checkout@v4

- name: Check for dispatch targets
id: check
run: |
if [ ! -f .github/dispatch-targets.json ]; then
echo "No dispatch-targets.json found, skipping."
echo "has_targets=false" >> $GITHUB_OUTPUT
exit 0
fi
COUNT=$(python3 -c "import json; print(len(json.load(open('.github/dispatch-targets.json'))))")
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The inline Python snippet for reading the JSON count doesn't handle potential JSON parsing errors. If the dispatch-targets.json file is malformed or corrupted, the python3 command will fail with an uncaught exception, causing the step to fail without a clear error message. Consider wrapping the JSON parsing in a try-except block or adding validation to provide a more informative error message when the file is invalid.

Suggested change
COUNT=$(python3 -c "import json; print(len(json.load(open('.github/dispatch-targets.json'))))")
COUNT=$(python3 - << 'EOF'
import json
import sys
path = '.github/dispatch-targets.json'
try:
with open(path, 'r', encoding='utf-8') as f:
data = json.load(f)
except json.JSONDecodeError as exc:
print(f"Error: Failed to parse {path}: {exc}", file=sys.stderr)
sys.exit(1)
# Expect a JSON array; adjust here if a different structure is intended.
if not isinstance(data, list):
print(f"Error: Expected a JSON array in {path}, got {type(data).__name__}", file=sys.stderr)
sys.exit(1)
print(len(data))
EOF
)

Copilot uses AI. Check for mistakes.
echo "has_targets=$([ $COUNT -gt 0 ] && echo true || echo false)" >> $GITHUB_OUTPUT

- name: Extract version from release tag
if: steps.check.outputs.has_targets == 'true'
id: version
run: |
VERSION="${{ github.event.release.tag_name }}"
VERSION="${VERSION#v}"
echo "version=$VERSION" >> $GITHUB_OUTPUT

- name: Generate codebelt-aicia token
if: steps.check.outputs.has_targets == 'true'
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ vars.CODEBELT_AICIA_APP_ID }}
private-key: ${{ secrets.CODEBELT_AICIA_PRIVATE_KEY }}
owner: codebeltnet

- name: Dispatch to downstream repos
if: steps.check.outputs.has_targets == 'true'
run: |
python3 - <<'EOF'
import json, urllib.request, os, sys

targets = json.load(open('.github/dispatch-targets.json'))
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The inline Python script uses json.load with open() but doesn't use a context manager (with statement) to ensure the file is properly closed. While this works in a short-lived script context, it's not following Python best practices. Consider using 'with open('.github/dispatch-targets.json') as f: targets = json.load(f)' for proper resource management and to follow idiomatic Python patterns.

Copilot uses AI. Check for mistakes.
token = os.environ['GH_TOKEN']
version = os.environ['VERSION']
source = os.environ['SOURCE_REPO']

for repo in targets:
url = f'https://api.github.com/repos/codebeltnet/{repo}/dispatches'
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The hardcoded organization name 'codebeltnet' in the URL construction limits reusability and makes the workflow less flexible. If this workflow needs to be used across different organizations or if the organization name changes, the script will require modification. Consider using a GitHub Actions variable or environment variable (e.g., github.repository_owner) to make this configurable.

Copilot uses AI. Check for mistakes.
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}')
Comment on lines +72 to +73
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The urllib.request.urlopen call does not handle potential HTTP errors. If the API request fails with a 4xx or 5xx status code, it will raise an HTTPError exception, causing the workflow to fail without attempting to dispatch to remaining repositories. Consider adding error handling to catch HTTPError exceptions, log the failure, and continue with the next repository in the list.

Copilot uses AI. Check for mistakes.
EOF
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
VERSION: ${{ steps.version.outputs.version }}
SOURCE_REPO: ${{ github.event.repository.name }}
Loading