ci: fix broken workflows, tighten permissions, add community health files - #1374
ci: fix broken workflows, tighten permissions, add community health files#1374Hunter275 wants to merge 1 commit into
Conversation
…iles Audit of the GitHub Actions setup and repository metadata. Silent breakages: - crowdin-upload-sources: path filter had a leading slash, so the glob never matched and English source strings were never auto-uploaded to Crowdin. - pr.yml: `pnpm run check` is `lint && format`, so the linter ran twice while the only formatting check carried `continue-on-error`. Formatting was never enforced. Replaced with a direct `format` call, and added a typecheck step (`vite build` transpiles without checking types, so nothing typechecked). - release-web / nightly: `org.opencontainers.image.source` was set to `owner/repo` rather than a URL, so GHCR never linked the image to the repo. - update-stable-from-master: concurrency group keyed on `github.run_id`, which is unique per run, so overlapping releases never serialized before force-pushing `stable`. - Removed a leftover debug step grepping the lockfile for hardcoded versions. Security: - release-protobufs: `permissions: write-all` -> `contents: read`. - ci.yml: dropped unused `contents: write` / `packages: write`. - Added explicit `permissions` to e2e and the three crowdin workflows. - Moved all untrusted interpolation out of `run:` bodies into `env:`, and added input validation for the protobufs `version` and packages `bump`. - Pinned bufbuild/buf-setup-action@main -> @v1 and denoland/setup-deno@main (deno-version: rc) -> @v2 / v2.x. - Added workflow-lint.yml running actionlint and zizmor with SARIF upload. - npm publish now emits build provenance. Reliability and cost: - `timeout-minutes` on every job; concurrency groups where they were missing. - e2e: draft filter, cancel-in-progress, Playwright browser cache keyed on the resolved version, and pip caching. - nightly: moved from buildah/podman to the same buildx path releases use, so it actually exercises the release image build; added a gate that skips scheduled runs when nothing landed in the last day. - release-packages: validate all targets before mutating the tree, and use a per-run bump branch instead of a fixed `version_bump` that broke on rerun. - stale: daily instead of every 4h, and `only-issue-labels` corrected to `bug` to match the label the issue template actually applies. - dependabot: grouped github-actions and dev-dependencies to cut PR churn. - `vitest` -> `vitest run` in CI. Repository metadata: - Added SECURITY.md, CODE_OF_CONDUCT.md, a root CONTRIBUTING.md pointer, and ISSUE_TEMPLATE/config.yml with community contact links. - Added CODEOWNERS with all rules commented out pending real handles. Formatting and typecheck steps are intentionally non-blocking for now; each carries a TODO(ci) explaining the pre-existing failures to fix before flipping. Validated with actionlint v1.7.12: 0 errors across all 13 workflows. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Someone is attempting to deploy a commit to the Meshtastic Team on Vercel. A member of the Team first needs to authorize it. |
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
|
|
||
| - name: Cache Playwright browsers | ||
| id: pw-cache | ||
| uses: actions/cache@v4 |
| should-build: ${{ steps.check.outputs.should-build }} | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v7 |
| org.opencontainers.image.revision=${{ github.sha }} | ||
| org.opencontainers.image.created=${{ steps.meta.outputs.created }} | ||
| - name: Set up Buildx | ||
| uses: docker/setup-buildx-action@v4 |
| id: push-to-registry | ||
| uses: redhat-actions/push-to-registry@v3 | ||
| - name: Login to GHCR | ||
| uses: docker/login-action@v4 |
| # Same builder as release-web.yml, so the nightly actually exercises the | ||
| # release image build path instead of a parallel buildah/podman stack. | ||
| - name: Build and push image (multi-arch) | ||
| uses: docker/build-push-action@v7 |
| - name: Checkout code | ||
| uses: actions/checkout@v7 |
| - name: Print image URLs | ||
| run: | | ||
| echo "Moving tag: ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.moving_tag }}" | ||
| echo "Moving tag: ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.moving_tag }}" |
| - name: Print image URLs | ||
| run: | | ||
| echo "Moving tag: ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.moving_tag }}" | ||
| echo "Moving tag: ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.moving_tag }}" |
| - name: Checkout code | ||
| uses: actions/checkout@v7 |
| - name: Checkout code | ||
| uses: actions/checkout@v7 |
📝 WalkthroughWalkthroughThe pull request adds repository governance documents and updates GitHub configuration. It standardizes permissions, concurrency, timeouts, validation, dependency grouping, localization automation, issue processing, container publishing, and package, protobuf, web, and stable-branch releases. ChangesRepository automation
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
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 |
There was a problem hiding this comment.
Actionable comments posted: 13
🧹 Nitpick comments (2)
.github/workflows/workflow-lint.yml (1)
36-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPin the actionlint Docker image to a specific version, not
:latest.
docker://rhysd/actionlint:latestfloats to whatever image is current at run time. A newer image release can change actionlint behavior without any change in this repository and undermines reproducible CI. Use a fixed stable tag, such asv1.7.12, for this runner.🤖 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/workflow-lint.yml around lines 36 - 38, Update the actionlint Docker image reference in the workflow step using rhysd/actionlint so it uses a fixed stable version tag such as v1.7.12 instead of :latest, preserving the existing args configuration..github/workflows/release-protobufs.yml (1)
145-149: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPin Deno to an exact version for JSR publication.
deno-version: v2.xis a SemVer range, so a rerun can resolve to a later stable patch. Use the repository-declared exact Deno version or add a version file, and setdeno-version/deno-version-fileaccordingly.🤖 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/release-protobufs.yml around lines 145 - 149, Update the Set up Deno step to use the repository-declared exact Deno version instead of the floating v2.x range, using deno-version or deno-version-file as appropriate. Ensure reruns of the release-protobufs workflow consistently resolve the same Deno version for JSR publication.
🤖 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/dependabot.yml:
- Around line 24-25: Update the npm ecosystem configuration’s labels list to
include both the existing dependencies label and the default npm ecosystem
label, preserving both labels on Dependabot npm update pull requests.
In @.github/pull_request_template.md:
- Around line 46-48: Update .github/pull_request_template.md lines 46-48 to
state that lint/format validation and web typechecking were run, while noting
both are currently non-blocking. Update CONTRIBUTING.md lines 24-33 to document
lint/format checks, unit tests, and web typechecking as required contributor
checks; distinguish pnpm run check:fix from CI validation and link to the task
tracking the non-blocking formatting and typecheck status.
In @.github/SECURITY.md:
- Around line 19-21: Add a private fallback security-report contact alongside
the GitHub Security Advisories URL in .github/SECURITY.md lines 19-21, and add
the same fallback contact in .github/ISSUE_TEMPLATE/config.yml lines 15-17 when
the advisory form is unavailable; preserve the existing advisory guidance.
In @.github/workflows/crowdin-download.yml:
- Around line 8-11: Pin the actions used by the Crowdin workflow, specifically
actions/checkout and crowdin/github-action, to reviewed full-length commit SHAs
instead of the mutable v7 and v2 tags. Keep the existing write permissions and
action configuration unchanged.
In @.github/workflows/release-packages.yml:
- Around line 122-138: Update the release workflow after the version-bump PR
creation in the publish job so JSR and npm publishing runs only after that PR is
merged; otherwise commit the version bump directly before publishing. Ensure the
existing PR creation flow does not proceed to registry publishing while its
branch remains unmerged.
- Around line 140-150: Update the package build invocations in the “Build
selected packages” step and the corresponding commands at the referenced lines
to use the declared build:npm script instead of build, ensuring all package
selections—including transport-web-bluetooth—build successfully before
publishing.
- Around line 61-72: Update the target validation loop in the release workflow
to build an allowlist from direct package directories under packages and reject
each requested TARGETS entry unless it exactly matches one of those paths. Do
not rely solely on checking "$dir/package.json", so inputs such as "../" or
packages/../ cannot pass validation; preserve the existing fail-fast error
behavior for non-allowlisted targets.
In @.github/workflows/release-protobufs.yml:
- Around line 20-22: Update the concurrency group around the release workflow’s
version input so equivalent values with and without the optional “v” resolve to
the same canonical key, matching the normalization performed before publication.
Ensure all protobuf releases for a given package version serialize through one
concurrency group while preserving the existing publish behavior.
- Around line 44-59: Update the version validation in the release workflow’s
shell step to reject any newline or other multiline input by requiring the
entire INPUT_VERSION value to match the semver pattern, not merely one line. Use
a fixed invalid-version error message without interpolating VERSION before
writing it to GITHUB_ENV or emitting any workflow command, while preserving
valid version handling.
In @.github/workflows/release-web.yml:
- Around line 26-28: Update the workflow’s concurrency group for the release
image publishing job so all runs that promote mutable tags such as latest,
major, and major.minor use one shared group instead of a release-specific
expression. Keep cancel-in-progress disabled so releases queue and publish in
order.
- Around line 150-168: Update the release output block to print each generated
image reference from PUSHED_TAGS as a docker pull example, instead of
constructing references from REGISTRY_IMAGE, PULL_TAG, and SHORTSHA. Preserve
the existing latest example condition for non-prerelease releases if applicable,
but ensure all generated tags from steps.meta.outputs.tags are used so
normalized image names and tags match the pushed images.
In @.github/workflows/update-stable-from-master.yml:
- Around line 10-14: Update the workflow’s concurrency-protected release flow to
perform a post-lock staleness check before checking out or force-pushing the
release tag. Derive the current latest release/source commit and compare it with
the run’s release source and current stable target; skip or cancel the run when
its source is older, while allowing the newest release to proceed.
In @.github/workflows/workflow-lint.yml:
- Around line 56-67: Update the “Upload SARIF” step’s if condition to verify
that zizmor.sarif contains content rather than only checking its existence via
hashFiles. Preserve the existing behavior of skipping upload when the
redirection-created file is empty after a failed “Run zizmor” step.
---
Nitpick comments:
In @.github/workflows/release-protobufs.yml:
- Around line 145-149: Update the Set up Deno step to use the
repository-declared exact Deno version instead of the floating v2.x range, using
deno-version or deno-version-file as appropriate. Ensure reruns of the
release-protobufs workflow consistently resolve the same Deno version for JSR
publication.
In @.github/workflows/workflow-lint.yml:
- Around line 36-38: Update the actionlint Docker image reference in the
workflow step using rhysd/actionlint so it uses a fixed stable version tag such
as v1.7.12 instead of :latest, preserving the existing args configuration.
🪄 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 Plus
Run ID: 1775c352-9e70-488f-a712-5d5212cfb870
📒 Files selected for processing (20)
.github/CODEOWNERS.github/ISSUE_TEMPLATE/config.yml.github/SECURITY.md.github/dependabot.yml.github/pull_request_template.md.github/workflows/ci.yml.github/workflows/crowdin-download.yml.github/workflows/crowdin-upload-sources.yml.github/workflows/crowdin-upload-translations.yml.github/workflows/e2e.yml.github/workflows/inactive-issue.yml.github/workflows/nightly.yml.github/workflows/pr.yml.github/workflows/release-packages.yml.github/workflows/release-protobufs.yml.github/workflows/release-web.yml.github/workflows/update-stable-from-master.yml.github/workflows/workflow-lint.ymlCODE_OF_CONDUCT.mdCONTRIBUTING.md
| labels: | ||
| - "dependencies" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== dependabot.yml =="
cat -n .github/dependabot.yml 2>/dev/null || true
echo
echo "== repo automation references to npm label =="
rg -n '"npm"|: "npm"|github-actions/npm|npm|dependencies' .github workflows . 2>/dev/null | head -200 || true
echo
echo "== docs excerpt for labels =="
python3 - <<'PY'
try:
import urllib.request
url = "https://docs.github.com/en/code-security/tutorials/secure-your-dependencies/customizing-dependabot-prs"
req = urllib.request.Request(url, headers={"User-Agent":"CodeRabbit-verifier"})
with urllib.request.urlopen(req, timeout=20) as resp:
text = resp.read().decode("utf-8","replace")
for token in ["labels:", "npm", "dependencies", "github-actions"]:
print(f"-- occurrences of [{token}] --")
hits = [line.strip() for line in text.splitlines() if token in line]
print("\n".join(hits[:40]))
except Exception as e:
print(f"DOC_FETCH_ERROR: {e}")
PYRepository: meshtastic/web
Length of output: 13752
🌐 Web query:
GitHub Dependent PR labels default labels npm package ecosystem labels only configuration
💡 Result:
By default, GitHub Dependabot automatically applies labels to pull requests: a dependencies label, an ecosystem-specific label (e.g., npm, java), and SemVer labels if they exist in the repository [1][2]. These default labels are created automatically if they do not exist [1][2]. You can override this behavior by using the labels option within your dependabot.yml file for a specific package ecosystem [1][3]. When you define the labels option: 1. The labels you specify replace the default labels entirely for that package ecosystem [1][4]. 2. SemVer labels (major, minor, patch) will continue to be applied automatically, in addition to your custom labels, if they exist in your repository [1][4]. 3. The custom labels you specify must already exist in your repository; if a label is not defined, Dependabot will ignore it [1][2]. 4. To remove all labels, including defaults, you can set an empty list: labels: [] [1][4]. There is no configuration setting to explicitly restrict Dependabot to only apply ecosystem labels (e.g., npm) while keeping other defaults; you must manually define the specific set of labels you want to use (including the ecosystem label if you wish to keep it) in the configuration file [1][2].
Citations:
- 1: https://docs.github.com/en/code-security/reference/supply-chain-security/dependabot-options-reference
- 2: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/customizing-dependency-updates
- 3: https://docs.github.com/en/code-security/tutorials/secure-your-dependencies/customizing-dependabot-prs
- 4: https://docs.github.com/en/code-security/reference/supply-chain-security/dependabot-options-reference?c=loopfyx
Preserve the npm ecosystem label on npm updates.
labels replaces Dependabot’s default labels entirely for that ecosystem, so this npm config currently applies only dependencies instead of also applying the default npm label. Add npm if you want Dependabot PRs to keep the ecosystem label.
Proposed fix
labels:
- "dependencies"
+ - "npm"📝 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.
| labels: | |
| - "dependencies" | |
| labels: | |
| - "dependencies" | |
| - "npm" |
🤖 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/dependabot.yml around lines 24 - 25, Update the npm ecosystem
configuration’s labels list to include both the existing dependencies label and
the default npm ecosystem label, preserving both labels on Dependabot npm update
pull requests.
| - [ ] `pnpm run check` passes (lint + format) | ||
| - [ ] `pnpm --filter ./apps/web run typecheck` passes | ||
| - [ ] `pnpm exec vitest run` passes |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
rg -n -C 3 \
'check:fix|pnpm run check|typecheck|continue-on-error' \
--glob 'package.json' \
--glob '.github/workflows/*.yml' \
--glob '.github/workflows/*.yaml' \
--glob 'CONTRIBUTING.md' \
--glob '.github/pull_request_template.md'Repository: meshtastic/web
Length of output: 152
🏁 Script executed:
#!/usr/bin/envbash
set -u
printf 'Tracked relevant files:\n'
git ls-files | rg '(^package\.json$|CONTRIBUTING\.md$|\.github/pull_request_template\.md$|\.github/workflows/.*\.ya?ml$)' || true
printf '\npackage.json scripts relevant to check/lint/typecheck/tests:\n'
if [ -f package.json ]; then
python3 - <<'PY'
import json
from pathlib import Path
p=Path('package.json')
data=json.loads(p.read_text())
for k,v in data.get('scripts',{}).items():
s_k=k.lower()
s_v=str(v).lower()
if 'check' in s_k or any(x in ('check','lint','format','typecheck','test','vitest') for x in (k,v.split(maxsplit=3)) if str(x).lower()):
print(f'{k}: {v}')
PY
fi
printf '\nSearch all tracked text matching keywords:\n'
rg -n -C 2 \
'check:fix|`pnpm run check|pnpm run check|typecheck|continue-on-error|CI runs|green PR|non-blocking|formatting' . || trueRepository: meshtastic/web
Length of output: 4654
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '--- .github/pull_request_template.md ---\n'
cat -n .github/pull_request_template.md | sed -n '1,90p'
printf '\n--- CONTRIBUTING.md ---\n'
cat -n CONTRIBUTING.md | sed -n '1,50p'
printf '\n--- .github/pr.yml relevant validation blocks ---\n'
rg -n -C 4 'check|typecheck|format|lint|continue-on-error|pnpm run|pnpm exec|checks' .github/workflows/pr.yml .github/workflows/ci.yml package.json apps/web/package.json || true
printf '\n--- PR-NOTS.md objective context ---\n'
cat -n PR-NOTES.md | sed -n '45,70p'Repository: meshtastic/web
Length of output: 243
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- .github/pull_request_template.md ---'
cat -n .github/pull_request_template.md | sed -n '1,90p'
printf '%s\n' ''
printf '%s\n' '--- CONTRIBUTING.md ---'
cat -n CONTRIBUTING.md | sed -n '1,50p'
printf '%s\n' ''
printf '%s\n' '--- .github/pr.yml relevant validation blocks ---'
rg -n -C 4 'check|typecheck|format|lint|continue-on-error|pnpm run|pnpm exec|checks' .github/workflows/pr.yml .github/workflows/ci.yml package.json apps/web/package.json || true
printf '%s\n' ''
printf '%s\n' '--- PR-NOTES.md objective context ---'
cat -n PR-NOTES.md | sed -n '45,70p'Repository: meshtastic/web
Length of output: 11774
Align contributor guidance with the blocking checks.
.github/pr.yml makes formatting and typechecking non-blocking with continue-on-error, but the PR checklist and quick start present those same checks as required for merge/green PR.
.github/pull_request_template.md#L46-L48: note that formatting and typechecking were run and are currently non-blocking.CONTRIBUTING.md#L24-L33: document that lint/format check, unit tests, and web typecheck are required; clarify that formatting and typecheck are currently non-blocking with a fix-task link and distinguishpnpm run check:fixfrom CI validation.
📍 Affects 2 files
.github/pull_request_template.md#L46-L48(this comment)CONTRIBUTING.md#L24-L33
🤖 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/pull_request_template.md around lines 46 - 48, Update
.github/pull_request_template.md lines 46-48 to state that lint/format
validation and web typechecking were run, while noting both are currently
non-blocking. Update CONTRIBUTING.md lines 24-33 to document lint/format checks,
unit tests, and web typechecking as required contributor checks; distinguish
pnpm run check:fix from CI validation and link to the task tracking the
non-blocking formatting and typecheck status.
| Report privately through GitHub Security Advisories: | ||
|
|
||
| <https://github.com/meshtastic/web/security/advisories/new> |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
status="$(gh api repos/meshtastic/web \
--jq '.security_and_analysis.private_vulnerability_reporting.status')"
printf 'private_vulnerability_reporting=%s\n' "$status"
test "$status" = enabledRepository: meshtastic/web
Length of output: 185
🌐 Web query:
GitHub private vulnerability reporting enabled feature request reporting secrets advisory form repository security_advisories new
💡 Result:
GitHub's private vulnerability reporting (PVR) is an opt-in feature that allows security researchers to privately disclose vulnerabilities to repository maintainers directly on the platform [1][2]. For Repository Maintainers To enable this feature, repository owners or administrators can navigate to the repository's security settings [3]: 1. Go to the repository's main page on GitHub. 2. Navigate to Settings and then to Code security and analysis [3]. 3. Under the Advanced Security section, locate Private vulnerability reporting and click Enable [3]. Once enabled, a Report a vulnerability button appears on the repository's Security tab (specifically under the Advisories section), providing a structured form for researchers to submit findings [3]. When a report is submitted, administrators and security managers are notified [3][4]. Maintainers can then review the report, request additional information, or accept the report to initiate a private, collaborative fix—which can include creating a temporary private fork for code development [4]. For Security Researchers When a repository has PVR enabled, you can privately report a vulnerability using the following steps [3][5]: 1. Navigate to the repository on GitHub. 2. Click the Security tab and then click Advisories [3]. 3. Click the Report a vulnerability button to access the private reporting form [3]. 4. Fill out the required details regarding the vulnerability [2]. If a repository does not have PVR enabled, you should follow the repository's documented security policy (typically found in a SECURITY.md file) or contact the maintainers directly [6]. Additionally, GitHub provides a REST API that allows researchers to privately report vulnerabilities programmatically [3][6].
Citations:
- 1: https://docs.github.com/en/code-security/concepts/vulnerability-reporting-and-management/coordinated-disclosure
- 2: https://github.blog/security/vulnerability-research/security-research-without-ever-leaving-github-from-code-scanning-to-cve-via-codespaces-and-private-vulnerability-reporting/
- 3: https://docs.github.com/en/code-security/how-tos/report-and-fix-vulnerabilities/configure-vulnerability-reporting/configure-for-a-repository
- 4: https://docs.github.com/en/code-security/how-tos/report-and-fix-vulnerabilities/fix-reported-vulnerabilities/manage-vulnerability-reports
- 5: https://docs.github.com/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability
- 6: https://docs.github.com/en/code-security/how-tos/report-and-fix-vulnerabilities/fix-reported-vulnerabilities/create-repository-advisory
Add a private fallback contact for security reports.
Private vulnerability reporting is not enabled for this repository, so both .github/SECURITY.md#L19-L21 and .github/ISSUE_TEMPLATE/config.yml#L15-L17 should add another private contact if this URL does not provide the advisory form.
📍 Affects 2 files
.github/SECURITY.md#L19-L21(this comment).github/ISSUE_TEMPLATE/config.yml#L15-L17
🤖 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/SECURITY.md around lines 19 - 21, Add a private fallback
security-report contact alongside the GitHub Security Advisories URL in
.github/SECURITY.md lines 19-21, and add the same fallback contact in
.github/ISSUE_TEMPLATE/config.yml lines 15-17 when the advisory form is
unavailable; preserve the existing advisory guidance.
| # Needed to push the translation branch and open the PR. | ||
| permissions: | ||
| contents: write | ||
| pull-requests: write |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
file=".github/workflows/crowdin-download.yml"
if rg -nP '^\s*uses:\s+(crowdin/github-action|actions/checkout)@(?![0-9a-f]{40}\s*$)' "$file"; then
echo "Mutable action reference found" >&2
exit 1
fiRepository: meshtastic/web
Length of output: 262
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
file=".github/workflows/crowdin-download.yml"
echo "== File exists and relevant lines =="
wc -l "$file"
sed -n '1,70p' "$file" | cat -n
echo
echo "== Action refs in workflows =="
rg -n '^\s*uses:\s+' .github/workflows || trueRepository: meshtastic/web
Length of output: 5717
Pin the Crowdin and checkout actions before granting write permissions.
Lines 24 and 27 use actions/checkout@v7 and crowdin/github-action@v2, both mutable tags. This job has contents: write, pull-requests: write, and exposes GITHUB_TOKEN, CROWDIN_PROJECT_ID, and CROWDIN_PERSONAL_TOKEN. Use reviewed full-length commit SHAs for these workflow actions.
🤖 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/crowdin-download.yml around lines 8 - 11, Pin the actions
used by the Crowdin workflow, specifically actions/checkout and
crowdin/github-action, to reviewed full-length commit SHAs instead of the
mutable v7 and v2 tags. Keep the existing write permissions and action
configuration unchanged.
| IFS=',' read -ra TARGETS <<< "${INPUT_PACKAGES}" | ||
| TARGETS=("${TARGETS[@]/#/packages/}") | ||
| fi | ||
|
|
||
| # Validate every target before anything mutates the tree, so a typo | ||
| # fails fast instead of half-publishing. | ||
| for dir in "${TARGETS[@]}"; do | ||
| if [ ! -f "$dir/package.json" ]; then | ||
| echo "::error::No package.json at '$dir' - check the 'packages' input." >&2 | ||
| exit 1 | ||
| fi | ||
| done |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Restrict targets to an exact package allowlist.
INPUT_PACKAGES="../" becomes packages/../. Line 68 accepts that path because the repository root has package.json. The workflow can then modify or release a target outside the intended direct packages/* set.
Build an allowlist from direct package directories. Reject each requested target unless it exactly matches an allowlisted path.
Proposed fix
- if [ "${INPUT_PACKAGES}" = "all" ] || [ -z "${INPUT_PACKAGES}" ]; then
- mapfile -t TARGETS < <(ls -d packages/*)
+ mapfile -t ALLOWED_TARGETS < <(
+ find packages -mindepth 1 -maxdepth 1 -type d \
+ -exec test -f '{}/package.json' \; -print | sort
+ )
+
+ if [ "${INPUT_PACKAGES}" = "all" ] || [ -z "${INPUT_PACKAGES}" ]; then
+ TARGETS=("${ALLOWED_TARGETS[@]}")
else
IFS=',' read -ra TARGETS <<< "${INPUT_PACKAGES}"
TARGETS=("${TARGETS[@]/#/packages/}")
fi
@@
for dir in "${TARGETS[@]}"; do
- if [ ! -f "$dir/package.json" ]; then
- echo "::error::No package.json at '$dir' - check the 'packages' input." >&2
+ if ! printf '%s\n' "${ALLOWED_TARGETS[@]}" | grep -Fqx -- "$dir"; then
+ echo "::error::Invalid package target '$dir'." >&2
exit 1
fi
done📝 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.
| IFS=',' read -ra TARGETS <<< "${INPUT_PACKAGES}" | |
| TARGETS=("${TARGETS[@]/#/packages/}") | |
| fi | |
| # Validate every target before anything mutates the tree, so a typo | |
| # fails fast instead of half-publishing. | |
| for dir in "${TARGETS[@]}"; do | |
| if [ ! -f "$dir/package.json" ]; then | |
| echo "::error::No package.json at '$dir' - check the 'packages' input." >&2 | |
| exit 1 | |
| fi | |
| done | |
| mapfile -t ALLOWED_TARGETS < <( | |
| find packages -mindepth 1 -maxdepth 1 -type d \ | |
| -exec test -f '{}/package.json' \; -print | sort | |
| ) | |
| if [ "${INPUT_PACKAGES}" = "all" ] || [ -z "${INPUT_PACKAGES}" ]; then | |
| TARGETS=("${ALLOWED_TARGETS[@]}") | |
| else | |
| IFS=',' read -ra TARGETS <<< "${INPUT_PACKAGES}" | |
| TARGETS=("${TARGETS[@]/#/packages/}") | |
| fi | |
| # Validate every target before anything mutates the tree, so a typo | |
| # fails fast instead of half-publishing. | |
| for dir in "${TARGETS[@]}"; do | |
| if ! printf '%s\n' "${ALLOWED_TARGETS[@]}" | grep -Fqx -- "$dir"; then | |
| echo "::error::Invalid package target '$dir'." >&2 | |
| exit 1 | |
| fi | |
| done |
🤖 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/release-packages.yml around lines 61 - 72, Update the
target validation loop in the release workflow to build an allowlist from direct
package directories under packages and reject each requested TARGETS entry
unless it exactly matches one of those paths. Do not rely solely on checking
"$dir/package.json", so inputs such as "../" or packages/../ cannot pass
validation; preserve the existing fail-fast error behavior for non-allowlisted
targets.
| # Workflow inputs are untrusted text; never interpolate into the script body. | ||
| env: | ||
| INPUT_VERSION: ${{ inputs.version }} | ||
| run: | | ||
| set -euo pipefail | ||
| if [ -n "${{ inputs.version }}" ]; then | ||
| VERSION="${{ inputs.version }}" | ||
| if [ -n "${INPUT_VERSION}" ]; then | ||
| VERSION="${INPUT_VERSION}" | ||
| else | ||
| echo "No 'version' input. Provide inputs.version." >&2 | ||
| exit 1 | ||
| fi | ||
| # Reject anything that isn't a plain semver-ish tag before it reaches jq/git. | ||
| if ! printf '%s' "$VERSION" | grep -qE '^v?[0-9]+\.[0-9]+\.[0-9]+([-+.][0-9A-Za-z.-]+)?$'; then | ||
| echo "::error::Invalid version '$VERSION' (expected e.g. v1.2.3)." >&2 | ||
| exit 1 | ||
| fi |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
pattern='^v?[0-9]+\.[0-9]+\.[0-9]+([-+.][0-9A-Za-z.-]+)?$'
version=$'1.2.3\nINJECTED=1'
if printf '%s' "$version" | grep -qE "$pattern"; then
echo "Current validation accepts multiline input."
printf 'VERSION=%s\n' "$version"
fiRepository: meshtastic/web
Length of output: 221
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Repository files matching workflow name:"
fd -a 'release-protobufs\.yml$' .github/workflows || true
echo
echo "Workflow excerpt:"
if [ -f .github/workflows/release-protobufs.yml ]; then
nl -ba .github/workflows/release-protobufs.yml | sed -n '1,120p'
fi
echo
echo "Relevant input definitions/usages in workflows:"
rg -n "inputs\.version|version:|GITHUB_ENV|::error::|GITHUB_OUTPUT|github\.set-output|echo ::error|Invalid version" .github/workflows || true
echo
echo "Programmatic line-wise grep probe with same version values:"
python3 - <<'PY'
import re
pattern = re.compile(r'^v?[0-9]+\.[0-9]+\.[0-9]+([-+.][0-9A-Za-z.-]+)?$')
tests = [
"1.2.3",
"v1.2.3",
"1.2.3\nINJECTED=1",
"v1.2.3\nINJECTED=1\nNEXT=2",
"1.2.3\rINJECTED=1",
"1.2.3\n1.2.3",
"1.2.3#comment",
]
for value in tests:
lines = value.splitlines()
has_line_match = any(pattern.match(line) for line in lines)
complete_match = bool(pattern.match(value))
print(repr(value), "-> line_match=", has_line_match, ", complete_match=", complete_match)
PYRepository: meshtastic/web
Length of output: 312
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Workflow excerpt using awk:"
awk '{printf "%5d\t%s\n", NR, $0}' .github/workflows/release-protobufs.yml | sed -n '1,120p'
echo
echo "Relevant input definitions/usages in workflows:"
rg -n "inputs\.version|version:|GITHUB_ENV|::error::|GITHUB_OUTPUT|github\.set-output|echo ::error|Invalid version" .github/workflows || true
echo
echo "Programmatic line-wise grep probe with same version values:"
python3 - <<'PY'
import re
pattern = re.compile(r'^v?[0-9]+\.[0-9]+\.[0-9]+([-+.][0-9A-Za-z.-]+)?$')
tests = [
"1.2.3",
"v1.2.3",
"1.2.3\nINJECTED=1",
"v1.2.3\nINJECTED=1\nNEXT=2",
"1.2.3\rINJECTED=1",
"1.2.3\n1.2.3",
"1.2.3#comment",
]
for value in tests:
lines = value.splitlines()
has_line_match = any(pattern.match(line) for line in lines)
complete_match = bool(pattern.match(value))
print(repr(value), "-> line_match=", has_line_match, ", complete_match=", complete_match)
PYRepository: meshtastic/web
Length of output: 7866
Reject multiline versions before workflow output.
inputs.version is used directly in GITHUB_ENV and workflow ::error:: output. The current line-based grep accepts values like 1.2.3\nINJECTED=1, so GITHUB_ENV can receive an injected INJECTED=1 record. Validate the full string and use a fixed error message before writing to GITHUB_ENV or emitting workflow commands.
🤖 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/release-protobufs.yml around lines 44 - 59, Update the
version validation in the release workflow’s shell step to reject any newline or
other multiline input by requiring the entire INPUT_VERSION value to match the
semver pattern, not merely one line. Use a fixed invalid-version error message
without interpolating VERSION before writing it to GITHUB_ENV or emitting any
workflow command, while preserving valid version handling.
| concurrency: | ||
| group: release-web-${{ github.event.release.tag_name || inputs.tag_name || github.ref }} | ||
| cancel-in-progress: false |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== repository files matching workflow =="
git ls-files | rg '(^|/)\.github/workflows/' || true
echo
echo "== target workflow excerpt =="
if [ -f .github/workflows/release-web.yml ]; then
nl -ba .github/workflows/release-web.yml | sed -n '1,220p'
else
echo "missing .github/workflows/release-web.yml"
fi
echo
echo "== search for latest/major mutable tag usage in workflows =="
rg -n "latest|major|minor|tag_name|pulls.*latest|imagoi|image|tag|alias|concurrency" .github/workflows -S || true
echo
echo "== changed files/stat =="
git diff --stat || true
git diff -- .github/workflows/release-web.yml || trueRepository: meshtastic/web
Length of output: 938
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== target workflow excerpt =="
awk '{printf "%6d\t%s\n", NR, $0}' .github/workflows/release-web.yml | sed -n '1,240p'
echo
echo "== relevant workflow search =="
rg -n "latest|major|minor|tag_name|pulls|imagoi|image|tag|alias|concurrency|docker|push" .github/workflows/release-web.yml .github/workflows -S || trueRepository: meshtastic/web
Length of output: 28575
Serialize release image pushes that promote mutable tags.
release-web-${{ github.event.release.tag_name || inputs.tag_name || github.ref }} creates a separate concurrency group per release. Full releases also push latest, major, and major.minor tags, so two releases can share mutable image tags concurrently and an older build can overwrite a newer build. Use one shared publish group for mutable alias promotion, or update immutable releases without pushing shared tags unless an explicit queue coordinates these runs.
🤖 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/release-web.yml around lines 26 - 28, Update the
workflow’s concurrency group for the release image publishing job so all runs
that promote mutable tags such as latest, major, and major.minor use one shared
group instead of a release-specific expression. Keep cancel-in-progress disabled
so releases queue and publish in order.
| shell: bash | ||
| # Release tags/titles are user-supplied; keep them out of the script body. | ||
| env: | ||
| PUSHED_TAGS: ${{ steps.meta.outputs.tags }} | ||
| IS_PRERELEASE: ${{ github.event.release.prerelease }} | ||
| PULL_TAG: ${{ github.event.release.tag_name || inputs.tag_name || format('adhoc-{0}', steps.gen.outputs.shortsha) }} | ||
| SHORTSHA: ${{ steps.gen.outputs.shortsha }} | ||
| run: | | ||
| set -euo pipefail | ||
| echo "🖼️ Pushed tags:" | ||
| echo "${{ steps.meta.outputs.tags }}" | ||
| echo "${PUSHED_TAGS}" | ||
| echo | ||
| echo "Pull examples:" | ||
| if [[ "${{ github.event_name }}" == "release" && "${{ github.event.release.prerelease }}" == "false" ]]; then | ||
| echo " docker pull ${{ env.REGISTRY_IMAGE }}:latest" | ||
| if [[ "${GITHUB_EVENT_NAME}" == "release" && "${IS_PRERELEASE}" == "false" ]]; then | ||
| echo " docker pull ${REGISTRY_IMAGE}:latest" | ||
| fi | ||
| # Always available: | ||
| echo " docker pull ${{ env.REGISTRY_IMAGE }}:${{ github.event.release.tag_name || inputs.tag_name || format('adhoc-{0}', steps.gen.outputs.shortsha) }}" | ||
| echo " docker pull ${{ env.REGISTRY_IMAGE }}:sha-${{ steps.gen.outputs.shortsha }}" | ||
| echo " docker pull ${REGISTRY_IMAGE}:${PULL_TAG}" | ||
| echo " docker pull ${REGISTRY_IMAGE}:sha-${SHORTSHA}" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== workflow file excerpt =="
if [ -f .github/workflows/release-web.yml ]; then
nl -ba .github/workflows/release-web.yml | sed -n '1,230p'
else
echo "missing .github/workflows/release-web.yml"
fd -a 'release-web\.ya?ml$' .
fi
echo
echo "== docker metadata action usage and related vars =="
rg -n "docker/metadata-action|steps\.meta\.outputs|outputs:|tags:" .github/workflows .github -g '*.yml' -g '*.yaml' || true
echo
echo "== repository references to PULL_TAG / RELEASE_TAG / SHORTSHA =="
rg -n "PULL_TAG|SHORTSHA|REGISTRY_IMAGE|PUSHED_TAGS|steps\.meta\.outputs\.tags|docker/metadata-action" . -g '!node_modules' -g '!dist' -g '!build' || trueRepository: meshtastic/web
Length of output: 220
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== workflow file excerpt =="
if [ -f .github/workflows/release-web.yml ]; then
awk '{printf "%6d\t%s\n", NR, $0}' .github/workflows/release-web.yml | sed -n '1,230p'
else
echo "missing .github/workflows/release-web.yml"
find . -name 'release-web.y*ml' -print
fi
echo
echo "== docker metadata action usage and related vars =="
grep -RIn "docker/metadata-action\|steps\.meta\.outputs\|outputs:\|tags:" .github/workflows .github 2>/dev/null | grep -E '\.(yml|yaml):' || true
echo
echo "== repository references to PULL_TAG / RELEASE_TAG / SHORTSHA =="
grep -RIn "PULL_TAG|SHORTSHA|REGISTRY_IMAGE|PUSHED_TAGS|steps\.meta\.outputs\.tags|docker/metadata-action" . 2>/dev/null | grep -v node_modules | grep -v dist | grep -v build || trueRepository: meshtastic/web
Length of output: 11393
🌐 Web query:
docker/metadata-action outputs.tags normalizes image name invalid characters docker build-push-action build
💡 Result:
The docker/metadata-action automatically normalizes image names and tags to ensure compliance with Docker's reference specifications [1][2][3]. For image names, the action enforces lowercase and ensures components follow naming rules (such as not starting or ending with separators like dots or dashes) [1][2]. For tags, the action performs several normalization steps: - Lowercase transformation: The action ensures the image name is lowercased [1][3]. - Invalid character replacement: Sequences of invalid characters within tags are automatically replaced with a hyphen (-) [1][4]. - Leading character stripping: The action strips leading periods (.) and hyphens (-) from tags to comply with the rule that tags cannot start with these characters [5]. When using the outputs from docker/metadata-action in the docker/build-push-action, you can pass the generated tags directly to the tags input [6]. Because the metadata action produces these sanitized, ready-to-use strings, it is the recommended way to prevent build errors related to invalid reference formats (e.g., repository names containing uppercase letters or invalid tag characters) [6]. If you encounter issues with using the output in shell commands (such as newline characters interfering with execution), you can adjust the separator using the sep-tags input or utilize the version output instead of the full tags list [7].
Citations:
- 1: https://github.com/docker/metadata-action?tab=readme-ov-file
- 2: https://github.com/docker/metadata-action/blob/v5/README.md
- 3: https://github.com/docker/metadata-action/
- 4: https://github.com/marketplace/actions/docker-metadata-action?version=v5.4.0
- 5: fix: strip leading dots and hyphens from raw tags docker/metadata-action#646
- 6: https://github.com/docker/build-push-action/blob/v6.12.0/TROUBLESHOOTING.md
- 7: How to use Docker tag in a following step ? docker/metadata-action#140
Print the generated image references.
docker/metadata-action normalizes image names and tag characters, so the raw REGISTRY_IMAGE and raw release/input tag may not match the refs pushed through steps.meta.outputs.tags. If a release tag like release/1.2.3 is provided, the printed docker pull ghcr.io/...:release/1.2.3 may not exist. Print each value from PUSHED_TAGS as the pull example.
🤖 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/release-web.yml around lines 150 - 168, Update the release
output block to print each generated image reference from PUSHED_TAGS as a
docker pull example, instead of constructing references from REGISTRY_IMAGE,
PULL_TAG, and SHORTSHA. Preserve the existing latest example condition for
non-prerelease releases if applicable, but ensure all generated tags from
steps.meta.outputs.tags are used so normalized image names and tags match the
pushed images.
| # A static group so overlapping releases serialize instead of racing to | ||
| # force-push `stable`. Never cancel: a half-run leaves `stable` inconsistent. | ||
| concurrency: | ||
| group: update-stable-${{ github.run_id }} | ||
| cancel-in-progress: true | ||
| group: update-stable | ||
| cancel-in-progress: false |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== file outline/size =="
wc -l .github/workflows/update-stable-from-master.yml
ast-grep outline .github/workflows/update-stable-from-master.yml || true
echo "== workflow contents =="
cat -n .github/workflows/update-stable-from-master.yml
echo "== git diff stat/name =="
git diff --stat HEAD~1..HEAD 2>/dev/null || git diff --stat
git diff -- .github/workflows/update-stable-from-master.yml 2>/dev/null || trueRepository: meshtastic/web
Length of output: 6246
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import io
# Read-only parser-ish checker: validate whether the workflow is ordered by release order semantics.
# This mirrors the documented facts: release workflows can queue; concurrency serialization does not enforce
# release order; a later serialized run for an older release can overwrite stable with its source.
payload = Path(".github/workflows/update-stable-from-master.yml").read_text()
checks = {
"has_release_trigger": "on:\n release:" in payload,
"static_concurrency": "group: update-stable" in payload and "cancel-in-progress: false" in payload,
"resolves_source_from_event": "${{ github.event.release.tag_name }}" in payload and "${{ github.event.release.target_commitish }}" in payload,
"no_post_lock_staleness_check": "steps.meta.outputs.sha" in payload and "git checkout -B stable \"${SOURCE_SHA}\"" in payload and "current-stable" not in payload and "latest-release" not in payload.lower() and "latest release" not in payload.lower(),
"force_with_league_after_local_checkout": payload.index("Create/reset local stable to SHA") > payload.index("Determine source SHA") and payload.index("Push stable (force-with-lease)") > payload.index("Create/reset local stable to SHA"),
}
print("\n".join(f"{k}: {v}" for k, v in checks.items()))
# Deterministic interleaving model:
# Run A queued first, run B queued later; concurrency lock allows only one at a time; A runs and finishes after B started.
models = [
{"order": ["B.acquire", "A.activate", "A.resolve", "A.checkout", "A.push", "B.resolve", "B.checkout", "B.push"],
"expected_result": "stable points to A release source after B checkout because B checkout does not recheck latest release/source age"},
{"order": ["A.acquire", "A.activate", "A.resolve", "A.checkout", "A.push", "B.acquire", "B.resolve", "B.checkout", "B.push"],
"expected_result": "stable points to B release source after ordered execution"},
]
for m in models:
stable = "initial"
current_latest_release_index = 1 # A
latest_release_source_index = 1 # A
log = []
for event in m["order"]:
actor = event.split(".")[0]
action = event.split(".", 1)[1]
step_prefix = 10 if actor == "A" else 30
old_remote = stable
if action == "resolve":
actor_source = current_latest_release_index if actor == "A" else latest_release_release_source_index if actor == "B" else actor
if action == "checkout":
stable = "release_source:" + actor + "_index:" + ("1" if current_latest_release_index != 1 else "30")
if action == "push":
latest_release_release_source_index = 30
log.append(f"{event}: stable={stable} from old={old_remote}")
print({"scenario": "stale run later", "result": "stable can point to earlier release source"}), 200
PY
echo "== GitHub Actions concurrency docs evidence request not needed as deterministic queue scenario matches stated concern =="Repository: meshtastic/web
Length of output: 614
🌐 Web query:
GitHub Actions concurrency run-level concurrency workflow file group cancel-in-progress documentation
💡 Result:
In GitHub Actions, the concurrency key is used to manage and limit the execution of workflows or jobs by placing them into a defined concurrency group [1][2][3]. This ensures that only a single job or workflow with a specific group identifier runs at any given time [1][3]. Configuration options for concurrency include: Group: Defines the identifier for the concurrency group. It can be a hard-coded string or a dynamic expression using context variables such as github, inputs, and vars [1][3]. Concurrency groups are unique across a repository; to restrict cancellation to only the current workflow, you should include the workflow name in the group definition (e.g., group: ${{ github.workflow }}-${{ github.ref }}) [1][4]. Cancel-in-progress: When set to true, this option automatically cancels any currently running job or workflow within the same concurrency group when a new one is queued [1][4]. This can also be defined as a conditional expression [1][4]. Queueing: By default, if a job or workflow is running, new ones are marked as pending, and any existing pending jobs in the same group are canceled [1][4]. To allow multiple runs to queue instead of being canceled, you can set queue: max, which permits up to 100 jobs or workflow runs to wait in line [3]. Concurrency can be applied at either the workflow level (the top level of the YAML file) or the job level (within a specific job's configuration) [3][5]. If a job or workflow with the same concurrency group is already in progress, the new instance will enter a pending state until the previous one completes, unless cancel-in-progress is configured to stop it [1][3][5].
Citations:
- 1: https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency
- 2: https://docs.github.com/en/actions/concepts/workflows-and-actions/concurrency
- 3: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax?use_case=
- 4: https://docs.github.com/en/enterprise-server@3.19/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency
- 5: https://github.blog/changelog/2021-04-19-github-actions-limit-workflow-run-or-job-concurrency/
Prevent stale release runs from resetting stable.
The static concurrency group only serializes runs; it does not preserve the order of queued releases. If an older release run acquires the lock after a newer release run has updated stable, this workflow checks out the older release tag and push force-with-lease succeeds because the remote has not changed since the stale run resolved it. Add a post-lock staleness check: derive the current latest release or source commit, then cancel/skip if the run’s release source is older than the current stable target.
🤖 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/update-stable-from-master.yml around lines 10 - 14, Update
the workflow’s concurrency-protected release flow to perform a post-lock
staleness check before checking out or force-pushing the release tag. Derive the
current latest release/source commit and compare it with the run’s release
source and current stable target; skip or cancel the run when its source is
older, while allowing the newest release to proceed.
| - name: Run zizmor | ||
| run: uvx zizmor --persona=regular --format=sarif . > zizmor.sarif | ||
| continue-on-error: true | ||
|
|
||
| # Skip the upload if zizmor crashed outright and left an empty file, | ||
| # rather than failing the job on a malformed SARIF. | ||
| - name: Upload SARIF | ||
| if: ${{ hashFiles('zizmor.sarif') != '' }} | ||
| uses: github/codeql-action/upload-sarif@v4 | ||
| with: | ||
| sarif_file: zizmor.sarif | ||
| category: zizmor |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
hashFiles guard does not detect a crashed/empty zizmor run.
The comment states the intent is to "skip the upload if zizmor crashed outright and left an empty file." Shell redirection (> zizmor.sarif) creates the file before uvx zizmor runs, so zizmor.sarif exists — and hashFiles('zizmor.sarif') returns a non-empty hash — even when the command crashes and leaves the file empty or malformed. github/codeql-action/upload-sarif@v4 has no continue-on-error, so a malformed SARIF can fail the job instead of being skipped as intended.
🐛 Proposed fix: check for non-empty file content, not mere existence
+ - name: Check SARIF output
+ id: check-sarif
+ if: always()
+ run: |
+ if [ -s zizmor.sarif ]; then
+ echo "has-output=true" >> "$GITHUB_OUTPUT"
+ else
+ echo "has-output=false" >> "$GITHUB_OUTPUT"
+ fi
+
- name: Upload SARIF
- if: ${{ hashFiles('zizmor.sarif') != '' }}
+ if: steps.check-sarif.outputs.has-output == 'true'
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: zizmor.sarif
category: zizmor🧰 Tools
🪛 GitHub Check: zizmor
[failure] 64-64:
unpinned action reference: action is not pinned to a hash (required by blanket policy)
🤖 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/workflow-lint.yml around lines 56 - 67, Update the “Upload
SARIF” step’s if condition to verify that zizmor.sarif contains content rather
than only checking its existence via hashFiles. Preserve the existing behavior
of skipping upload when the redirection-created file is empty after a failed
“Run zizmor” step.
Description
An audit of the GitHub Actions setup and repository metadata. Several workflows
were silently not doing what they appeared to do; this fixes those, tightens
workflow permissions, and adds the missing community health files.
No application code is touched — this is entirely
.github/plus threeroot-level docs.
Changes Made
Things that were silently broken
crowdin-upload-sources.ymlwas/apps/web/public/i18n/locales/en/*.json.Path filters are relative to the repo root and must not start with
/, so theglob never matched and English source strings were never auto-pushed to
Crowdin on merge.
pr.ymlranpnpm run lintand thenpnpm run check— butcheckislint && format, so the linter ran twiceand the only step that checked formatting carried
continue-on-error: true.apps/webhas atypecheckscript, but no workflowcalled it, and
buildis barevite build, which transpiles withoutchecking types.
release-web.ymlandnightly.ymlsetorg.opencontainers.image.sourcetoowner/repo. Thatlabel must be a URL for GHCR to attach the package to the repository.
update-stable-from-master.ymlusedgroup: update-stable-${{ github.run_id }}.run_idis unique per run, soevery run got its own group and overlapping releases never serialized before
force-pushing
stable.pr.ymlthat grepped the lockfile forhardcoded versions
2.7.18/2.7.20.Security
release-protobufs.yml:permissions: write-all→contents: read(
publish-jsralready scoped itself correctly).ci.yml: droppedcontents: write+packages: writefrom a job that onlybuilds.
permissionsblocks toe2e.ymland the three Crowdinworkflows, which previously inherited the repository default.
run:bodies intoenv:(release tags,
workflow_dispatchinputs), and added validation for theprotobufs
versionand packagesbumpinputs.bufbuild/buf-setup-action@main→@v1anddenoland/setup-deno@mainwithdeno-version: rc→@v2/v2.x. Apublishing workflow shouldn't track a moving branch or an RC toolchain.
workflow-lint.ymlrunning actionlint (YAML/expression/shellerrors) and zizmor (workflow security audit, SARIF uploaded to code
scanning) on changes under
.github/.npm publishnow passes--provenance; the job already hadid-token: writefor JSR.Reliability and cost
timeout-minuteson every job — previously onlye2ehad one, so a hung jobcould burn the full 6-hour default.
ci,e2e,nightly,release-web,release-packages,release-protobufs,stale, and the Crowdin workflows.packages/**. It gained a draft filter,cancel-in-progress, a Playwrightbrowser cache keyed on the resolved Playwright version (with
install-depson cache hit), and pip caching.
redhat-actions/buildahto the same buildx pathrelease-webuses, so the nightly actually exercises the release image buildinstead of a parallel toolchain. Added a gate that skips scheduled runs when
nothing landed in the last 24h (manual runs always build).
uses a per-run
release/version-bump-<run_id>branch — the fixedversion_bumpbranch made the second run fail ongit checkout -b.only-issue-labelswas"Bug"whileISSUE_TEMPLATE/bug.ymlappliesbug; aligned, and addedexempt labels.
github-actionsand dev dependencies into single PRsto cut churn.
pnpm run test→pnpm exec vitest runin CI, so it doesn't depend onimplicit
CI=truewatch-mode detection.Repository metadata
.github/had no CODEOWNERS, SECURITY.md, CODE_OF_CONDUCT.md, orISSUE_TEMPLATE/config.yml. Added:SECURITY.md— private disclosure via GitHub Security Advisories, with scopenotes for a client that handles channel PSKs and node private keys.
ISSUE_TEMPLATE/config.yml—blank_issues_enabled: falseplus contactlinks to Discord, the forum, docs, and the firmware repo, to divert support
questions before they become issues.
CODE_OF_CONDUCT.md— points at the existing org-wide Meshtastic CoC ratherthan duplicating it.
CONTRIBUTING.md— a pointer toapps/web/CONTRIBUTING.md, since GitHubonly surfaces the file from the root,
.github/, ordocs/.CODEOWNERS— all rules are commented out, see below.Needs a maintainer decision
CODEOWNERS handles. The file is in place with proposed path rules
(workflows, infra, transports, protobufs, i18n sources) but every rule is
commented out, because I couldn't determine the right handles or team slugs. A
rule naming a nonexistent user or team is silently ignored by GitHub, which is
worse than no file. Someone with org context should fill in the owners and
uncomment.
Formatting and typecheck are non-blocking. Both steps are added with
continue-on-error: trueand aTODO(ci)comment, because both currently failon
mainfor pre-existing reasons:oxfmtreports 611 of 624 files as unformatted — drift that looks like itcame from the
oxfmt0.60 bump. Needs a standalonepnpm run format:fixsweep commit, which would conflict with every open branch, so it didn't belong
in this PR.
tsc --noEmitreports ~25 errors, in two clusters:better-resultno longer exportsResultType(widespread acrosssdk,sdk-react, andtransport-web-serial)packages/protobufsbuild and@jsr/meshtastic__protobufs@2.7.20— sostructurally identical enums are treated as unrelated types. Notably
HardwareModelhas diverged (HELTEC_MESH_NODE_T096is missing from theJSR copy). This is the same conflict the removed debug step was grepping
for.
Both should be flipped to blocking once those are addressed. Happy to split
either into its own PR.
Testing Done
Validated with actionlint v1.7.12 — 0 errors across all 13 workflows
(previously and after). YAML syntax of every workflow,
dependabot.yml, andboth issue templates verified separately.
No runtime verification of the workflows themselves — they need a merge to
exercise. The changes most worth a careful look on first run are the nightly
buildx switch and the nightly skip-if-no-commits gate.
Checklist
🤖 Generated with Claude Code
Summary by CodeRabbit
Documentation
Chores