-
Notifications
You must be signed in to change notification settings - Fork 0
fix: prevent PR titles with special characters from breaking Slack changelog workflow #38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,14 +21,13 @@ jobs: | |
|
|
||
| - name: Get PR Details | ||
| id: pr-details | ||
| env: | ||
| PR_NUMBER: ${{ github.event.pull_request.number }} | ||
| PR_TITLE: ${{ github.event.pull_request.title }} | ||
| PR_AUTHOR: ${{ github.event.pull_request.user.login }} | ||
| PR_URL: ${{ github.event.pull_request.html_url }} | ||
| BRANCH_NAME: ${{ github.event.pull_request.head.ref }} | ||
| run: | | ||
| # Extract PR information | ||
| PR_NUMBER="${{ github.event.pull_request.number }}" | ||
| PR_TITLE="${{ github.event.pull_request.title }}" | ||
| PR_AUTHOR="${{ github.event.pull_request.user.login }}" | ||
| PR_URL="${{ github.event.pull_request.html_url }}" | ||
| BRANCH_NAME="${{ github.event.pull_request.head.ref }}" | ||
|
|
||
| # Get changed files from the merge commit | ||
| echo "Getting files from merge commit..." | ||
| git diff --name-only HEAD~1 HEAD > changed_files.txt | ||
|
|
@@ -74,34 +73,42 @@ jobs: | |
|
|
||
| - name: Generate Changelog Message | ||
| id: changelog | ||
| env: | ||
| PR_TITLE: ${{ steps.pr-details.outputs.pr_title }} | ||
| PR_AUTHOR: ${{ steps.pr-details.outputs.pr_author }} | ||
| PR_URL: ${{ steps.pr-details.outputs.pr_url }} | ||
| TEMPLATE_FOLDERS: ${{ steps.pr-details.outputs.template_folders }} | ||
| TOTAL_TEMPLATES: ${{ steps.pr-details.outputs.total_templates }} | ||
| run: | | ||
| # Create a formatted changelog message | ||
| TIMESTAMP=$(TZ='Europe/Brussels' date +"%d/%m/%Y %H:%M") | ||
|
|
||
| # Build the complete message (using clean Slack formatting) | ||
| MESSAGE=" | ||
| 📝 ${{ steps.pr-details.outputs.pr_title }} | ||
| 👤 ${{ steps.pr-details.outputs.pr_author }} | ||
| 🔗 ${{ steps.pr-details.outputs.pr_url }} | ||
| 📝 $PR_TITLE | ||
| 👤 $PR_AUTHOR | ||
| 🔗 $PR_URL | ||
| ⏰ $TIMESTAMP | ||
|
|
||
| Templates: | ||
| ${{ steps.pr-details.outputs.template_folders }}" | ||
| $TEMPLATE_FOLDERS" | ||
|
|
||
| # If more than 10 templates, add a note | ||
| if [ ${{ steps.pr-details.outputs.total_templates }} -gt 10 ]; then | ||
| if [ "$TOTAL_TEMPLATES" -gt 10 ]; then | ||
| MESSAGE="$MESSAGE | ||
| _...and $((${{ steps.pr-details.outputs.total_templates }} - 10)) more templates_" | ||
| _...and $((TOTAL_TEMPLATES - 10)) more templates_" | ||
| fi | ||
|
|
||
| # Set output (escape for JSON) | ||
| # Escape quotes and newlines for JSON | ||
| MESSAGE_ESCAPED=$(echo "$MESSAGE" | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g') | ||
| # Set output (escape for JSON: backslashes first, then quotes, then newlines) | ||
| MESSAGE_ESCAPED=$(echo "$MESSAGE" | sed 's/\\/\\\\/g; s/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g') | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- workflow context ---'
nl -ba .github/workflows/slack_changelog.yml | sed -n '80,125p'
printf '%s\n' '--- jq usage ---'
rg -n -C 3 'jq|SLACK_MESSAGE|MESSAGE_ESCAPED|curl' .github/workflows .github 2>/dev/null || true
printf '%s\n' '--- available tools ---'
command -v jq || true
command -v actionlint || true
printf '%s\n' '--- serializer and embedding probe ---'
python3 - <<'PY'
import json
samples = {
"tab": "title\tpath",
"carriage_return": "title\rpath",
"newline": "title\npath",
"backslash_quote": 'title\\"path',
"del": "title\x7fpath",
}
for name, value in samples.items():
manual = value.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n")
manual_payload = '{"text":"' + manual + '"}'
proposed_payload = '{"text":' + json.dumps(value) + '}'
for label, payload in (("manual", manual_payload), ("proposed", proposed_payload)):
try:
json.loads(payload)
status = "valid"
except json.JSONDecodeError as exc:
status = f"invalid ({exc.msg})"
print(f"{name}: {label}: {status}")
PYRepository: silverfin/bso_github_actions Length of output: 49020 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- complete relevant workflow ---'
sed -n '1,125p' .github/workflows/slack_changelog.yml
printf '%s\n' '--- exact serializer patterns ---'
rg -n -C 2 'jq[[:space:]]+(-R[[:space:]]+-s|-Rs)|MESSAGE=|TEMPLATE|github.event.pull_request|GITHUB_OUTPUT' \
.github/workflows/slack_changelog.yml .github/workflows/run_tests.yml .github/workflows/check_dependencies.yml || true
printf '%s\n' '--- actionlint ---'
actionlint .github/workflows/slack_changelog.yml
printf '%s\n' '--- end-to-end JSON handoff probe ---'
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
output_file="$tmpdir/GITHUB_OUTPUT"
message_file="$tmpdir/message"
# Include every JSON string control character and JSON-significant characters.
python3 - "$message_file" <<'PY'
import sys
value = ''.join(chr(i) for i in range(0x20)) + ' \\" / \u2028 \u2029'
with open(sys.argv[1], 'w', encoding='utf-8', newline='') as f:
f.write(value)
PY
MESSAGE="$(cat "$message_file")"
MESSAGE_ESCAPED="$(printf '%s' "$MESSAGE" | jq -Rs .)"
printf 'message=%s\n' "$MESSAGE_ESCAPED" >> "$output_file"
SLACK_MESSAGE="$(sed 's/^message=//' "$output_file")"
payload="$(printf '{"text":%s}' "$SLACK_MESSAGE")"
printf 'serialized=%s\n' "$SLACK_MESSAGE"
printf 'payload=%s\n' "$payload"
printf '%s' "$payload" | jq -e '.text | type == "string"' >/dev/null
printf 'payload_validation=valid\n'
printf '%s\n' '--- original escaping comparison ---'
original="$(printf '%s' "$MESSAGE" | sed 's/\\/\\\\/g; s/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g')"
original_payload="$(printf '{"text":"%s"}' "$original")"
if printf '%s' "$original_payload" | jq -e . >/dev/null 2>&1; then
printf 'original_payload=valid\n'
else
printf 'original_payload=invalid\n'
fiRepository: silverfin/bso_github_actions Length of output: 19576 🏁 Script executed: #!/bin/bash
set -eu
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
message_file="$tmpdir/message"
output_file="$tmpdir/GITHUB_OUTPUT"
python3 - "$message_file" <<'PY'
import sys
value = ''.join(chr(i) for i in range(0x20)) + ' \\" / \u2028 \u2029'
with open(sys.argv[1], 'w', encoding='utf-8', newline='') as f:
f.write(value)
PY
MESSAGE="$(cat "$message_file")"
MESSAGE_ESCAPED="$(printf '%s' "$MESSAGE" | jq -Rs .)"
printf 'message=%s\n' "$MESSAGE_ESCAPED" >> "$output_file"
SLACK_MESSAGE="$(sed 's/^message=//' "$output_file")"
payload="$(printf '{"text":%s}' "$SLACK_MESSAGE")"
printf 'serialized=%s\n' "$SLACK_MESSAGE"
printf 'payload=%s\n' "$payload"
printf '%s' "$payload" | jq -e '.text | type == "string"' >/dev/null
printf 'proposed_payload_validation=valid\n'
original="$(printf '%s' "$MESSAGE" | sed 's/\\/\\\\/g; s/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g')"
original_payload="$(printf '{"text":"%s"}' "$original")"
if printf '%s' "$original_payload" | jq -e . >/dev/null 2>&1; then
printf 'original_payload=valid\n'
else
printf 'original_payload=invalid\n'
fi
printf '%s\n' '--- newline and output-line checks ---'
case "$MESSAGE_ESCAPED" in
*$'\n'*|*$'\r'*) printf 'serialized_single_line=false\n'; exit 1 ;;
*) printf 'serialized_single_line=true\n' ;;
esac
awk 'index($0, "message=") == 1 { count++ } END { printf "output_message_lines=%d\n", count }' "$output_file"Repository: silverfin/bso_github_actions Length of output: 776 Use a complete JSON serializer for the Slack text. The 🤖 Prompt for AI Agents |
||
| echo "message=$MESSAGE_ESCAPED" >> $GITHUB_OUTPUT | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- workflow excerpt ---'
nl -ba .github/workflows/slack_changelog.yml | sed -n '50,110p'
printf '%s\n' '--- GITHUB_OUTPUT references ---'
rg -n -C 2 '\$GITHUB_OUTPUT' .github/workflows/slack_changelog.yml
printf '%s\n' '--- available shell lint ---'
command -v shellcheck || trueRepository: silverfin/bso_github_actions Length of output: 1158 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- workflow excerpt ---'
sed -n '50,110p' .github/workflows/slack_changelog.yml
printf '%s\n' '--- ShellCheck for the scalar redirections ---'
cat <<'SH' | shellcheck -s bash -
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
echo "pr_title=$PR_TITLE" >> $GITHUB_OUTPUT
echo "pr_author=$PR_AUTHOR" >> $GITHUB_OUTPUT
echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT
echo "total_templates=$TOTAL_TEMPLATES" >> $GITHUB_OUTPUT
SH
printf '%s\n' '--- ShellCheck for the grouped redirection ---'
cat <<'SH' | shellcheck -s bash -
{
echo "$TEMPLATE_FOLDERS"
echo 'EOF'
} >> $GITHUB_OUTPUT
SH
printf '%s\n' '--- ShellCheck for the message redirection ---'
cat <<'SH' | shellcheck -s bash -
echo "message=$MESSAGE_ESCAPED" >> $GITHUB_OUTPUT
SH
printf '%s\n' '--- Bash redirection behavior with a space in the target path ---'
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT
target="$tmpdir/output file"
GITHUB_OUTPUT="$target" bash -c 'echo "message=value" >> $GITHUB_OUTPUT'
if test -f "$target"; then
printf 'created=%q\n' "$target"
printf 'contents='
cat "$target"
else
printf '%s\n' 'target_not_created'
fiRepository: silverfin/bso_github_actions Length of output: 3924 🏁 Script executed: #!/bin/bash
printf '%s\n' '--- ShellCheck for all relevant fragments ---'
for fragment in scalar grouped message; do
case "$fragment" in
scalar)
code='echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
echo "pr_title=$PR_TITLE" >> $GITHUB_OUTPUT
echo "pr_author=$PR_AUTHOR" >> $GITHUB_OUTPUT
echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT
echo "total_templates=$TOTAL_TEMPLATES" >> $GITHUB_OUTPUT'
;;
grouped)
code='{ echo "template_folders<<EOF"; echo "$TEMPLATE_FOLDERS"; echo "EOF"; } >> $GITHUB_OUTPUT'
;;
message)
code='echo "message=$MESSAGE_ESCAPED" >> $GITHUB_OUTPUT'
;;
esac
printf '[%s]\n' "$fragment"
printf '%s\n' "$code" | shellcheck -s bash - || true
done
printf '%s\n' '--- Bash redirection behavior with a space in the target path ---'
tmpdir=$(mktemp -d)
target="$tmpdir/output file"
GITHUB_OUTPUT="$target" bash -c 'echo "message=value" >> $GITHUB_OUTPUT'
status=$?
printf 'status=%s\n' "$status"
if test -f "$target"; then
printf 'created=%s\n' yes
printf 'contents='
cat "$target"
else
printf '%s\n' 'created=no'
fi
rm -rf "$tmpdir"Repository: silverfin/bso_github_actions Length of output: 2680 Quote all An unquoted target fails with 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
|
|
||
| - name: Post to Slack | ||
| env: | ||
| SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} | ||
| SLACK_MESSAGE: ${{ steps.changelog.outputs.message }} | ||
| run: | | ||
| # Send changelog message as text field for Workflow Builder | ||
| curl -X POST "${{ secrets.SLACK_WEBHOOK_URL }}" \ | ||
| curl -X POST "$SLACK_WEBHOOK_URL" \ | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Slack Without Action: Add |
||
| -H "Content-Type: application/json" \ | ||
| -d "{\"text\":\"${{ steps.changelog.outputs.message }}\"}" | ||
| -d "{\"text\":\"$SLACK_MESSAGE\"}" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Count templates before truncating the display list.
Line [49] limits
TEMPLATE_FOLDERSto 10 entries before Line [57] calculatesTOTAL_TEMPLATES. Therefore this condition never becomes true, and Slack never reports additional templates for PRs that change more than 10 folders. Count the full list first, then applyhead -10only to the displayed value.🤖 Prompt for AI Agents