Skip to content

fix(ci): make build.yml GA build fire on release tags under protected main - #764

Merged
andyne13 merged 1 commit into
mainfrom
hotfix/2.0.1-build-guard
Jul 23, 2026
Merged

fix(ci): make build.yml GA build fire on release tags under protected main#764
andyne13 merged 1 commit into
mainfrom
hotfix/2.0.1-build-guard

Conversation

@andyne13

@andyne13 andyne13 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

The v2.0.1 tag push triggered build.yml but all three image-build jobs skipped — a green run that built nothing.

Cause

The jobs were guarded on github.event.base_ref == 'refs/heads/main'. That field is empty for a tag pushed to a branch-protected main: a protected main only receives PR-merge commits (created server-side), never client branch pushes, so GitHub records no branch association and base_ref arrives empty. The guard skipped everything, silently.

It worked for v2.0.0 only because main was still unprotected then (direct client pushes established the association). v2.0.1 is the first release since branch protection went on (2026-07-09), so it is the first to hit this.

Fix

Add a verify-tag gate that the three build jobs needs::

  • fires for release tags (v*) but not RC tags (build_rc.yml owns -rc.)
  • verifies the tagged commit is reachable from origin/main (merge-base --is-ancestor), failing loud (red) instead of skipping silently if a tag is ever off-main

Trigger matrix after this change:

Event Result
vX.Y.Z on main verify passes → 3 images build
vX.Y.Z off main verify fails red → builds skip
vX.Y.Z-rc.N verify skipped → builds skip (handled by build_rc.yml)
workflow_dispatch skipped (unchanged)

Unblocks the v2.0.1 release. Must also back-merge to develop so future releases inherit the fix.

Summary by CodeRabbit

  • Bug Fixes
    • Release builds now verify that tagged commits are included in the main branch before publishing Docker images.
    • Release candidate tags are excluded from this verification.
    • Prevents Docker images from being published for tags based on commits outside the main branch.

… check

The GA image build was guarded on github.event.base_ref == 'refs/heads/main'.
That field is empty for a tag pushed to a branch-protected main: protected
main only receives PR-merge commits (server-side), never client branch pushes,
so GitHub records no branch association and base_ref comes through empty. The
guard therefore skipped every job silently — a green run that built nothing.
It worked for v2.0.0 only because main was still unprotected then.

Replace it with a verify-tag gate that the three build jobs depend on:
- fires for release tags (v*) but not RC tags (build_rc.yml owns -rc.)
- verifies the tagged commit is reachable from origin/main via merge-base,
  failing LOUD (red) instead of skipping silently if a tag is off-main

First release affected: v2.0.1.
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The release workflow adds GA tag ancestry verification against origin/main and makes the three Docker image publishing jobs depend on that verification.

Changes

Release tag validation

Layer / File(s) Summary
Verify release tags and gate image builds
.github/workflows/build.yml
Adds a verify-tag job for non-RC version tags that checks tag ancestry against origin/main; the image, Ray image, and admin UI image publishing jobs now require it.

Estimated code review effort: 3 (Moderate) | ~15 minutes

Possibly related PRs

  • linagora/openrag#648: Modifies the same release workflow and introduces the admin UI tag-build job.

Suggested labels: chore

Suggested reviewers: enjoybacon7, ahmath-gadji

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main CI change: enabling GA builds from release tags on protected main.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch hotfix/2.0.1-build-guard

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added the chore No production code impact, typically improve tooling, code quality, etc label Jul 23, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/build.yml:
- Around line 20-22: Update the job conditions for build-and-push-image,
build-and-push-image-ray, and build-and-push-image-admin-ui to allow
workflow_dispatch runs to proceed even when verify-tag is skipped, while still
requiring verify-tag success for other events.
- Around line 32-35: Update the workflow step containing the git merge-base
check to pass github.ref_name through the step’s env configuration, then
reference the quoted shell variable in both echo messages instead of
interpolating the GitHub expression directly in run. Keep github.sha
interpolation unchanged unless similarly required.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 75d94916-d6a5-4cdd-9562-f10ab1481200

📥 Commits

Reviewing files that changed from the base of the PR and between c08c5e9 and 736ded6.

📒 Files selected for processing (1)
  • .github/workflows/build.yml

Comment on lines +20 to +22
verify-tag:
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '-rc.')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow files =="
git ls-files | grep -E '(^|/)\.github/workflows/.*\.ya?ml$' || true

echo
echo "== build.yml relevant lines =="
if [ -f .github/workflows/build.yml ]; then
  nl -ba .github/workflows/build.yml | sed -n '1,180p'
fi

echo
echo "== github ref/event_name docs relevant local references =="
rg -n "workflow_dispatch|github\.ref|always\(\)|needs\.[a-z-]+\.result|runs-on|verify-tag|depends|needs:" .github/workflows README.md 2>/dev/null || true

Repository: linagora/openrag

Length of output: 561


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== build.yml relevant lines =="
python3 - <<'PY'
from pathlib import Path
p=Path(".github/workflows/build.yml")
for i,line in enumerate(p.read_text().splitlines(),1):
    if 1 <= i <= 180:
        print(f"{i:4}\t{line}")
PY

echo
echo "== event_name/ref refs =="
rg -n "event_name|github\.ref|workflow_dispatch|verify-tag|needs:|always\(\)" .github/workflows/build.yml .github/workflows 2>/dev/null || true

Repository: linagora/openrag

Length of output: 8846


🌐 Web query:

GitHub Actions skipped job downstream jobs needs dependency job skipped docs

💡 Result:

In GitHub Actions, if a job is skipped or fails, all downstream jobs that depend on it (via the needs keyword) are automatically skipped [1][2][3]. This behavior propagates through the dependency chain [1][4]. To force a downstream job to run even if a dependency is skipped or fails, you must explicitly override this default behavior using the always conditional expression in the downstream job's if property [1][2][3]. Key Implementation Details: 1. Use always: Adding if: ${{ always }} ensures the job is evaluated even when preceding jobs are skipped [1][2]. 2. Manage Dependency Results: When using always, the downstream job will run regardless of the outcome of its dependencies. If you need to verify the status of specific dependencies (e.g., to ensure they either succeeded or were skipped but did not fail), you should combine always with checks on the result property [5][6][7]: downstream-job: needs: [upstream-job] if: ${{ always && (needs.upstream-job.result == 'success' || needs.upstream-job.result == 'skipped') }} runs-on: ubuntu-latest steps: - run: echo "This job runs even if upstream-job was skipped." 3. Context Considerations: By default, if no if condition is provided, GitHub Actions applies an implicit success condition [8]. Using always removes this implicit check, which is why it is often necessary to manually check for 'failure' or 'cancelled' states if you want to avoid running the job in those specific scenarios [6][7]. Warning: A skipped job is still reported as "Success" in the overall workflow status [9][10]. If you use a skipped job as a dependency for a required check in a pull request, that check will be considered successful, which might bypass intended gating logic [9][10].

Citations:


🌐 Web query:

GitHub Actions workflow_dispatch branches tags refs branch push github.ref

💡 Result:

The workflow_dispatch event in GitHub Actions allows for manual triggering of workflows. A critical requirement for this trigger is that the workflow file must exist on the repository's default branch [1][2][3]. Regarding branches, tags, and github.ref: 1. Workflow File Location: While the workflow file itself must reside on the default branch to enable the trigger, the workflow can be executed against other branches or tags [2]. 2. Selecting Refs: When triggering the workflow manually via the GitHub UI, GitHub CLI (e.g., gh workflow run --ref <branch/tag>), or the REST API, you can specify the target branch or tag [2]. 3. github.ref Context: When the workflow runs, the github.ref context variable is set to the fully-formed reference (e.g., refs/heads/<branch_name> or refs/tags/<tag_name>) of the branch or tag that was selected for the manual run [1][4][3]. 4. No Branch/Tag Filtering: Unlike the push event, the workflow_dispatch event trigger does not support branches or tags filtering keywords (like on.push.branches or on.push.tags) in the workflow YAML configuration. The event is inherently manual and defined by the selection made at the time of execution [1][5][6].

Citations:


Preserve the workflow_dispatch path.

A normal manual dispatch runs from a branch ref, so verify-tag is skipped and every job with needs: verify-tag is then skipped. Add an explicit dispatch condition, such as if: github.event_name == 'workflow_dispatch' || needs.verify-tag.result == 'success', to build-and-push-image, build-and-push-image-ray, and build-and-push-image-admin-ui.

Also applies to: 39-40, 96-97, 144-145

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build.yml around lines 20 - 22, Update the job conditions
for build-and-push-image, build-and-push-image-ray, and
build-and-push-image-admin-ui to allow workflow_dispatch runs to proceed even
when verify-tag is skipped, while still requiring verify-tag success for other
events.

Source: Learnings

Comment on lines +32 to +35
if git merge-base --is-ancestor "${{ github.sha }}" FETCH_HEAD; then
echo "OK: ${{ github.ref_name }} (${{ github.sha }}) is on main"
else
echo "::error::Tag ${{ github.ref_name }} is not on main — refusing to build GA images."

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow excerpt =="
if [ -f .github/workflows/build.yml ]; then
  nl -ba .github/workflows/build.yml | sed -n '1,90p'
else
  echo "missing .github/workflows/build.yml"
fi

echo
echo "== changed files summary =="
git diff --stat || true

echo
echo "== workflow metadata/name refs tags =="
python3 - <<'PY'
from pathlib import Path
p=Path('.github/workflows/build.yml')
if p.exists():
    text=p.read_text()
    for i,line in enumerate(text.splitlines(),1):
        if 'github.ref_name' in line or 'github.sha' in line or 'match:' in line:
            print(f"{i}: {line}")
PY

Repository: linagora/openrag

Length of output: 217


🌐 Web query:

GitHub Actions untrusted inputs run with context values script injection documentation

💡 Result:

GitHub Actions script injection occurs when untrusted context values—such as those from pull request titles, body text, or issue labels—are directly interpolated into workflow shell scripts using the ${{ }} syntax [1][2][3]. Because these expressions are evaluated and expanded into the script body before execution, an attacker can supply malicious characters (like quotes or semicolons) to break out of the intended command and execute arbitrary code on the runner [1][4][3]. To mitigate these risks, follow these authoritative security practices: 1. Use Intermediate Environment Variables (Recommended for Inline Scripts) Instead of placing context expressions directly into a run command, map the untrusted value to an environment variable [5][6]. The runner then treats this as data rather than executable code [5][3]. Example: env: USER_INPUT: ${{ github.event.issue.title }} run: | echo "$USER_INPUT" 2. Use Actions Instead of Inline Scripts The most secure approach is to use a pre-built or custom JavaScript action [5][7]. Actions receive inputs as arguments, which prevents them from being interpreted as shell commands [5][7]. 3. Implement Least Privilege Ensure your workflows operate with the minimum necessary permissions [4][6]. Limit the GITHUB_TOKEN permissions to prevent an attacker from gaining excessive access to your repository even if an injection occurs [4][6]. 4. Enable Security Scanning Enable code scanning, such as GitHub's CodeQL, to automatically identify unsafe use of untrusted inputs in your workflows [2][4]. Treat all data from the github context (e.g., github.event.pull_request.body, github.event.issue.title) as potentially untrusted input, even if they seem benign [1][3][8]. Avoid the use of ${{ }} syntax inside run blocks for any user-controlled values [2][4]. [1][5][2][4][3][9][6][7][8]

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow excerpt =="
if [ -f .github/workflows/build.yml ]; then
  cat -n .github/workflows/build.yml | sed -n '1,120p'
else
  echo "missing .github/workflows/build.yml"
fi

echo
echo "== GitHub context interpolations and triggers =="
python3 - <<'PY'
from pathlib import Path
p = Path('.github/workflows/build.yml')
if not p.exists():
    raise SystemExit
text = p.read_text()
for name in ['on:', 'schedule:', 'pull_request:', 'pull_request_target:', 'push:', 'workflow_dispatch:', 'match:', 'github.ref_name', 'github.sha']:
    lines = [f"{i}: {line}" for i, line in enumerate(text.splitlines(), 1) if name in line]
    print(f"-- {name} --")
    for line in lines:
        print(line)
if not any(x in text for x in ['github.ref_name', 'github.sha']):
    print("-- no github ref/sha occurrences --")
PY

echo
echo "== deterministic shell expansion behavior for quoted tag names =="
python3 - <<'PY'
import subprocess, tempfile, os
examples = [
    'git merge-base --is-ancestor "v1.0;malicious"'
    'git merge-base --is-ancestor "$TAG_NAME"'
]
for src in examples:
    print(src)
    with tempfile.TemporaryDirectory() as d:
        path = os.path.join(d, 'script.sh')
        content = f"{src}\\nTAG_NAME=\\'v1.0;echo INJECTED\\'\\nprintenv TAG_NAME\\n"
        with open(path, 'wb') as f:
            # emulate POSIX sh: first run expands, second run sets after expansion
            if src.endswith('"v1.0;malicious"'):
                expanded = 'git merge-base --is-ancestor "v1.0;malicious"'
                to_write = expanded + '\\nTAG_NAME=\\'v1.0;echo INJECTED\\'\\nprintenv TAG_NAME\\n'
            elif src.endswith('"$TAG_NAME"'):
                expanded = 'git merge-base --is-ancestor "$TAG_NAME"'
                to_write = expanded + '\\nTAG_NAME=\\"v1.0;echo INJECTED\\"\\nprintenv TAG_NAME\\n'
        subprocess.run(['sh', path], cwd=d, text=True, capture_output=True)
PY

Repository: linagora/openrag

Length of output: 5665


Avoid interpolating github.ref_name directly into run.

This workflow is triggered on push: tags, so repository-controlled tag names are expanded inside the shell script before execution. Pass the tag name via env and quote the shell variable to avoid script-injection risk.

Proposed fix
+        env:
+          TAG_NAME: ${{ github.ref_name }}
+          TAG_SHA: ${{ github.sha }}
         run: |
           git fetch --no-tags origin main
-          if git merge-base --is-ancestor "${{ github.sha }}" FETCH_HEAD; then
-            echo "OK: ${{ github.ref_name }} (${{ github.sha }}) is on main"
+          if git merge-base --is-ancestor "$TAG_SHA" FETCH_HEAD; then
+            echo "OK: $TAG_NAME ($TAG_SHA) is on main"
           else
-            echo "::error::Tag ${{ github.ref_name }} is not on main — refusing to build GA images."
+            echo "::error::Tag $TAG_NAME is not on main — refusing to build GA images."
           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.

Suggested change
if git merge-base --is-ancestor "${{ github.sha }}" FETCH_HEAD; then
echo "OK: ${{ github.ref_name }} (${{ github.sha }}) is on main"
else
echo "::error::Tag ${{ github.ref_name }} is not on main — refusing to build GA images."
env:
TAG_NAME: ${{ github.ref_name }}
TAG_SHA: ${{ github.sha }}
run: |
git fetch --no-tags origin main
if git merge-base --is-ancestor "$TAG_SHA" FETCH_HEAD; then
echo "OK: $TAG_NAME ($TAG_SHA) is on main"
else
echo "::error::Tag $TAG_NAME is not on main — refusing to build GA images."
exit 1
🧰 Tools
🪛 zizmor (1.26.1)

[error] 33-33: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[error] 35-35: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build.yml around lines 32 - 35, Update the workflow step
containing the git merge-base check to pass github.ref_name through the step’s
env configuration, then reference the quoted shell variable in both echo
messages instead of interpolating the GitHub expression directly in run. Keep
github.sha interpolation unchanged unless similarly required.

Source: Linters/SAST tools

@andyne13
andyne13 merged commit 6a18a53 into main Jul 23, 2026
6 checks passed
@andyne13
andyne13 deleted the hotfix/2.0.1-build-guard branch July 23, 2026 19:55
ewan102 pushed a commit to ewan102/openrag that referenced this pull request Jul 27, 2026
…lation

Review findings on the verify-tag gate added in linagora#764 (raised by @hedhoud and
CodeRabbit/zizmor), all three confirmed against the merged workflow:

1. Tag format was too loose. The guard only rejected '-rc.', so v2.0.1-rc1,
   v2.0.1-beta, vfoo etc. passed and would publish GA images and move :latest.
   The rc1 case is the sharp one: build_rc.yml triggers on 'v*-rc.*' which
   requires the dot, so a one-character typo matched neither workflow's intent.
   Now validated against ^v[0-9]+\.[0-9]+\.[0-9]+$ and failed loud.

2. Template injection. ${{ github.ref_name }} expanded into the run body before
   the shell ran, and git permits ; $ ` " | & in ref names — arbitrary code
   execution in a job that holds packages:write and Docker Hub credentials.
   Tag name and SHA now passed via env: and referenced as shell variables.

3. Checkout persisted credentials, inconsistent with build_rc.yml which already
   sets persist-credentials: false on all three checkouts (95fd86f). The repo
   is public, so the origin/main fetch still works without them.

Behavior: vX.Y.Z on main builds; vX.Y.Z-rc.N skips to build_rc.yml; malformed
or prerelease tags and off-main tags now fail loudly instead of publishing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

chore No production code impact, typically improve tooling, code quality, etc

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant