ci: publish release on tag - #37637
Conversation
|
Looks like this PR is ready to merge! 🎉 |
|
WalkthroughThe release pipeline now starts from pushed tags instead of published release events. CI uses tag refs for builds, tests, deployment, and publication. Release-action creates draft releases, and CI publishes the matching draft release after artifact publication. ChangesTag-based release lifecycle
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant TagPush
participant CIWorkflow
participant DockerPublishing
participant GitHubReleaseAPI
TagPush->>CIWorkflow: trigger workflow for tag ref
CIWorkflow->>DockerPublishing: build and publish tag images
CIWorkflow->>GitHubReleaseAPI: find matching draft release
GitHubReleaseAPI-->>CIWorkflow: return draft release
CIWorkflow->>GitHubReleaseAPI: mark release as published
Suggested labels: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (1)
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. Comment |
90a2d57 to
b337436
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/release-action/src/index.ts (1)
11-54: Enforcerelease-tagfor theupdate-releaseactionRight now
update-releasetrustscore.getInput('release-tag')without validation. If the input is missing/misspelled,updateReleaseToFinalwill be called with an empty tag and fail via the GitHub API.You can fail fast with a clearer error by making the input required inside this branch, e.g.:
- } else if (action === 'update-release') { - await updateReleaseToFinal({ githubToken, releaseTag: core.getInput('release-tag') }); + } else if (action === 'update-release') { + const releaseTag = core.getInput('release-tag', { required: true }); + await updateReleaseToFinal({ githubToken, releaseTag }); }This keeps other actions unchanged while guaranteeing a valid tag when
update-releaseis used.
🧹 Nitpick comments (2)
packages/release-action/src/updateReleaseToFinal.ts (1)
1-33: Release finalization logic is sound (minor nit on unreachable path)The flow of
updateReleaseToFinal—look up by tag, short-circuit if not draft, then updatedraft: false—matches the new draft-creation behavior and uses the Octokit APIs correctly.The
if (!releaseId)guard is effectively redundant because a non-existent release will causegetReleaseByTagto throw before this block, but it doesn’t harm correctness. Given we already validaterelease-tagat the caller, no further changes are strictly required here..github/workflows/ci.yml (1)
962-981: Consider sequencing docs update afterupdate-releaseto avoid brief mismatchWith the new
update-releasejob, the GitHub Release is finalized (draft → false) only afterdocker-image-publishcompletes. Thedocs-updatejob currently:
- Runs for tags (
if: startsWith(github.ref, 'refs/tags/')),- Needs
docker-image-publishbut notupdate-release.This means
docs-updateandupdate-releasecan run in parallel; in a fast run, docs could be refreshed slightly before the corresponding GitHub Release has been switched out of draft.If the intent of ARCH-1900 is that documentation only reflects finished (non-draft) releases, you may want to make
docs-updatedepend onupdate-releaseas well:- docs-update: + docs-update: name: Update Version Durability - if: startsWith(github.ref, 'refs/tags/') - needs: - - docker-image-publish + if: startsWith(github.ref, 'refs/tags/') + needs: + - docker-image-publish + - update-releaseThis would guarantee that docs are updated only after both Docker publishing and release finalization are complete.
Also applies to: 985-993
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (7)
.github/actions/build-docker/action.yml(2 hunks).github/workflows/ci-test-e2e.yml(2 hunks).github/workflows/ci.yml(18 hunks)packages/release-action/action.yml(1 hunks)packages/release-action/src/bumpNextVersion.ts(1 hunks)packages/release-action/src/index.ts(2 hunks)packages/release-action/src/updateReleaseToFinal.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)
**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation
Files:
packages/release-action/src/updateReleaseToFinal.tspackages/release-action/src/bumpNextVersion.tspackages/release-action/src/index.ts
🧬 Code graph analysis (2)
packages/release-action/src/updateReleaseToFinal.ts (1)
.github/actions/update-version-durability/index.js (1)
octokit(17-19)
packages/release-action/src/index.ts (1)
packages/release-action/src/updateReleaseToFinal.ts (1)
updateReleaseToFinal(6-33)
🪛 actionlint (1.7.9)
.github/workflows/ci.yml
975-975: file "dist/index.js" does not exist in "/home/jailuser/git/packages/release-action". it is specified at "main" key in "runs" section in "Changeset release" action
(action)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: 🔨 Test UI (EE) / MongoDB 8.2 [legacy watchers] coverage (5/5)
- GitHub Check: 🔨 Test UI (EE) / MongoDB 8.2 [legacy watchers] coverage (3/5)
- GitHub Check: 🔨 Test UI (EE) / MongoDB 8.2 [legacy watchers] coverage (4/5)
- GitHub Check: 🔨 Test UI (EE) / MongoDB 5.0 (4/5)
- GitHub Check: 🔨 Test UI (EE) / MongoDB 5.0 (5/5)
- GitHub Check: 🔨 Test UI (EE) / MongoDB 5.0 (3/5)
- GitHub Check: 🔨 Test UI (EE) / MongoDB 5.0 (2/5)
- GitHub Check: 🔨 Test UI (CE) / MongoDB 8.2 (1/4)
- GitHub Check: 🔨 Test UI (CE) / MongoDB 8.2 (4/4)
🔇 Additional comments (5)
packages/release-action/src/bumpNextVersion.ts (1)
91-99: Draft releases correctly support the new finalize stepSetting
draft: trueoncreateReleaseis consistent with the newupdate-releasejob andupdateReleaseToFinalhelper, ensuring releases stay draft until CI finishes and explicitly finalizes them. No issues spotted here..github/workflows/ci-test-e2e.yml (1)
79-112: Tag-aware gating for coverage suffix and registry login looks correctThe updated conditions for
DOCKER_TAG_SUFFIX_ROCKETCHATand the login step correctly extend behavior to tag pushes while preserving the existing develop/PR constraints. Operator precedence and the truthy/falsey flow in the expressions are sound; no issues found..github/actions/build-docker/action.yml (1)
38-44: Extended tag/develop gating in build-docker matches CI behaviorThe new login condition and
SERVICE_SUFFIXexpression correctly align composite action behavior with the workflow: publishing/login only for same-repo PRs, tags, or develop, and-covsuffix only for coverage images on tags/develop. No functional issues identified.Also applies to: 114-128
.github/workflows/ci.yml (1)
9-10: Tag-driven gating and matrix logic look coherent across CIThe switch to
on.push.tagsplus the variousstartsWith(github.ref, 'refs/tags/')conditions in build, Docker, deploy, and test jobs is internally consistent:
release-versions.by-tagcorrectly classifies semver vs-rc.*tags.- The
buildmatrixexcludeandbuild-gh-dockermatrixtypeexpressions result in production builds only for tags/develop and coverage-only builds for other branches.- Docker build/publish conditions are aligned across jobs and the composite
build-dockeraction, including the newDOCKER_TAG_SUFFIX_ROCKETCHATbehavior.- UI retries are sensibly increased only for tags/develop/master, keeping PR runs leaner.
No functional issues found in these gating and matrix changes.
Also applies to: 135-151, 230-283, 306-399, 519-520, 565-566, 593-594, 749-781, 832-832, 936-957
packages/release-action/action.yml (1)
4-17: I'll verify the three concerns raised in the review comment about theaction.ymlfile. Let me start by examining the repository structure and the relevant files.
<function_calls>
cd /tmp && git clone --quiet https://github.com/googleapis/release-action.git && cd release-action && git log --oneline -1
</function_calls>Now let me examine the full content of the action.yml file and related files:
<function_calls>
cd /tmp/release-action && cat -n packages/release-action/action.yml
</function_calls>Let me check the src/index.ts file to see runtime validation:
<function_calls>
cd /tmp/release-action && find . -name "src" -type d | head -5
</function_calls><function_calls>
cd /tmp/release-action && find . -name "index.ts" -o -name "index.js" | grep -v node_modules | head -10
</function_calls><function_calls>
cd /tmp/release-action && ls -la packages/release-action/
</function_calls><function_calls>
cd /tmp/release-action/packages/release-action && ls -la
</function_calls><function_calls>
cd /tmp/release-action && find packages/release-action -type f -name ".ts" -o -name ".js" | head -20
</function_calls><function_calls>
cd /tmp/release-action && cat packages/release-action/action.yml
</function_calls><function_calls>
cd /tmp/release-action && ls -la packages/release-action/dist/ 2>/dev/null || echo "dist directory does not exist"
</function_calls><function_calls>
cd /tmp/release-action && cat packages/release-action/package.json | grep -A 10 '"scripts"'
</function_calls><function_calls>
cd /tmp/release-action && find packages/release-action -name "*.ts" -type f
</function_calls><function_calls>
cd /tmp/release-action && cat packages/release-action/src/index.ts 2>/dev/null | head -100
</function_calls>Let me search for where the action input is parsed and validated:
<function_calls>
cd /tmp/release-action && rg -n "release-tag|update-release|action" packages/release-action/src/ -A 2 -B 2 | head -60
</function_calls><function_calls>
cd /tmp/release-action && wc -l packages/release-action/src/index.ts
</function_calls><function_calls>
cd /tmp/release-action && cat packages/release-action/src/index.ts
</function_calls>Now let me check the build process and look for the dist directory in the git history:
<function_calls>
cd /tmp/release-action && git ls-files packages/release-action/dist/ | head -20
</function_calls><function_calls>
cd /tmp/release-action && cat .gitignore | grep -i dist
</function_calls>Let me check the full action.yml more carefully:
<function_calls>
cd /tmp/release-action && head -20 packages/release-action/action.yml
</function_calls><function_calls>
cd /tmp/release-action && cat packages/release-action/action.yml | sed -n '1,20p'
</function_calls><function_calls>
cd /tmp/release-action && python3 << 'EOF'
import yamlwith open('packages/release-action/action.yml', 'r') as f:
data = yaml.safe_load(f)print("Action input 'action' description:")
print(repr(data['inputs']['action']['description']))
print("\n---")
print("All inputs:")
for input_name, input_config in data['inputs'].items():
print(f"\n{input_name}:")
print(f" description: {repr(input_config.get('description', 'N/A'))}")
print(f" required: {input_config.get('required', False)}")
EOF
</function_calls><function_calls>
cd /tmp/release-action && git log --all --oneline -- packages/release-action/action.yml | head -10
</function_calls><function_calls>
cd /tmp/release-action && git show HEAD:packages/release-action/action.yml | head -30
</function_calls>Let me verify what the current branch is and check for dist/index.js:
<function_calls>
cd /tmp/release-action && git branch -a && echo "---" && git status --short packages/release-action/action.yml
</function_calls><function_calls>
cd /tmp/release-action && rg -n "getInput|action" packages/release-action/src/index.ts | head -20
</function_calls><function_calls>
cd /tmp/release-action && grep -n "update-release|release-tag" packages/release-action/src/index.ts
</function_calls><function_calls>
cd /tmp/release-action && cat packages/release-action/src/index.ts
</function_calls><function_calls>
cd /tmp/release-action && grep -n "case|switch" packages/release-action/src/index.ts
</function_calls><function_calls>
cd /tmp/release-action && rg -n "update-release|publish-final|'cut'|'next'|'patch'" packages/release-action/src/
</function_calls><function_calls>
cd /tmp/release-action && cat packages/release-action/package.json
</function_calls><function_calls>
cd /tmp/release-action && cat .github/workflows/*.yml | grep -A 5 -B 5 "dist|build" | head -40
</function_calls><function_calls>
cd /tmp/release-action && find .github -name "*.yml" -type f
</function_calls><function_calls>
cd /tmp/release-action && cat .github/workflows/ci.yml | grep -A 3 -B 3 dist
</function_calls><function_calls>
cd /tmp/release-action && head -50 .github/workflows/ci.yml
</function_calls><function_calls>
cd /tmp/release-action && cat .github/workflows/ci.yml
</function_calls>Now let me check if there's a build/dist step in the workflow:
<function_calls>
cd /tmp/release-action && find . -name ".yml" -o -name ".yaml" | xargs grep -l "npm run|yarn|dist|build" | grep -E "workflow|action" | head -5
</function_calls><function_calls>
cd /tmp/release-action && ls -la .github/workflows/
</function_calls><function_calls>
cd /tmp/release-action && grep -r "dist/index.js|bundle|build" .github/workflows/
</function_calls>Let me check if dist is in gitignore:
<function_calls>
cd /tmp/release-action && cat .gitignore
</function_calls><function_calls>
cd /tmp/release-action && ls -la packages/release-action/dist/ 2>&1 || echo "Not found"
</function_calls>Now let me verify what the actual action description looks like by reading the raw file:
<function_calls>
cd /tmp/release-action && sed -n '1,20p' packages/release-action/action.yml
</function_calls><function_calls>
cd /tmp/release-action && sed -n '6p' packages/release-action/action.yml | od -c
</function_calls><function_calls>
cd /tmp/release-action && python3 << 'EOF'
with open('packages/release-action/action.yml', 'r') as f:
lines = f.readlines()
for i, line in enumerate(lines[4:8], start=5):
print(f"Line {i}: {repr(line)}")
EOF
</function_calls><function_calls>
cd /tmp/release-action && git status
</function_calls><function_calls>
cd /tmp/release-action && git log --oneline | head -20
</function_calls>Let me check the PR branch if available:
<function_calls>
cd /tmp/release-action && git branch -r | head -20
</function_calls><function_calls>
cd /tmp/release-action && git show HEAD:packages/release-action/action.yml | sed -n '1,20p'
</function_calls>Let me look at the actual file content one more time to understand the complete state:
<function_calls>
cd /tmp/release-action && python3 << 'EOF'
import yamlwith open('packages/release-action/action.yml', 'r') as f:
content = f.read()
print("=== RAW FILE CONTENT ===")
print(content)
print("\n=== PARSED YAML ===")
data = yaml.safe_load(content)
print("Action description:", repr(data['inputs']['action']['description']))
print("\nInputs:")
for name in data['inputs']:
print(f" - {name}")
EOF
</function_calls>Now let me check if there are other TypeScript files that might handle update-release:
<function_calls>
cd /tmp/release-action && find . -name "*.ts" -type f | grep -v node_modules
</function_calls><function_calls>
cd /tmp/release-action && rg -i "update.*release" packages/release-action/src/
</function_calls>Let me check the main entry point and see all possible actions:
<function_calls>
cd /tmp/release-action && grep -E "^\s+case\s+['"]" packages/release-action/src/index.ts
</function_calls><function_calls>
cd /tmp/release-action && sed -n '1,150p' packages/release-action/src/index.ts | tail -100
</function_calls><function_calls>
cd /tmp/release-action && grep -n "case|action|publish-final|cut|next|patch" packages/release-action/src/index.ts | head -30
</function_calls>Let me directly read and analyze the main file more carefully:
<function_calls>
cd /tmp/release-action && wc -l packages/release-action/src/index.ts && cat packages/release-action/src/index.ts | grep -n "."
</function_calls><function_calls>
cd /tmp/release-action && cat -n packages/release-action/src/index.ts | head -100
</function_calls>Based on my verification, I can now provide the rewritten review comment:
Verify action description completeness and dist/index.js build artifact
The
actioninput description ends with a dangling"or"and does not list all supported actions. Update the description to explicitly enumerate valid options:publish-final,cut,next,patch, andupdate-pr-description, so consumers understand which actions are available.The new
release-taginput is markedrequired: falsein the metadata but appears unused in the source code. Confirm whether this input is actually required for any action mode or whether it should be removed. If retained, ensure runtime validation is implemented insrc/index.ts.The
runs.maindirective points todist/index.js, which does not exist in the repository. Verify that the build process (likelynpm run buildor similar) is executed and that the compiled artifact is either committed or the path is corrected before this action is published.
5c60609 to
c709893
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
.github/workflows/ci.yml (2)
373-389: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winUpdate the composite action’s tag authorization paths.
These steps now request publishing for tags, but
.github/actions/build-docker/action.ymlstill logs into GHCR only for internal PRs,releaseevents, ordevelop. A tag push is apushevent, so the action attempts to push without authentication. Its DockerHub login and FIPS meteor-build selection use the same obsolete release-event condition, so FIPS tag builds are also affected. Treatrefs/tags/*as the former release path in the composite action.🤖 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/ci.yml around lines 373 - 389, Update the authentication and FIPS build-condition paths in the composite action’s action.yml to treat refs/tags/* push refs like the previous release-event condition. Apply this consistently to GHCR login, DockerHub login, and FIPS meteor-build selection, while preserving the existing internal-PR and develop behavior.
674-675: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winMake reusable E2E coverage-image selection tag-aware.
Tag builds publish the Rocket.Chat coverage image as
${tag}-amd64-cov, but.github/workflows/ci-test-e2e.ymlstill appends-covonly forreleaseevents ordevelop. These EE coverage jobs therefore run the production image on tag pushes, invalidating the coverage run (and potentially leavingreport-coveragewithout data). Replace that reusable workflow’s release-event check with the same tag-ref condition.🤖 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/ci.yml around lines 674 - 675, Update the reusable E2E coverage-image selection logic to append -cov when the ref is a tag, using the same tag-ref condition as the tag image publishing flow instead of checking only release events or develop. Preserve the existing coverage behavior for non-tag builds.
🤖 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/ci.yml:
- Around line 10-14: Restrict the workflow’s tag-triggered release path around
the by-tag release classification to supported stable and RC tag patterns, or
add an early guard that stops execution before deploy, DockerHub promotion, docs
updates, or GitHub-release publication when the release output is empty. Ensure
unrecognized tags such as foo cannot publish or overwrite artifacts.
---
Outside diff comments:
In @.github/workflows/ci.yml:
- Around line 373-389: Update the authentication and FIPS build-condition paths
in the composite action’s action.yml to treat refs/tags/* push refs like the
previous release-event condition. Apply this consistently to GHCR login,
DockerHub login, and FIPS meteor-build selection, while preserving the existing
internal-PR and develop behavior.
- Around line 674-675: Update the reusable E2E coverage-image selection logic to
append -cov when the ref is a tag, using the same tag-ref condition as the tag
image publishing flow instead of checking only release events or develop.
Preserve the existing coverage behavior for non-tag builds.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f69e8836-845a-4045-af92-ae984a030a74
📒 Files selected for processing (1)
.github/workflows/ci.yml
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
- GitHub Check: Hacktron Security Check
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-04-27T18:32:21.871Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 40321
File: .github/workflows/ci.yml:137-145
Timestamp: 2026-04-27T18:32:21.871Z
Learning: In .github/workflows/ci.yml, the `diff` step under `release-versions` intentionally uses a bash `if` with `gh pr diff ... | grep -q ...; then ... fi`. For non-`pull_request` workflow triggers where `GH_PR_NUM` can be empty, the `gh` command may fail, but the surrounding bash `if` is relied on to treat that failure as the condition being false and skip the `then` block, allowing the step/job to exit cleanly. Do not add extra guards for non-PR event types unless this failure/skip behavior is intentionally changed.
Applied to files:
.github/workflows/ci.yml
🪛 zizmor (1.26.1)
.github/workflows/ci.yml
[warning] 194-199: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[warning] 250-251: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[info] 213-213: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[info] 215-215: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[info] 220-220: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[info] 220-220: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[info] 240-240: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[info] 241-241: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[error] 229-229: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default
(cache-poisoning)
[info] 270-270: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[error] 275-275: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default
(cache-poisoning)
[warning] 326-326: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[warning] 369-369: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[warning] 452-459: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[warning] 527-527: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[warning] 549-549: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[warning] 832-832: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[warning] 927-927: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 930-930: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default
(cache-poisoning)
[warning] 1046-1051: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[warning] 1101-1107: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[info] 1154-1154: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[info] 1196-1196: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[info] 1196-1196: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[warning] 1227-1232: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[info] 1260-1260: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[info] 1260-1260: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[info] 1295-1295: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[info] 1295-1295: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #37637 +/- ##
===========================================
- Coverage 68.64% 68.63% -0.02%
===========================================
Files 4164 4164
Lines 158957 158957
Branches 28160 28160
===========================================
- Hits 109113 109094 -19
- Misses 44678 44686 +8
- Partials 5166 5177 +11
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
c709893 to
26cae2f
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
packages/release-action/src/publishRelease.ts (1)
119-120: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove the implementation comments from this TypeScript file.
The repository guideline requires concise implementation code without comments. Move this rationale to release documentation if it must remain.
As per coding guidelines, avoid code comments in the implementation.
🤖 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 `@packages/release-action/src/publishRelease.ts` around lines 119 - 120, Remove the two implementation comment lines near the release publishing logic in publishRelease.ts, leaving the surrounding code unchanged; preserve the rationale only in release documentation if needed.Source: Coding guidelines
🤖 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/ci.yml:
- Around line 1309-1315: Update both the Docker publish job and GitHub release
marking step to pass the release and latest-release outputs through environment
variables before entering Bash. In the comparison around MAKE_LATEST, compare
the quoted shell variables rather than embedding `${{
needs.release-versions.outputs.latest-release }}` or the release output
directly, preserving the existing latest-tag selection behavior.
In `@docs/release-process.md`:
- Line 66: Update the “Build and tests” statement in the release-process
documentation to remove the absolute claim that tag pushes never skip tests.
Limit the guarantee to release bump commits, or explicitly describe the existing
exception when the exact commit already has a successful “Tests Done” check.
In `@packages/release-action/src/publishRelease.ts`:
- Around line 119-126: Reorder the release flow so the draft created by
createRelease exists before pushChanges emits the newVersion tag event; ensure
tag-triggered CI cannot run before draft lookup succeeds, while preserving the
existing release metadata and subsequent artifact-publishing behavior.
---
Nitpick comments:
In `@packages/release-action/src/publishRelease.ts`:
- Around line 119-120: Remove the two implementation comment lines near the
release publishing logic in publishRelease.ts, leaving the surrounding code
unchanged; preserve the rationale only in release documentation if needed.
🪄 Autofix
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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: f3b9d00f-cd50-43dd-823f-5da9ea37d0be
📒 Files selected for processing (5)
.github/workflows/ci.ymldocs/release-process.mdpackages/release-action/action.ymlpackages/release-action/src/bumpNextVersion.tspackages/release-action/src/publishRelease.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/release-action/src/bumpNextVersion.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (4)
- GitHub Check: 📦 Build Packages
- GitHub Check: Hacktron Security Check
- GitHub Check: CodeQL-Build
- GitHub Check: CodeQL-Build
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)
**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation
Files:
packages/release-action/src/publishRelease.ts
🧠 Learnings (4)
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In the Rocket.Chat repository, do not reference Biome lint rules in code review feedback. Biome is not used even if biome.json exists; only reference Biome rules if there is explicit, project-wide usage documented. For TypeScript files, review lint implications without Biome guidance unless the project enables Biome rules.
Applied to files:
packages/release-action/src/publishRelease.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In this repository (RocketChat/Rocket.Chat), Biome lint rules are not used even if a biome.json exists. When reviewing TypeScript files (e.g., packages/ui-voip/src/providers/useMediaSession.ts), ensure lint suggestions do not reference Biome-specific rules. Rely on general ESLint/TypeScript lint rules and project conventions instead.
Applied to files:
packages/release-action/src/publishRelease.ts
📚 Learning: 2026-05-06T12:21:44.083Z
Learnt from: juliajforesti
Repo: RocketChat/Rocket.Chat PR: 40256
File: apps/meteor/client/components/CreateDiscussion/CreateDiscussion.tsx:121-149
Timestamp: 2026-05-06T12:21:44.083Z
Learning: Field wrappers in rocket.chat/fuselage-forms (Field, FieldLabel, FieldRow, FieldError, FieldHint) auto-create htmlFor/id associations, aria-describedby, and role="alert" for errors. Do not manually set htmlFor, id, aria-describedby, or role attributes when using these wrappers. This automatic wiring does not apply to plain rocket.chat/fuselage components, which require explicit ID wiring per the accessibility docs. In code reviews, prefer using fuselage-forms wrappers for form fields and verify there is no unnecessary manual ID/aria wiring in files that use these wrappers. If a component uses plain fuselage components, ensure proper id wiring as per docs.
Applied to files:
packages/release-action/src/publishRelease.ts
📚 Learning: 2026-04-27T18:32:21.871Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 40321
File: .github/workflows/ci.yml:137-145
Timestamp: 2026-04-27T18:32:21.871Z
Learning: In .github/workflows/ci.yml, the `diff` step under `release-versions` intentionally uses a bash `if` with `gh pr diff ... | grep -q ...; then ... fi`. For non-`pull_request` workflow triggers where `GH_PR_NUM` can be empty, the `gh` command may fail, but the surrounding bash `if` is relied on to treat that failure as the condition being false and skip the `then` block, allowing the step/job to exit cleanly. Do not add extra guards for non-PR event types unless this failure/skip behavior is intentionally changed.
Applied to files:
.github/workflows/ci.yml
🪛 LanguageTool
docs/release-process.md
[uncategorized] ~3-~3: The official name of this software platform is spelled with a capital “H”.
Context: ... implemented by the GitHub workflows in .github/workflows/ and the custom changesets-b...
(GITHUB)
[uncategorized] ~7-~7: The official name of this software platform is spelled with a capital “H”.
Context: ...tag push triggers the main CI pipeline (.github/workflows/ci.yml), which builds, tests...
(GITHUB)
[style] ~9-~9: For conciseness, consider replacing this expression with an adverb.
Context: ...itHub Release is created as a draft at the moment the tag is pushed and is only flipped t...
(AT_THE_MOMENT)
[grammar] ~66-~66: Use a hyphen to join words.
Context: ...essful "Tests Done" check, which release bump commits don't. 4. GHCR publish ...
(QB_NEW_EN_HYPHEN)
🪛 zizmor (1.29.0)
.github/workflows/ci.yml
[info] 213-213: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[info] 215-215: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[info] 1310-1310: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[info] 1310-1310: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
🔇 Additional comments (5)
packages/release-action/action.yml (1)
6-6: LGTM!docs/release-process.md (1)
1-65: LGTM!Also applies to: 67-102
.github/workflows/ci.yml (3)
11-12: Restrict release processing to recognized release tags.
tags: '*'still starts deploy, image publication, service notification, GitHub-release publication, and documentation updates for tags that leavereleaseempty. This duplicates the existing finding.
18-20: LGTM!
979-995: LGTM!
| MAKE_LATEST=false | ||
| if [[ '${{ needs.release-versions.outputs.release }}' == 'latest' && '${{ needs.release-versions.outputs.latest-release }}' == "$GITHUB_REF_NAME" ]]; then | ||
| MAKE_LATEST=true | ||
| fi | ||
|
|
||
| gh api -X PATCH "repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}" \ | ||
| -F draft=false -f make_latest="$MAKE_LATEST" |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== workflow excerpt =="
sed -n '1130,1335p' .github/workflows/ci.yml | cat -n | sed 's/^/LINE /'
echo "== release-versions output definitions =="
rg -n "release-versions|outputs:\s*$|release:|latest-release|GH_REF_NAME|GITHUB_REF_NAME|MAKE_LATEST|publish" .github/workflows/ci.yml
echo "== focused snippets around candidates =="
python3 - <<'PY'
from pathlib import Path
p=Path('.github/workflows/ci.yml')
lines=p.read_text().splitlines()
ranges=[(1140,1190),(1260,1335)]
for a,b in ranges:
print(f"\n--- lines {a}-{b} ---")
for i in range(a,b+1):
print(f"{i}: {lines[i-1]}")
PY
echo "== deterministic Bash single-quote interpolation illustration =="
python3 - <<'PY'
tags=["latest","latest'; printf 'INJECTED'; echo; #","latest\",\"latest'"]
for tag in tags:
# The template expands GitHub Actions expression literally; assume outputs.latest-release becomes this tag.
line = f"if [[ 'latest' == 'latest' && '{tag}' == \"main\" ]]; then ..."
print(f"tag={tag!r}")
print(f"generated_line={line}")
try:
exec(line)
print("parsed_and_executed=no_syntax_error")
except Exception as e:
print(f"runtime_or_parse_{type(e).__name__.lower()}={e}")
PYRepository: RocketChat/Rocket.Chat
Length of output: 27422
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== release-versions candidate excerpt =="
sed -n '1,150p' .github/workflows/ci.yml | cat -n
echo "== repo refs to release-versions/latest outputs =="
rg -n "steps\.latest|GITHUB_OUTPUT|latest-release|needs\.release-versions\.outputs\.latest-release|needs\.release-versions\.outputs\.release" .github/workflows/ci.yml
echo "== deterministic Bash interpolation simulation for single-quoted injection =="
python3- <<'PY'
from pathlib import Path
workflow = Path(".github/workflows/ci.yml").read_text().splitlines()
template = workflow[1168]
print("template:", template)
for tag in ["latest", "latest'; printf 'INJECTED'>&2; echo \"; main", "latest\"latest'1", "latest' && [[ \"a\" == a ]]; then printf 'INJECTED' >&2; then :; "]:
line = template.replace("${{ needs.release-versions.outputs.latest-release }}", tag)
print("tag:", repr(tag))
print("line:", line)
try:
exec(line)
print("exec_result:", "success")
except Exception as e:
print("exec_exception:", type(e).__name__, str(e).splitlines()[0])
PYRepository: RocketChat/Rocket.Chat
Length of output: 9205
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== deterministic Bash single-quote parsing probe =="
python3 <<'PY'
import subprocess, sys, textwrap
templates = [
"if [[ '${latest}' == \"main\" ]]; then echo 1; fi",
"if [[ '${latest}' == \"main\" ]]; then printf 'INJECTED' >&2; fi",
]
for t in templates:
for latest in ["1.0.0", "latest'", "latest'; printf 'INJECTED' >&2; echo"]:
code = t.replace("${latest}", latest)
print(f"latest={latest!r}")
print(f"code={code!r}")
try:
out = subprocess.run(
["bash", "-c", code],
text=True,
input="",
stderr=subprocess.PIPE,
timeout=5,
)
print(f"stdout={out.stdout!r} stderr={out.stderr!r} returncode={out.returncode}")
except Exception as e:
print(f"exception={type(e).__name__}: {e}")
print("---")
PY
echo "== deterministic Bash source-string comparison quote-split example =="
bash -c 'latest="latest'\''"; if [[ '"'"''"'"'${latest}'"'"'"' == "main" ]]; then printf "matched\n"; fi' || true
echo "== local run with controlled output is unavailable: inspect fixed assignment pattern =="
sed -n '1155,1174p;1303,1311p' .github/workflows/ci.ymlRepository: RocketChat/Rocket.Chat
Length of output: 1594
Injection (CWE-78): Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
Reachability: Internal
Do not embed latest-release directly in the Bash comparison.
The release tag flow is pushed to *, and latest-release reads the newest ls-remote tag. A tag name can contain a single quote, so using it inside ${{ ... }} can change the command source when that tag becomes the selected latest tag. Assign release and latest-release to environment variables first, then compare quoted shell variables in both the Docker publish job and the GitHub release marking step.
🧰 Tools
🪛 zizmor (1.29.0)
[info] 1310-1310: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[info] 1310-1310: 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/ci.yml around lines 1309 - 1315, Update both the Docker
publish job and GitHub release marking step to pass the release and
latest-release outputs through environment variables before entering Bash. In
the comparison around MAKE_LATEST, compare the quoted shell variables rather
than embedding `${{ needs.release-versions.outputs.latest-release }}` or the
release output directly, preserving the existing latest-tag selection behavior.
Source: Linters/SAST tools
|
|
||
| 1. **`release-versions`** — classifies the tag by name: `X.Y.Z` → release `latest`, `X.Y.Z-rc.N` → `release-candidate`. It also computes `latest-release`, the newest non-rc/non-beta tag in the repo, used later to decide whether this tag should become `latest` on Docker Hub and GitHub. | ||
| 2. **`notify-draft-services`** — registers the version on `releases.rocket.chat` as a draft (`draftAs: candidate` or `stable`), so internal services know a release is in flight. | ||
| 3. **Build and tests** — packages and the Meteor app are built, and the full test suite (unit, API, UI, apps, federation) runs. Tag pushes never skip tests: the merge-queue test-guard only skips when the exact commit already has a successful "Tests Done" check, which release bump commits don't. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Remove the absolute “never skip” claim.
The sentence says tag pushes never skip tests, then states that the guard can skip when the exact commit already has a successful Tests Done check. Limit the guarantee to release bump commits, or document the exception.
Suggested wording
-Tag pushes never skip tests: the merge-queue test-guard only skips when the exact commit already has a successful "Tests Done" check, which release bump commits don't.
+Release bump tag commits do not skip tests. For other tags, the merge-queue test-guard can skip a build when the exact tagged commit already has a successful "Tests Done" check.📝 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.
| 3. **Build and tests** — packages and the Meteor app are built, and the full test suite (unit, API, UI, apps, federation) runs. Tag pushes never skip tests: the merge-queue test-guard only skips when the exact commit already has a successful "Tests Done" check, which release bump commits don't. | |
| 3. **Build and tests** — packages and the Meteor app are built, and the full test suite (unit, API, UI, apps, federation) runs. Release bump tag commits do not skip tests. For other tags, the merge-queue test-guard can skip a build when the exact tagged commit already has a successful "Tests Done" check. |
🧰 Tools
🪛 LanguageTool
[grammar] ~66-~66: Use a hyphen to join words.
Context: ...essful "Tests Done" check, which release bump commits don't. 4. GHCR publish ...
(QB_NEW_EN_HYPHEN)
🤖 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 `@docs/release-process.md` at line 66, Update the “Build and tests” statement
in the release-process documentation to remove the absolute claim that tag
pushes never skip tests. Limit the guarantee to release bump commits, or
explicitly describe the existing exception when the exact commit already has a
successful “Tests Done” check.
| // the release stays a draft until CI publishes all artifacts for the tag; | ||
| // a draft can't be 'latest', so CI decides make_latest when publishing it | ||
| await octokit.rest.repos.createRelease({ | ||
| name: newVersion, | ||
| tag_name: newVersion, | ||
| body: releaseBody, | ||
| prerelease, | ||
| make_latest: isLatestRelease ? 'true' : 'false', | ||
| draft: true, |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
Create the draft release before the tag-triggered workflow can run.
pushChanges() pushes newVersion before createRelease() executes. The tag workflow can therefore search for the draft before it exists, fail the release job, and leave the tag unpublished. Create the draft before emitting the tag event, or make the promotion job poll and retry until the matching draft exists. The PR sequence also places tag-triggered CI before draft lookup. (github.com)
🤖 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 `@packages/release-action/src/publishRelease.ts` around lines 119 - 126,
Reorder the release flow so the draft created by createRelease exists before
pushChanges emits the newVersion tag event; ensure tag-triggered CI cannot run
before draft lookup succeeds, while preserving the existing release metadata and
subsequent artifact-publishing behavior.
Proposed changes (including videos or screenshots)
Issue(s)
ARCH-1900
Steps to test or reproduce
Further comments
Summary by CodeRabbit
Release Automation
Release Action
Documentation