-
Notifications
You must be signed in to change notification settings - Fork 369
ci: keep the rcli Homebrew tap current automatically #737
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| name: rcli Homebrew tap | ||
|
|
||
| # Keeps RunanywhereAI/homebrew-tap's Formula/rcli.rb pointing at the newest | ||
| # release. Nothing did this before, so the tap sat at 0.20.10 from 14 July while | ||
| # releases went on to 0.20.24: `brew install runanywhereai/tap/rcli` handed | ||
| # people a month-old CLI, which is what "brew install rcli doesn't work" was. | ||
| # | ||
| # This is a SEPARATE workflow on purpose. release.yml creates the GitHub Release | ||
| # as a DRAFT, and update-tap.sh reads the published .sha256 sidecars over HTTPS, | ||
| # so it cannot run inside that job — the assets are not downloadable yet. The | ||
| # `release: published` event fires when a human publishes the draft, which is | ||
| # exactly the moment the tarballs become fetchable. Keeping it out of | ||
| # release.yml also means a tap failure can never fail a release. | ||
| on: | ||
| release: | ||
| types: [published] | ||
| workflow_dispatch: | ||
| inputs: | ||
| version: | ||
| description: "Version to point the tap at, without the leading v (e.g. 0.20.24)" | ||
| required: true | ||
| type: string | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| # update-tap.sh clones, commits and pushes with no rebase and no retry, so two | ||
| # runs racing (a release publish alongside a manual dispatch) can have one push | ||
| # rejected and leave the tap pointing at the older release. Queue them instead. | ||
| # cancel-in-progress stays false: a cancelled run here means a tap that never | ||
| # got updated, which is the exact failure this workflow exists to prevent. | ||
| concurrency: | ||
| group: rcli-homebrew-tap | ||
| cancel-in-progress: false | ||
|
|
||
| jobs: | ||
| update-tap: | ||
| name: Point the tap at the published release | ||
| runs-on: ubuntu-latest | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| # Job level, not step level: a step's `if` cannot read the `secrets` context | ||
| # at all, and cannot read env declared on that same step either. Hoisting it | ||
| # here is what makes the token-presence check below actually evaluate. | ||
| env: | ||
| TAP_TOKEN: ${{ secrets.RCLI_TAP_TOKEN }} | ||
|
Comment on lines
+43
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
file=".github/workflows/rcli-tap.yml"
printf '%s\n' "== workflow =="
cat -n "$file"
printf '%s\n' "== related scripts and references =="
rg -n -C 4 'update-tap|TAP_TOKEN|HAS_TAP_TOKEN|concurrency|queue|max|cancel-in-progress' .github rcli 2>/dev/null || true
printf '%s\n' "== workflow history summary =="
git diff --statRepository: RunanywhereAI/runanywhere-sdks Length of output: 50389 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' "== update-tap.sh =="
cat -n rcli/scripts/update-tap.sh
printf '%s\n' "== focused workflow excerpts =="
sed -n '24,92p' .github/workflows/rcli-tap.yml
printf '%s\n' "== repository references to the token =="
rg -n -C 3 'RCLI_TAP_TOKEN|TAP_TOKEN' --glob '!rcli/third_party/**' --glob '!*.lock' .Repository: RunanywhereAI/runanywhere-sdks Length of output: 6200 🌐 Web query:
💡 Result: In GitHub Actions, environment variables defined at both the job level and the step level are available to JavaScript actions via the standard Node.js process.env object [1]. When you define an environment variable in a workflow—whether in the job's env block or a specific step's env block—the runner automatically exposes these as environment variables to the underlying process execution [2][1]. Key implementation details include: 1. Job-level vs. Step-level Variables: Variables defined at the job level (jobs.<job_id>.env) are accessible to all steps within that job [3]. Variables defined at the step level (jobs.<job_id>.steps[*].env) are specific to that individual step [2][3]. Both will be populated in process.env for a JavaScript action executed in that context [1]. 2. Accessing Secrets in 'if' Conditions: The secrets context cannot be directly accessed within if conditional expressions [4][5]. Attempting to reference secrets.SECRET_NAME directly in an if condition will result in a parsing error (e.g., "Unrecognized named-value") [4][6]. 3. Recommended Workaround: To conditionally execute steps based on a secret's presence or value, you must first map the secret to an environment variable [4][5]. Example pattern: jobs: example_job: runs-on: ubuntu-latest env: # Map secret to an env variable at the job level HAS_SECRET: ${{ secrets.MY_SECRET!= '' }} steps: - name: Conditional Step if: env.HAS_SECRET == 'true' uses:./my-javascript-action env: # Pass the actual secret if needed by the action MY_SECRET: ${{ secrets.MY_SECRET }} By mapping the secret to an environment variable first, you allow the GitHub Actions runner to resolve the value before the if condition is evaluated, as env context variables are accessible in most workflow keys [7][6]. Citations:
🌐 Web query:
💡 Result: It is correct that GitHub Actions secrets cannot be directly referenced in if conditionals [1][2]. This restriction applies because the if conditional is evaluated by GitHub Actions before the job is sent to a runner [3][4]. Because secrets are sensitive, they are not available to the evaluation engine in this context to prevent potential exposure [1][2]. If a secret is referenced directly in an if conditional, it will be evaluated as an empty string [1][2]. To work around this limitation, you can define the secret as an environment variable at the job level [1][2]. While you still cannot use that environment variable directly in a job-level if conditional (as the environment variable context is not typically available for job-level conditions), you can use it to conditionally execute specific steps within the job [1][2]. For example: jobs: my-job: runs-on: ubuntu-latest env: MY_SECRET_VAL: ${{ secrets.MY_SECRET }} steps: - name: Run only if secret is set if: ${{ env.MY_SECRET_VAL!= '' }} run: echo "Secret is present" Citations:
🏁 Script executed: #!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re
path = Path(".github/workflows/rcli-tap.yml")
lines = path.read_text().splitlines()
job_env = {}
in_job = False
in_job_env = False
steps = []
current = None
for i, line in enumerate(lines, 1):
if line == "jobs:":
in_job = True
if in_job and line == " env:":
in_job_env = True
continue
if in_job_env:
m = re.match(r" ([A-Z][A-Z0-9_]*)\s*:", line)
if m:
job_env[m.group(1)] = i
elif line and not line.startswith(" "):
in_job_env = False
m = re.match(r" - name: (.+)", line)
if m:
current = {"name": m.group(1), "line": i, "env": [], "refs": []}
steps.append(current)
if current:
m = re.match(r" ([A-Z][A-Z0-9_]*)\s*:", line)
if m:
current["env"].append(m.group(1))
if "TAP_TOKEN" in line:
current["refs"].append((i, line.strip()))
print("job-level env:", job_env)
for step in steps:
print(f'{step["name"]!r}: step env={step["env"]}, TAP_TOKEN refs={step["refs"]}')
print("claim checks:")
print(" TAP_TOKEN is job-scoped:", "TAP_TOKEN" in job_env)
print(" update step declares TAP_TOKEN:", any(
s["name"] == "Update the tap" and "TAP_TOKEN" in s["env"] for s in steps
))
print(" verification condition uses TAP_TOKEN:", any(
"if:" in line and "TAP_TOKEN" in line
for line in lines
))
PYRepository: RunanywhereAI/runanywhere-sdks Length of output: 817 Scope Line 44 makes the secret available to 🤖 Prompt for AI Agents |
||
| steps: | ||
| - uses: actions/checkout@v7 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - name: Resolve version | ||
| id: version | ||
| env: | ||
| INPUT_VERSION: ${{ inputs.version }} | ||
| RELEASE_TAG: ${{ github.event.release.tag_name }} | ||
| run: | | ||
| version="${INPUT_VERSION:-${RELEASE_TAG#v}}" | ||
| if [ -z "${version}" ]; then | ||
| echo "::error::no version to publish" | ||
| exit 1 | ||
| fi | ||
| echo "version=${version}" >> "$GITHUB_OUTPUT" | ||
|
|
||
| # The tap is a different repository, so GITHUB_TOKEN cannot write to it. | ||
| # Mirrors how the rcli signing step degrades: when the credential is | ||
| # absent, say so and stop rather than failing the run, so an unconfigured | ||
| # fork or a release cut before the secret exists stays green. | ||
| - name: Update the tap | ||
| env: | ||
| VERSION: ${{ steps.version.outputs.version }} | ||
| run: | | ||
| if [ -z "${TAP_TOKEN}" ]; then | ||
| echo "::warning::RCLI_TAP_TOKEN not configured — skipping the Homebrew tap update." | ||
| echo "Grant a token with push access to RunanywhereAI/homebrew-tap, then re-run" | ||
| echo "this workflow, or update by hand: ./rcli/scripts/update-tap.sh ${VERSION}" | ||
| exit 0 | ||
| fi | ||
| export RCLI_TAP_REPO="https://x-access-token:${TAP_TOKEN}@github.com/RunanywhereAI/homebrew-tap.git" | ||
| ./rcli/scripts/update-tap.sh "${VERSION}" | ||
|
|
||
| # Proves the formula the tap now serves actually installs and runs, on the | ||
| # same OS a user would install from. `brew test` runs the formula's own | ||
| # test block, which checks `rcli version` and `rcli backends`. | ||
| # No continue-on-error: this step exists to catch a formula that does not | ||
| # install, and a check that cannot fail would report a broken tap as | ||
| # healthy — which is how the tap sat broken for a month in the first place. | ||
| - name: Verify brew install from the tap | ||
| if: ${{ env.TAP_TOKEN != '' }} | ||
| run: | | ||
| brew tap runanywhereai/tap | ||
| brew install runanywhereai/tap/rcli | ||
| brew test runanywhereai/tap/rcli | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: RunanywhereAI/runanywhere-sdks
Length of output: 6746
🌐 Web query:
GitHub Actions concurrency one pending run replaces pending queue queue max concurrency YAML support💡 Result:
In GitHub Actions, you can control how pending workflow runs are handled within a concurrency group using the queue property in your YAML configuration. By default, GitHub Actions uses queue: single [1][2]. In this mode, only one job or workflow run can be pending in a concurrency group at any time [1][2]. When a new run is queued for that group, any existing pending run is automatically canceled and replaced by the new one [1][2]. If you wish to allow multiple runs to wait in line rather than being canceled, you can set the queue property to max [1][2]. This configuration allows up to 100 jobs or workflow runs to be pending in the concurrency group simultaneously [1][3][2]. When the queue reaches this limit of 100, any subsequent incoming runs will be canceled [1][3][2]. It is important to note that the queue: max property cannot be used in combination with cancel-in-progress: true [2][4]. Attempting to use both in the same concurrency block will result in a workflow validation error because they represent conflicting strategies for managing in-progress and pending work [2][4]. Example YAML structure: concurrency: group: my-group-name queue: max cancel-in-progress: false # Required when using queue: max [2][4]
Citations:
🌐 Web query:
GitHub Actions concurrency queue max syntax official documentation💡 Result:
In GitHub Actions, the
concurrencyconfiguration supports aqueueproperty that allows you to manage how multiple workflow runs or jobs behave when they share the same concurrency group [1][2]. When you setqueue: max, you enable the concurrency group to queue up to 100 pending jobs or workflow runs [3][1]. Once the queue reaches this limit, any additional jobs or workflow runs that attempt to enter the group are automatically canceled [1][2]. Key details regarding thequeueproperty: - Default Behavior: By default, thequeueproperty uses the valuesingle. In this mode, only one job or workflow run can be pending in the concurrency group at a time; new incoming runs will cancel the existing pending run [1][2]. - Configuration: You can configure this within your workflow YAML file under theconcurrencyblock [4]. - Conflict Constraint: Thequeue: maxsetting cannot be used in combination withcancel-in-progress: true[1][2]. Attempting to use both simultaneously will result in a workflow validation error [1][2]. - Limits: This feature allows for a maximum of 100 queued jobs or workflow runs per concurrency group [3][1]. For additional context on how this functions within your workflow syntax: concurrency: group: my-group-name queue: max # cancel-in-progress: true <-- Cannot be used with queue: max This functionality was introduced to help manage sequential execution for deployments and shared resource access more effectively [4].Citations:
🏁 Script executed:
Repository: RunanywhereAI/runanywhere-sdks
Length of output: 3868
Preserve pending tap-update runs.
cancel-in-progress: falseretains the running job but replaces the existing pending run when a newer run enters the group. A manualinputs.versionupdate can therefore be dropped beforeupdate-tap.shruns.Add
queue: maxwhen the target GitHub or GHES environment supports it. Otherwise, use a durable queue.🤖 Prompt for AI Agents