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
8 changes: 4 additions & 4 deletions .github/workflows/block-non-dev-to-main.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Block non-dev PRs to main
name: Block non-staging PRs to main

on:
pull_request:
Expand All @@ -8,9 +8,9 @@ jobs:
check-source:
runs-on: ubuntu-latest
steps:
- name: Block PRs not from dev
- name: Block PRs not from staging
run: |
if [[ "${{ github.head_ref }}" != "dev" ]]; then
echo "You can only merge from dev to main."
if [[ "${{ github.head_ref }}" != "staging" ]]; then
echo "You can only merge from staging to main."
exit 1
Comment on lines +11 to 15
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Harden PR source check: avoid inlining untrusted github.head_ref in shell

Using ${{ github.head_ref }} directly inside the script risks shell injection. Prefer step-level if or pass via env var and quote.

Option A (recommended): use step-level condition to fail cleanly

-      - name: Block PRs not from staging
-        run: |
-          if [[ "${{ github.head_ref }}" != "staging" ]]; then
-            echo "You can only merge from staging to main."
-            exit 1
-          fi
+      - name: Block PRs not from staging
+        if: ${{ github.event.pull_request.head.ref != 'staging' }}
+        run: |
+          echo "You can only merge from staging to main."
+          exit 1

Option B: pass through environment and enforce bash

-      - name: Block PRs not from staging
-        run: |
-          if [[ "${{ github.head_ref }}" != "staging" ]]; then
-            echo "You can only merge from staging to main."
-            exit 1
-          fi
+      - name: Block PRs not from staging
+        env:
+          HEAD_REF: ${{ github.head_ref }}
+        shell: bash
+        run: |
+          if [[ "$HEAD_REF" != "staging" ]]; then
+            echo "You can only merge from staging to main."
+            exit 1
+          fi

Based on static analysis hints

📝 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.

Suggested change
- name: Block PRs not from staging
run: |
if [[ "${{ github.head_ref }}" != "dev" ]]; then
echo "You can only merge from dev to main."
if [[ "${{ github.head_ref }}" != "staging" ]]; then
echo "You can only merge from staging to main."
exit 1
- name: Block PRs not from staging
if: ${{ github.event.pull_request.head.ref != 'staging' }}
run: |
echo "You can only merge from staging to main."
exit 1
🧰 Tools
🪛 actionlint (1.7.7)

12-12: "github.head_ref" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions for more details

(expression)

🤖 Prompt for AI Agents
.github/workflows/block-non-dev-to-main.yml around lines 11-15: the workflow
inlines the untrusted github.head_ref into a shell block which can lead to shell
injection; replace this by using a step-level condition (preferred) that checks
github.head_ref != 'staging' to fail the job without running a shell, or if you
must run a script, pass github.head_ref into the step via an env variable and
reference it safely (e.g., ENV_VAR="${{ github.head_ref }}" then use
double-quoted "$ENV_VAR") and ensure the step uses bash (shell: bash) so quoting
is respected; update the step to either use if: ${{ github.head_ref != 'staging'
}} with a clear error message or move the check into a bash script that reads a
quoted env var.

fi
38 changes: 10 additions & 28 deletions .github/workflows/release-calendar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ name: Release Calendar
# Creates release PRs on a schedule or manually via workflow_dispatch.
#
# HOW IT WORKS:
# 1. Creates snapshot branches (release/staging-YYYY-MM-DD or release/production-YYYY-MM-DD)
# 2. Opens PRs from these branches (NOT directly from dev/staging)
# 3. New commits to dev/staging won't auto-update the PR - you control what's released
# 1. Dev → Staging: Creates snapshot branch (release/staging-YYYY-MM-DD) to prevent PR drift
# 2. Staging → Main: Opens PR directly from staging (no feature branch needed)
# 3. New commits to dev won't auto-update staging PRs - you control what's released
#
# SCHEDULE:
# • Friday 17:00 UTC (10am PT): Creates release/staging-* branch from dev → staging PR
# • Sunday 17:00 UTC (10am PT): Creates release/production-* branch from staging → main PR
# • Sunday 17:00 UTC (10am PT): Creates staging → main PR (direct from staging)
#
# MANUAL TRIGGER:
# Run via workflow_dispatch from main branch:
# - staging: Creates dev snapshot → staging PR
# - production: Creates staging snapshot → main PR
# - production: Creates staging → main PR (direct from staging)
#
# REQUIREMENTS:
# • Scheduled cron only runs when this file exists on the default branch (main)
Expand Down Expand Up @@ -272,9 +272,7 @@ jobs:
set -euo pipefail

PR_DATE=$(date +%Y-%m-%d)
BRANCH_NAME="release/production-${PR_DATE}"
echo "date=${PR_DATE}" >> "$GITHUB_OUTPUT"
echo "branch_name=${BRANCH_NAME}" >> "$GITHUB_OUTPUT"

echo "Fetching latest branches..."
git fetch origin main staging
Expand All @@ -290,8 +288,8 @@ jobs:

echo "staging_not_ahead=false" >> "$GITHUB_OUTPUT"

echo "Checking for existing pull requests from ${BRANCH_NAME} to main..."
EXISTING_PR=$(gh pr list --base main --head "${BRANCH_NAME}" --state open --limit 1 --json number --jq '.[0].number // ""')
echo "Checking for existing pull requests from staging to main..."
EXISTING_PR=$(gh pr list --base main --head staging --state open --limit 1 --json number --jq '.[0].number // ""')
echo "existing_pr=${EXISTING_PR}" >> "$GITHUB_OUTPUT"

if [ -n "$EXISTING_PR" ]; then
Expand Down Expand Up @@ -327,25 +325,11 @@ jobs:
fi
done

- name: Create release branch from staging
if: ${{ steps.guard_schedule.outputs.continue == 'true' && steps.production_status.outputs.staging_not_ahead != 'true' && steps.production_status.outputs.existing_pr == '' }}
env:
BRANCH_NAME: ${{ steps.production_status.outputs.branch_name }}
shell: bash
run: |
set -euo pipefail

echo "Creating release branch ${BRANCH_NAME} from staging"
git fetch origin staging
git checkout -b "${BRANCH_NAME}" origin/staging
git push origin "${BRANCH_NAME}"

- name: Create staging to main release PR
if: ${{ steps.guard_schedule.outputs.continue == 'true' && steps.production_status.outputs.staging_not_ahead != 'true' && steps.production_status.outputs.existing_pr == '' }}
env:
GH_TOKEN: ${{ github.token }}
PR_DATE: ${{ steps.production_status.outputs.date }}
BRANCH_NAME: ${{ steps.production_status.outputs.branch_name }}
COMMITS_AHEAD: ${{ steps.production_status.outputs.commits }}
shell: bash
run: |
Expand All @@ -359,22 +343,20 @@ jobs:

commits_ahead = os.environ["COMMITS_AHEAD"]
pr_date = os.environ["PR_DATE"]
branch_name = os.environ["BRANCH_NAME"]
formatted_date = datetime.strptime(pr_date, "%Y-%m-%d").strftime("%B %d, %Y")

pathlib.Path("pr_body.md").write_text(textwrap.dedent(f"""\
## 🎯 Production Release

**Release Date:** {formatted_date}
**Release Branch:** `{branch_name}`
**Commits ahead**: {commits_ahead}

This automated PR promotes tested changes from `staging` to `main` for production deployment.

### What's Included
All changes that have been verified in the staging environment.

**Note:** This PR uses a dedicated release branch, so new commits to `staging` will NOT automatically appear here.
**Note:** This PR is directly from `staging`, so new commits merged to `staging` will automatically appear here.

### Pre-Deployment Checklist
- [ ] All staging tests passed
Expand All @@ -392,11 +374,11 @@ jobs:
PY

TITLE="Release to Production - ${PR_DATE}"
echo "Creating PR with title: ${TITLE} from branch ${BRANCH_NAME} with ${COMMITS_AHEAD} commits ahead."
echo "Creating PR with title: ${TITLE} from staging with ${COMMITS_AHEAD} commits ahead."

gh pr create \
--base main \
--head "${BRANCH_NAME}" \
--head staging \
--title "${TITLE}" \
--label release \
--label automated \
Expand Down
Loading