feat(ci): include commit history in staging promotion PRs (#952)

* feat(ci): include commit history in staging promotion PRs and merge commits

Promotion PRs from staging->main previously had opaque bodies showing
only the batch SHA range. Now they enumerate all non-merge commits in
each batch as a flat markdown list, visible both in the PR body and
embedded in the merge commit message via --subject/--body flags.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix(ci): use unique delimiter for commit_summary output

Replace hardcoded COMMIT_SUMMARY_DELIM with a uuidgen-based delimiter
to prevent theoretical collisions with commit message content.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix(ci): use heredoc for PR body to avoid GFM code-block rendering

The inline --body string had 10 leading spaces per line (from YAML
indentation), which GitHub-flavored Markdown renders as a code block.
Move the body into a heredoc variable so content starts at column 0.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix(ci): truncate commit list at 50 and include PR number in merge subject

- Cap commit enumeration at 50 entries with a truncation note to avoid
  blowing past GitHub PR body/merge message limits on large batches.
- Prefix merge commit subject with #PR_NUMBER for traceability in git log.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix(ci): address review — shell expansion, body-file, uuidgen

1. Replace heredoc with string concatenation to prevent shell expansion
   of commit messages containing $, backticks, or backslashes
2. Use --body-file for merge commit body for robustness
3. Replace uuidgen with date +%s for portability

Addresses: https://github.com/nearai/ironclaw/pull/952#pullrequestreview-3938725460

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Henry Park
2026-03-12 15:33:35 -07:00
committed by GitHub
co-authored by Claude Opus 4.6
parent c54f739354
commit c7dec64b2d
+51 -15
View File
@@ -108,6 +108,7 @@ jobs:
outputs:
pr_number: ${{ steps.create-pr.outputs.pr_number }}
promotion_branch: ${{ steps.branch.outputs.branch }}
commit_summary: ${{ steps.create-pr.outputs.commit_summary }}
steps:
- uses: actions/checkout@v6
with:
@@ -186,30 +187,59 @@ jobs:
BRANCH="${{ steps.branch.outputs.branch }}"
BASE="${{ steps.find-base.outputs.base }}"
# Enumerate commits in this batch (exclude merge commits, cap at 50)
MAX_COMMITS=50
COMMIT_LIST=$(git log --oneline --no-merges --reverse "${RANGE}" 2>/dev/null || echo "")
if [ -n "$COMMIT_LIST" ]; then
COMMIT_COUNT=$(echo "$COMMIT_LIST" | wc -l | tr -d ' ')
if [ "$COMMIT_COUNT" -gt "$MAX_COMMITS" ]; then
COMMIT_MD=$(echo "$COMMIT_LIST" | head -n "$MAX_COMMITS" | sed 's/^/- /')
COMMIT_MD="${COMMIT_MD}
- ... and $((COMMIT_COUNT - MAX_COMMITS)) more (see compare view)"
else
COMMIT_MD=$(echo "$COMMIT_LIST" | sed 's/^/- /')
fi
else
COMMIT_COUNT=0
COMMIT_MD="- (no non-merge commits in range)"
fi
# Build PR body via concatenation to avoid heredoc shell expansion
# (commit messages in COMMIT_MD may contain $, backticks, or backslashes)
PR_BODY="## Auto-promotion from staging CI"
PR_BODY+=$'\n\n'"**Batch range:** \`${RANGE}\`"
PR_BODY+=$'\n'"**Promotion branch:** \`${BRANCH}\`"
PR_BODY+=$'\n'"**Base:** \`${BASE}\`"
PR_BODY+=$'\n'"**Triggered by:** Staging CI batch at ${TIMESTAMP}"
PR_BODY+=$'\n\n'"### Commits in this batch (${COMMIT_COUNT}):"
PR_BODY+=$'\n'"${COMMIT_MD}"
PR_BODY+=$'\n\n'"Waiting for gates:"
PR_BODY+=$'\n'"- Tests: pending"
PR_BODY+=$'\n'"- E2E: pending"
PR_BODY+=$'\n'"- Claude Code review: pending (will post comments on this PR)"
PR_BODY+=$'\n\n'"---"
PR_BODY+=$'\n'"*Auto-created by staging-ci workflow*"
PR_URL=$(gh pr create \
--base "$BASE" \
--head "$BRANCH" \
--title "chore: promote staging to ${BASE} (${TIMESTAMP})" \
--body "## Auto-promotion from staging CI
**Batch range:** \`${RANGE}\`
**Promotion branch:** \`${BRANCH}\`
**Base:** \`${BASE}\`
**Triggered by:** Staging CI batch at ${TIMESTAMP}
Waiting for gates:
- Tests: pending
- E2E: pending
- Claude Code review: pending (will post comments on this PR)
---
*Auto-created by staging-ci workflow*" \
--body "$PR_BODY" \
--label "staging-promotion")
PR_NUM=$(echo "$PR_URL" | grep -oE '[0-9]+$')
echo "pr_number=${PR_NUM}" >> "$GITHUB_OUTPUT"
echo "Created promotion PR #${PR_NUM}"
# Output commit summary for use in merge commit message
DELIM="COMMIT_SUMMARY_EOF_$(date +%s)"
{
echo "commit_summary<<${DELIM}"
echo "Commits in this batch (${COMMIT_COUNT}):"
echo "${COMMIT_MD}"
echo "${DELIM}"
} >> "$GITHUB_OUTPUT"
# ── Gate: wait for review, process findings, merge or block ─────
gate:
name: Staging Gate
@@ -419,12 +449,18 @@ jobs:
env:
GH_TOKEN: ${{ steps.token.outputs.token }}
PR_NUMBER: ${{ needs.create-promotion-pr.outputs.pr_number }}
COMMIT_SUMMARY: ${{ needs.create-promotion-pr.outputs.commit_summary }}
run: |
if [ -n "$PR_NUMBER" ]; then
BASE=$(gh pr view "$PR_NUMBER" --json baseRefName --jq '.baseRefName')
if [ "$BASE" = "main" ]; then
echo "Merging promotion PR #${PR_NUMBER} (targets main)"
gh pr merge "$PR_NUMBER" --merge
TITLE=$(gh pr view "$PR_NUMBER" --json title --jq '.title')
if [ -n "$COMMIT_SUMMARY" ]; then
gh pr merge "$PR_NUMBER" --merge --subject "#${PR_NUMBER} $TITLE" --body-file <(printf '%s' "$COMMIT_SUMMARY")
else
gh pr merge "$PR_NUMBER" --merge
fi
echo "merged=true" >> "$GITHUB_OUTPUT"
else
echo "PR #${PR_NUMBER} targets '${BASE}' (not main) — leaving open for chain resolution"