Repository navigation
ci(relay): add Deploy Relay workflow (Fly) - #5575
Conversation
The relay ships to Fly, not Vercel, and was never wired into CI — deploy-production.yml deploys DB/API/Web/Marketing/Admin only, so relay changes silently never deployed (a merged relay fix stayed dark until someone manually ran the Fly deploy). Adds a dedicated workflow that runs the canonical apps/relay/scripts/deploy.sh (fly scale + rolling deploy + smoke test) on main pushes touching apps/relay or packages/shared, plus workflow_dispatch to force/first-run a deploy. Path-filtered so a heavy 7-region rolling deploy doesn't fire on every unrelated push; concurrency-guarded so deploys never overlap. Requires a FLY_API_TOKEN secret in the production environment.
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
Ready to review this PR? Stage has broken it down into 1 individual chapter for you:
Chapters generated by Stage for commit 1598d73 on Jul 10, 2026 7:35pm UTC. |
📝 WalkthroughWalkthroughAdds a GitHub Actions workflow that deploys ChangesRelay deployment automation
Estimated code review effort: 2 (Simple) | ~10 minutes Sequence Diagram(s)sequenceDiagram
participant GitHubActions
participant flyctl
participant RelayDeployScript
GitHubActions->>GitHubActions: Start on filtered push or manual dispatch
GitHubActions->>flyctl: Install flyctl
GitHubActions->>RelayDeployScript: Run deploy.sh with FLY_API_TOKEN
RelayDeployScript->>flyctl: Deploy apps/relay to Fly.io
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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 |
Greptile SummaryThis PR adds a new dedicated GitHub Actions workflow (
Confidence Score: 3/5The workflow is structurally sound and fills a real deployment gap, but the unpinned remote script installation runs inside the production environment alongside live secrets. The flyctl installer is fetched at runtime from .github/workflows/deploy-relay.yml — specifically the flyctl installation step
|
| Filename | Overview |
|---|---|
| .github/workflows/deploy-relay.yml | New dedicated Fly deploy workflow; correct structure and concurrency guard, but installs flyctl via an unpinned `curl |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant GH as GitHub (push to main)
participant WF as deploy-relay.yml
participant Runner as ubuntu-latest Runner
participant FlyIO as fly.io install.sh
participant Fly as Fly.io API
participant Smoke as Smoke Test
GH->>WF: "push (apps/relay/** changed)"
WF->>Runner: acquire concurrency lock (deploy-relay)
Runner->>Runner: "checkout@SHA"
Runner->>FlyIO: "curl -L https://fly.io/install.sh | sh"
FlyIO-->>Runner: flyctl binary (unpinned version)
Runner->>Fly: fly scale count (7 regions)
Runner->>Fly: fly deploy --strategy rolling
Fly-->>Runner: deploy complete
Runner->>Runner: fly status
Runner->>Smoke: smoke-test.sh superset-relay.fly.dev [regions]
Smoke-->>Runner: pass/fail
Runner->>WF: release concurrency lock
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant GH as GitHub (push to main)
participant WF as deploy-relay.yml
participant Runner as ubuntu-latest Runner
participant FlyIO as fly.io install.sh
participant Fly as Fly.io API
participant Smoke as Smoke Test
GH->>WF: "push (apps/relay/** changed)"
WF->>Runner: acquire concurrency lock (deploy-relay)
Runner->>Runner: "checkout@SHA"
Runner->>FlyIO: "curl -L https://fly.io/install.sh | sh"
FlyIO-->>Runner: flyctl binary (unpinned version)
Runner->>Fly: fly scale count (7 regions)
Runner->>Fly: fly deploy --strategy rolling
Fly-->>Runner: deploy complete
Runner->>Runner: fly status
Runner->>Smoke: smoke-test.sh superset-relay.fly.dev [regions]
Smoke-->>Runner: pass/fail
Runner->>WF: release concurrency lock
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
.github/workflows/deploy-relay.yml:31-33
**Unpinned remote script download breaks action-pinning discipline**
Every other action in this repo is pinned to a full commit SHA (e.g., `actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5`), but the flyctl installer is fetched at runtime from `https://fly.io/install.sh` with no version or hash constraint. If fly.io's install script is ever compromised or unexpectedly changed, arbitrary code runs inside the `production` environment alongside `FLY_API_TOKEN`. The official `superfly/flyctl-actions/setup-flyctl` GitHub Action can be pinned to a specific commit SHA to close this gap — the PR description already notes this as a follow-up, but given the repo's strict pinning discipline and production secret exposure here, it's worth addressing before merge.
Reviews (1): Last reviewed commit: "ci(relay): add Deploy Relay workflow (Fl..." | Re-trigger Greptile
|
|
||
| - name: Install flyctl | ||
| run: | |
There was a problem hiding this comment.
Unpinned remote script download breaks action-pinning discipline
Every other action in this repo is pinned to a full commit SHA (e.g., actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5), but the flyctl installer is fetched at runtime from https://fly.io/install.sh with no version or hash constraint. If fly.io's install script is ever compromised or unexpectedly changed, arbitrary code runs inside the production environment alongside FLY_API_TOKEN. The official superfly/flyctl-actions/setup-flyctl GitHub Action can be pinned to a specific commit SHA to close this gap — the PR description already notes this as a follow-up, but given the repo's strict pinning discipline and production secret exposure here, it's worth addressing before merge.
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/deploy-relay.yml
Line: 31-33
Comment:
**Unpinned remote script download breaks action-pinning discipline**
Every other action in this repo is pinned to a full commit SHA (e.g., `actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5`), but the flyctl installer is fetched at runtime from `https://fly.io/install.sh` with no version or hash constraint. If fly.io's install script is ever compromised or unexpectedly changed, arbitrary code runs inside the `production` environment alongside `FLY_API_TOKEN`. The official `superfly/flyctl-actions/setup-flyctl` GitHub Action can be pinned to a specific commit SHA to close this gap — the PR description already notes this as a follow-up, but given the repo's strict pinning discipline and production secret exposure here, it's worth addressing before merge.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
.github/workflows/deploy-relay.yml (2)
32-35: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winPrefer the official pinned flyctl action over
curl | shof an unpinned installer.
curl -L https://fly.io/install.sh | shpulls and executes whatever the latest installer ships, giving non-reproducible builds and a broader supply-chain surface. The officialsuperfly/flyctl-actions/setup-flyctl(pinned to a commit SHA, as you already do forcheckout) lets you pin a specific flyctl version.♻️ Suggested approach
- - name: Install flyctl - run: | - curl -L https://fly.io/install.sh | sh - echo "$HOME/.fly/bin" >> "$GITHUB_PATH" + - name: Install flyctl + uses: superfly/flyctl-actions/setup-flyctl@<pinned-sha> # vX.Y.Z + with: + version: <pinned-flyctl-version>🤖 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/deploy-relay.yml around lines 32 - 35, Replace the custom “Install flyctl” step that pipes the unpinned installer into sh with the official superfly/flyctl-actions/setup-flyctl action, pinned to a specific commit SHA; configure the action to use the required flyctl version and remove the manual PATH update.
22-26: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winAdd a job
timeout-minutesto cap stuck deploys.With
cancel-in-progress: false(Line 20), a hungfly deploy/smoke test would occupy the default 6-hour job timeout and block every queued relay deploy behind it. A tight bound (e.g. 20–30 min) fails fast and frees the concurrency slot.♻️ Proposed change
deploy-relay: name: Deploy Relay to Fly runs-on: ubuntu-latest + timeout-minutes: 30 environment: production🤖 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/deploy-relay.yml around lines 22 - 26, Add a job-level timeout to the `deploy-relay` job by setting `timeout-minutes` to a tight bound such as 30, ensuring hung Fly deployments or smoke tests release the concurrency slot promptly.
🤖 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/deploy-relay.yml:
- Around line 29-30: Configure the actions/checkout step in the deploy workflow
with persist-credentials: false, keeping the existing pinned checkout action and
ensuring no GITHUB_TOKEN credentials are stored in .git/config.
---
Nitpick comments:
In @.github/workflows/deploy-relay.yml:
- Around line 32-35: Replace the custom “Install flyctl” step that pipes the
unpinned installer into sh with the official
superfly/flyctl-actions/setup-flyctl action, pinned to a specific commit SHA;
configure the action to use the required flyctl version and remove the manual
PATH update.
- Around line 22-26: Add a job-level timeout to the `deploy-relay` job by
setting `timeout-minutes` to a tight bound such as 30, ensuring hung Fly
deployments or smoke tests release the concurrency slot promptly.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3a64fb50-480e-4aac-9546-29851f3e9e7c
📒 Files selected for processing (1)
.github/workflows/deploy-relay.yml
🚀 Preview Deployment🔗 Preview Links
Preview updates automatically with new commits |
There was a problem hiding this comment.
3 issues found across 1 file
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name=".github/workflows/deploy-relay.yml">
<violation number="1" location=".github/workflows/deploy-relay.yml:30">
P2: Set `persist-credentials: false` on the checkout step. The deploy script only needs the working tree (not authenticated git operations), so there's no reason to leave the `GITHUB_TOKEN` persisted in `.git/config` where later steps (including the unpinned `curl | sh` installer) could read it.
```yaml
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false
```</violation>
<violation number="2" location=".github/workflows/deploy-relay.yml:34">
P2: The flyctl install via `curl | sh` is unpinned, pulling the latest CLI on every run. This is inconsistent with the repo's SHA-pinning conventions (e.g., actions/checkout pinned to a SHA+tag, vercel CLI pinned via $VERCEL_CLI_VERSION env var). The official Fly docs recommend pinning flyctl to a specific version for production deployments. Unexpected breaking changes in a new flyctl release could silently break this deploy. Consider using the `superfly/flyctl-actions/setup-flyctl` action with a pinned SHA and `version:` input, or add a version flag to the curl install.</violation>
<violation number="3" location=".github/workflows/deploy-relay.yml:34">
P2: The flyctl install step pipes curl output straight to sh without the `--fail` flag. If fly.io's CDN serves an HTTP error, curl won't signal failure — it'll pipe the error page to sh, which could produce a confusing error or, worse, silently "succeed" without installing flyctl. The deploy step that follows would then fail with a misleading `fly: command not found`.
The repo already uses `curl -fsSL` (with `-f`/`--fail`) in `bump-homebrew.yml`. Let's keep that convention: add `--fail` so HTTP errors from the installer are surfaced immediately.</violation>
</file>
Architecture diagram
sequenceDiagram
participant GitHub as GitHub (on push)
participant Runner as GitHub Actions Runner
participant Flyctl as flyctl CLI
participant FlyAPI as Fly.io API
participant DeployScript as apps/relay/scripts/deploy.sh
Note over GitHub,DeployScript: NEW: Dedicated Deploy Relay workflow
alt Push to main with matching paths
GitHub->>Runner: Trigger workflow (path filter passed)
else Workflow dispatch (manual)
GitHub->>Runner: Trigger workflow (workflow_dispatch)
end
Note over Runner: Concurrency group "deploy-relay" (no cancel)
Runner->>Runner: Checkout repository
Runner->>Runner: Install flyctl via curl
Runner->>DeployScript: Run deploy.sh with FLY_API_TOKEN secret
DeployScript->>Flyctl: fly scale (adjust instances per region)
Flyctl->>FlyAPI: scale API calls
FlyAPI-->>Flyctl: Success
DeployScript->>Flyctl: fly deploy (rolling update across 7 regions)
loop per region
Flyctl->>FlyAPI: Deploy new version
FlyAPI-->>Flyctl: Health check (smoke test)
end
Flyctl-->>DeployScript: Deployment complete
DeployScript-->>Runner: Exit 0
Runner-->>GitHub: Job success
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
|
||
| - name: Install flyctl | ||
| run: | | ||
| curl -L https://fly.io/install.sh | sh |
There was a problem hiding this comment.
P2: The flyctl install via curl | sh is unpinned, pulling the latest CLI on every run. This is inconsistent with the repo's SHA-pinning conventions (e.g., actions/checkout pinned to a SHA+tag, vercel CLI pinned via $VERCEL_CLI_VERSION env var). The official Fly docs recommend pinning flyctl to a specific version for production deployments. Unexpected breaking changes in a new flyctl release could silently break this deploy. Consider using the superfly/flyctl-actions/setup-flyctl action with a pinned SHA and version: input, or add a version flag to the curl install.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/deploy-relay.yml, line 34:
<comment>The flyctl install via `curl | sh` is unpinned, pulling the latest CLI on every run. This is inconsistent with the repo's SHA-pinning conventions (e.g., actions/checkout pinned to a SHA+tag, vercel CLI pinned via $VERCEL_CLI_VERSION env var). The official Fly docs recommend pinning flyctl to a specific version for production deployments. Unexpected breaking changes in a new flyctl release could silently break this deploy. Consider using the `superfly/flyctl-actions/setup-flyctl` action with a pinned SHA and `version:` input, or add a version flag to the curl install.</comment>
<file context>
@@ -0,0 +1,40 @@
+
+ - name: Install flyctl
+ run: |
+ curl -L https://fly.io/install.sh | sh
+ echo "$HOME/.fly/bin" >> "$GITHUB_PATH"
+
</file context>
|
|
||
| - name: Install flyctl | ||
| run: | | ||
| curl -L https://fly.io/install.sh | sh |
There was a problem hiding this comment.
P2: The flyctl install step pipes curl output straight to sh without the --fail flag. If fly.io's CDN serves an HTTP error, curl won't signal failure — it'll pipe the error page to sh, which could produce a confusing error or, worse, silently "succeed" without installing flyctl. The deploy step that follows would then fail with a misleading fly: command not found.
The repo already uses curl -fsSL (with -f/--fail) in bump-homebrew.yml. Let's keep that convention: add --fail so HTTP errors from the installer are surfaced immediately.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/deploy-relay.yml, line 34:
<comment>The flyctl install step pipes curl output straight to sh without the `--fail` flag. If fly.io's CDN serves an HTTP error, curl won't signal failure — it'll pipe the error page to sh, which could produce a confusing error or, worse, silently "succeed" without installing flyctl. The deploy step that follows would then fail with a misleading `fly: command not found`.
The repo already uses `curl -fsSL` (with `-f`/`--fail`) in `bump-homebrew.yml`. Let's keep that convention: add `--fail` so HTTP errors from the installer are surfaced immediately.</comment>
<file context>
@@ -0,0 +1,40 @@
+
+ - name: Install flyctl
+ run: |
+ curl -L https://fly.io/install.sh | sh
+ echo "$HOME/.fly/bin" >> "$GITHUB_PATH"
+
</file context>
The deploy script only reads the working tree (git rev-parse), so no authenticated git token is needed on the runner.
Summary
The relay ships to Fly, not Vercel, and was never wired into CI.
deploy-production.yml(on push tomain) deploys only DB / API / Web / Marketing / Admin — all Vercel. So relay changes silently never deployed: a merged relay fix stays dark until someone remembers to run the manual Fly deploy. This is exactly what happened with the descriptive-Forbiddenrelay fix in #5573.This adds a dedicated
Deploy Relayworkflow that reuses the canonicalapps/relay/scripts/deploy.sh(the samefly scale+ rolling deploy + smoke-test a human would run — single source of truth, no duplicated deploy logic).Design choices
deploy-production.yml. A 7-region rolling Fly deploy mutates the live fleet and is too heavy to run on every unrelated push tomain. So it's path-filtered toapps/relay/**+packages/shared/**(the runtime code the relay bundles) + the workflow file.workflow_dispatchincluded so it can be force-run — notably to ship the relay fix from fix(cli,relay): unbreak headless host registration (env key, org selection, relay reason) #5573, which merged before this workflow existed and so won't be picked up by the path filter retroactively.concurrency: deploy-relay(no cancel) so two rolling deploys never overlap.actions/checkoutSHA; installsflyctlvia the official installer (mirrors how the Vercel jobs install their CLI).Required before this works
FLY_API_TOKENsecret to the production environment (repo Settings → Environments → production). Without it the deploy step fails fast.Test plan
push(path-filtered) +workflow_dispatch.Follow-up (optional)
flyctlinstall to a SHA-pinnedsuperfly/flyctl-actions/setup-flyctlto match the repo's action-pinning discipline.Summary by cubic
Adds a dedicated GitHub Actions workflow to deploy the relay to Fly on relevant changes, using the canonical
apps/relay/scripts/deploy.sh, preventing overlapping deploys, and hardening checkout by not persisting credentials.New Features
.github/workflows/deploy-relay.yml.pushtomain(path-filtered) andworkflow_dispatchfor manual runs.concurrency: deploy-relay(no cancel) to avoid overlapping rolling deploys.flyctland runsapps/relay/scripts/deploy.sh(scale, rolling deploy, smoke test) in theproductionenvironment.persist-credentials: falsesince the deploy reads only the working tree.Migration
FLY_API_TOKENsecret to theproductionenvironment before running.Written for commit 1598d73. Summary will update on new commits.
Summary by CodeRabbit