Skip to content

blog: Lovable → Capgo live updates via GitHub Actions - #928

Merged
riderx merged 1 commit into
mainfrom
blog/lovable-capgo-github-actions
Aug 6, 2026
Merged

blog: Lovable → Capgo live updates via GitHub Actions#928
riderx merged 1 commit into
mainfrom
blog/lovable-capgo-github-actions

Conversation

@riderx

@riderx riderx commented Aug 6, 2026

Copy link
Copy Markdown
Member

Summary

  • New blog: /blog/automate-capgo-live-updates-from-lovable-github-actions/
  • Covers Publish → GitHub push → Actions → bundle upload (Lovable cannot run Capgo on Publish)
  • Step-by-step for clients: create Capgo API key, add CAPGO_TOKEN GitHub secret, Lovable prompt or workflow YAML
  • Cross-link from the existing Lovable → Capacitor guide (Step 12)

Test plan

  • Preview blog page after merge
  • Send Kuldeep the blog URL + Step 2 (GitHub secret) checklist

Made with Cursor


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Review in cubic

Summary by CodeRabbit

  • Documentation
    • Added a comprehensive guide for automating Capgo Live Update deployments from Lovable through GitHub Actions.
    • Documented setup requirements, API key and repository secret configuration, workflow creation, Vite and encryption considerations, verification, troubleshooting, and client guidance.
    • Added a related link to the mobile app development guide.

Explain Publish → GitHub push → Actions → bundle upload, with CAPGO_TOKEN setup and a Lovable prompt for the workflow file.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Aug 6, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_9bbf56ce-1e87-4dcf-8520-61563037b018)

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Added an English guide for using Lovable Publish with GitHub Actions to upload Capgo Live Updates. The guide covers token setup, workflow creation, build configuration, verification, troubleshooting, and client handoff. An existing Capacitor article now links to the guide.

Changes

Lovable Capgo deployment guide

Layer / File(s) Summary
Setup and prerequisites
apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md
Added publication metadata, deployment prerequisites, the Lovable-to-Capgo flow, and CAPGO_TOKEN setup instructions.
Workflow and handoff
apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md, apps/web/src/content/blog/en/transform-lovable-dev-app-to-mobile-with-capacitor.md
Added GitHub Actions workflow options, Vite and encryption guidance, verification steps, troubleshooting, client handoff instructions, and a related-guide link.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: actions-user

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the new Lovable-to-Capgo live updates blog post using GitHub Actions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

@sonarqubecloud

sonarqubecloud Bot commented Aug 6, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 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
`@apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md`:
- Line 59: Update the handoff sentence in the article to remove the personal
name “Kuldeep,” replacing it with a generic reference such as “the repository
owner” or “the client” while preserving the instruction’s meaning.
- Around line 104-120: Add workflow-level permissions restricting repository
contents to read-only, and configure the Checkout step’s actions/checkout@v6
usage with persist-credentials disabled before running install/build commands.
Leave the existing CAPGO_TOKEN scoping unchanged.
- Around line 122-125: Update the “Upload bundle to Capgo” workflow step to
include the --fail-on-incompatible option in the production bundle upload
command, ensuring CI fails and does not promote incompatible bundles.
- Around line 84-85: Update the deployment instructions to pin a fixed
`@capgo/cli` version in the app dependencies, then invoke that installed version
with npx --no-install instead of `@capgo/cli`@latest when using CAPGO_TOKEN.
- Around line 49-55: Update the “Step 1 — Create a Capgo API key” instructions
to link to https://console.capgo.app/settings/organization/api-keys and
explicitly direct users to create an app-scoped key with the “App Uploader”
role, replacing the current legacy link and generic upload-permission wording
while preserving the copy-once and secret-handling guidance.
- Around line 130-132: Update the encrypted bundles guidance to configure
CAPGO_PRIVATE_KEY in the upload step’s env block and pass it as
"$CAPGO_PRIVATE_KEY" to --key-data-v2, rather than interpolating the GitHub
secret directly in the shell command.
- Around line 79-87: Update the workflow instructions and Lovable prompt to
require a unique SemVer bundle version greater than the app’s native baseline,
derived using both GITHUB_RUN_ID and GITHUB_RUN_ATTEMPT so reruns produce
distinct versions. Apply this explicit --bundle version rule consistently to
both upload steps, rather than relying on the CLI’s existing version fallback.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d2c654f1-6622-4dc4-a53e-237c00a66a65

📥 Commits

Reviewing files that changed from the base of the PR and between a950895 and 430d6e0.

📒 Files selected for processing (2)
  • apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md
  • apps/web/src/content/blog/en/transform-lovable-dev-app-to-mobile-with-capacitor.md
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • Cap-go/capacitor-updater (manual)

Comment on lines +49 to +55
## Step 1 — Create a Capgo API key

1. Open [console.capgo.app/apikeys/](https://console.capgo.app/apikeys/)
2. Create an API key with permission to upload bundles for your app
3. Copy the key once. You will not see the full value again

Treat this key like a password. Never commit it to Git or paste it into Lovable chat.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- API-key references ---'
rg -n -i -C 3 'api.?keys|App Uploader|organization API|apikeys' apps/web/src apps/docs/src 2>/dev/null | head -240
printf '%s\n' '--- Reviewed article context ---'
sed -n '35,70p' apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md

Repository: Cap-go/website

Length of output: 29493


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- API-key documentation files ---'
fd -i 'api.*key|key.*api' apps/docs/src apps/web/src || true
printf '%s\n' '--- Documentation links and route definitions ---'
rg -n -i -C 2 'webapp/api-keys|public-api/api-keys|apikeys/' apps/docs/src apps/web/src | head -160
printf '%s\n' '--- Live documentation page ---'
curl -LsS --max-time 20 https://capgo.app/docs/webapp/api-keys/ | rg -i -o '.{0,100}(App Uploader|organization|apikeys|API key).{0,180}' | head -40 || true

Repository: Cap-go/website

Length of output: 30839


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- API-key setup instructions ---'
sed -n '1,125p' apps/docs/src/content/docs/docs/webapp/api-keys.mdx
printf '%s\n' '--- Reviewed link target status ---'
curl -LsS -o /tmp/capgo-apikeys.html -w 'HTTP %{http_code}\n' --max-time 20 https://console.capgo.app/apikeys/
grep -o '<title>[^<]*</title>' /tmp/capgo-apikeys.html | head -1 || true

Repository: Cap-go/website

Length of output: 8637


Security Misconfiguration (CWE-269): Improper Privilege Management

Reachability: External · Exploitability: Moderate

Direct users to the RBAC API-key page.

Link to https://console.capgo.app/settings/organization/api-keys and instruct users to create an app-scoped key with the App Uploader role. The current link and generic permission wording can lead users to create a legacy or organization-wide key.

🤖 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
`@apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md`
around lines 49 - 55, Update the “Step 1 — Create a Capgo API key” instructions
to link to https://console.capgo.app/settings/organization/api-keys and
explicitly direct users to create an app-scoped key with the “App Uploader”
role, replacing the current legacy link and generic upload-permission wording
while preserving the copy-once and secret-handling guidance.

Source: MCP tools


## Step 2 — Add `CAPGO_TOKEN` in GitHub (the only env step)

This is the step you send to Kuldeep and any client who owns the repo.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the personal name from the public guide.

Kuldeep is not defined in this article and makes the handoff instructions look client-specific. Replace it with “the repository owner” or “the client.”

🤖 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
`@apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md`
at line 59, Update the handoff sentence in the article to remove the personal
name “Kuldeep,” replacing it with a generic reference such as “the repository
owner” or “the client” while preserving the instruction’s meaning.

Comment on lines +79 to +87
Create `.github/workflows/capgo-live-updates.yml` that runs on every push to `main`:

1. Checkout code
2. Setup Node.js 24 with npm cache
3. Run `npm ci` and `npm run build`
4. Run `npx @capgo/cli@latest bundle upload --channel=production`
5. Use environment variable `CAPGO_TOKEN` from GitHub Actions secrets (do not hardcode the API key)

Use `actions/checkout@v6` and `actions/setup-node@v6`. Commit the workflow file to the repo.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- target article ---'
sed -n '1,180p' apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md
printf '%s\n' '--- relevant repository references ---'
rg -n -i --glob '!node_modules' --glob '!dist' \
  'bundle upload|--bundle|CapacitorUpdater\.version|package\.json|unique.*version|version.*unique' \
  apps/web/src/content apps/web/src 2>/dev/null | head -250
printf '%s\n' '--- related repository workflow/version references ---'
rg -n -i --glob '!node_modules' --glob '!dist' \
  'bundle upload|--bundle|CapacitorUpdater\.version|package\.json|version' \
  .github package.json README.md docs 2>/dev/null | head -250 || true

Repository: Cap-go/website

Length of output: 50372


🌐 Web query:

Capgo CLI bundle upload --bundle version CapacitorUpdater.version package.json unique bundle version

💡 Result:

When using the Capgo CLI to upload a bundle, the bundle version is determined by the CLI in a specific order of precedence [1]. The CLI looks for the version identifier using the following priority [1]: 1. The explicit version provided via the --bundle command-line option [1]. 2. The version defined in your capacitor.config.json under the key plugins.CapacitorUpdater.version [1]. 3. The version found in the package.json file (the version key) [2][1]. 4. If no version is found in these locations, it defaults to a generated string: 0.0.1-beta. [1]. All versions must adhere to semantic versioning (semver) standards [1]. It is a requirement that every bundle version uploaded to Capgo Cloud be unique and greater than 0.0.0 [3][4]. Once a version is uploaded, it cannot be overridden or reused—even if it is deleted—due to security reasons [3][4]. If you do not use the --bundle option, the CLI will automatically pull the version from your package.json [2]. To upload a bundle, use the command: npx @capgo/cli@latest bundle upload [3][5]

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- updater version handling ---'
rg -n -i --glob '!node_modules' --glob '!dist' \
  'bundle.*version|version.*bundle|CapacitorUpdater\.version|package\.json|unique|already exists|duplicate' \
  . | head -300
printf '%s\n' '--- package and workflow files ---'
find . -maxdepth 4 -type f \( -name 'package.json' -o -path '*/.github/workflows/*' -o -iname '*config*' \) -print | head -200

Repository: Cap-go/capacitor-updater

Length of output: 41759


🏁 Script executed (no clone):

#!/bin/bash
set -eu
python3 - <<'PY'
import json, urllib.request
urls = [
  "https://raw.githubusercontent.com/Cap-go/CLI/main/src/bundle/upload.ts",
  "https://raw.githubusercontent.com/Cap-go/CLI/main/src/commands/bundle/upload.ts",
]
for url in urls:
    print(f"--- {url} ---")
    try:
        with urllib.request.urlopen(url, timeout=20) as r:
            text = r.read().decode()
        print(text[:30000])
    except Exception as e:
        print(type(e).__name__, e)
PY
printf '%s\n' '--- linked documentation excerpts ---'
for url in \
  https://capgo.app/docs/cli/reference/bundle/ \
  https://capgo.app/blog/how-version-work-in-capgo/ \
  https://capgo.app/docs/cli/commands/; do
  echo "--- $url ---"
  curl -Ls --max-time 20 "$url" | tr '\n' ' ' | \
    sed 's/<[^>]*>/ /g; s/&nbsp;/ /g; s/&amp;/\&/g' | \
    grep -Eo '.{0,180}(--bundle|package\.json|CapacitorUpdater|unique|semver|override|reus).{0,260}' | head -30 || true
done

Length of output: 15088


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- version documentation ---'
for f in \
  apps/web/src/content/blog/en/how-version-work-in-capgo.md \
  apps/web/src/content/blog/en/automatic-build-and-release-with-github-actions.md \
  apps/web/src/pages/semver_tester.astro; do
  echo "--- $f ---"
  rg -n -C 5 'CapacitorUpdater\.version|--bundle|package\.json|unique|semver|version' "$f" | head -180
done
printf '%s\n' '--- article occurrences and line references ---'
rg -n -C 4 'bundle upload|Lovable|Publish|version|workflow' \
  apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md

Repository: Cap-go/website

Length of output: 28347


🏁 Script executed (no clone):

#!/bin/bash
set -eu
for url in \
  https://raw.githubusercontent.com/Cap-go/CLI/main/src/bundle/upload.ts \
  https://raw.githubusercontent.com/Cap-go/CLI/main/src/utils/getPackageJson.ts \
  https://raw.githubusercontent.com/Cap-go/CLI/main/src/utils/getVersion.ts; do
  echo "--- $url ---"
  curl -k -Ls --max-time 20 "$url" | \
    rg -n -C 6 'version|bundle|capacitor\.config|package\.json|semver|unique|random' | head -160 || true
done
printf '%s\n' '--- semver shape probe ---'
node - <<'JS'
const versions = ['0.0.1-ci.1', '0.0.1-ci.123456789', '0.0.1-ci.0'];
for (const v of versions) {
  const m = v.match(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/);
  console.log(v, Boolean(m), m?.[4]);
}
JS

Length of output: 8348


🌐 Web query:

site:docs.github.com GITHUB_RUN_ID GITHUB_RUN_ATTEMPT rerun workflow same run ID

💡 Result:

When you rerun a workflow run in GitHub Actions, the GITHUB_RUN_ID remains the same, while the GITHUB_RUN_ATTEMPT increments [1][2]. Specifically: - GITHUB_RUN_ID: This is a unique identifier for the workflow run within a repository. It does not change if you rerun the workflow run [1][3]. - GITHUB_RUN_ATTEMPT: This is a unique number for each attempt of a particular workflow run. It starts at 1 for the initial attempt and increments with every subsequent rerun [1][2]. This allows you to uniquely identify a specific attempt of a workflow run using a combination of the stable GITHUB_RUN_ID and the incrementing GITHUB_RUN_ATTEMPT [1].

Citations:


🏁 Script executed (no clone):

#!/bin/bash
set -eu
python3 - <<'PY'
from functools import total_ordering
import re

def semver(v):
    main, _, pre = v.partition('-')
    nums = tuple(map(int, main.split('.')))
    ids = [] if not pre else pre.split('.')
    return nums, ids

def cmp(a,b):
    am, ap = semver(a); bm, bp = semver(b)
    if am != bm: return (am > bm) - (am < bm)
    if not ap or not bp: return (not ap) - (not bp)
    for x,y in zip(ap,bp):
        if x == y: continue
        xn, yn = x.isdigit(), y.isdigit()
        if xn and yn: return (int(x)>int(y))-(int(x)<int(y))
        if xn != yn: return -1 if xn else 1
        return (x>y)-(x<y)
    return (len(ap)>len(bp))-(len(ap)<len(bp))

for a,b in [('0.0.1-ci.100','1.0.0'), ('1.0.1-ci.100','1.0.0'),
            ('1.0.1-ci.100.1','1.0.1-ci.100.2')]:
    print(f'{a} < {b}:', cmp(a,b) < 0)
PY

Length of output: 218


Use a unique bundle version that can update the installed app.

If --bundle is omitted, the CLI uses plugins.CapacitorUpdater.version or package.json.version; unchanged values are reused on later publishes. Capgo rejects reused versions. A random fallback applies only when neither value exists.

Use a unique SemVer greater than the app’s native baseline. Include both GITHUB_RUN_ID and GITHUB_RUN_ATTEMPT so workflow reruns also get distinct versions. Apply the same rule to the Lovable prompt and both upload steps.

🤖 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
`@apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md`
around lines 79 - 87, Update the workflow instructions and Lovable prompt to
require a unique SemVer bundle version greater than the app’s native baseline,
derived using both GITHUB_RUN_ID and GITHUB_RUN_ATTEMPT so reruns produce
distinct versions. Apply this explicit --bundle version rule consistently to
both upload steps, rather than relying on the CLI’s existing version fallback.

Source: MCP tools

Comment on lines +84 to +85
4. Run `npx @capgo/cli@latest bundle upload --channel=production`
5. Use environment variable `CAPGO_TOKEN` from GitHub Actions secrets (do not hardcode the API key)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- target document ---'
sed -n '70,135p' apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md

printf '%s\n' '--- package manifests and lockfiles ---'
find . -maxdepth 3 -type f \( -name 'package.json' -o -name 'package-lock.json' -o -name 'npm-shrinkwrap.json' -o -name 'yarn.lock' -o -name 'pnpm-lock.yaml' \) -print | sort

printf '%s\n' '--- related CLI instructions ---'
rg -n -C 2 'npx `@capgo/cli`|CAPGO_TOKEN|bundle upload' apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md apps/docs/src/content/docs/docs/getting-started/deploy.mdx apps/docs/src/content/docs/docs/getting-started/cicd-integration.mdx || true

printf '%s\n' '--- repository CLI dependency declarations ---'
rg -n -C 2 '"`@capgo/cli`"|`@capgo/cli`' --glob 'package.json' --glob '*lock*' . || true

Repository: Cap-go/website

Length of output: 19085


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
import json
import re

doc = Path("apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md").read_text()
commands = re.findall(r"npx\s+@capgo/cli(?:@[^\s]+)?\s+bundle upload[^\n`]*", doc)
manifests = []
for path in Path(".").rglob("package.json"):
    try:
        data = json.loads(path.read_text())
    except (OSError, json.JSONDecodeError):
        continue
    deps = {}
    for section in ("dependencies", "devDependencies", "optionalDependencies"):
        deps.update(data.get(section, {}))
    if "`@capgo/cli`" in deps:
        manifests.append((str(path), deps["`@capgo/cli`"]))

print("documented_cli_commands:")
for command in commands:
    print(f"- {command}")
print("uses_latest_tag:", any("`@capgo/cli`@latest" in command for command in commands))
print("local_cli_dependency_declarations:", manifests)
print("lockfile_backed_local_binary_possible:", bool(manifests))
PY

Repository: Cap-go/website

Length of output: 459


Other (CWE-829): Inclusion of Functionality from Untrusted Control Sphere

Reachability: External

Pin the Capgo CLI used with the production secret.

Add a fixed @capgo/cli version to the app dependencies and run it with npx --no-install. Do not use npx @capgo/cli@latest in a workflow that exposes CAPGO_TOKEN.

🤖 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
`@apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md`
around lines 84 - 85, Update the deployment instructions to pin a fixed
`@capgo/cli` version in the app dependencies, then invoke that installed version
with npx --no-install instead of `@capgo/cli`@latest when using CAPGO_TOKEN.

Source: MCP tools

Comment on lines +104 to +120
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
cache: 'npm'

- name: Install and build
run: |
npm ci
npm run build

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

file='apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md'
printf '%s\n' '--- target content ---'
sed -n '70,145p' "$file"

printf '%s\n' '--- workflow-related occurrences ---'
rg -n -C 3 'actions/checkout|npm ci|npm run build|CAPGO_TOKEN|permissions:|persist-credentials|on:' \
  apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md \
  apps/docs/src/content/docs/docs/getting-started/deploy.mdx || true

printf '%s\n' '--- exact workflow-shaped examples in repository ---'
rg -n -U -C 2 'jobs:\n[\s\S]{0,500}actions/checkout@v6' apps --glob '*.md' --glob '*.mdx' || true

Repository: Cap-go/website

Length of output: 46050


Security Misconfiguration (CWE-269): Improper Privilege Management

Reachability: External

Restrict GitHub credentials before running repository code.

Add permissions: contents: read at the workflow level and set persist-credentials: false on actions/checkout. CAPGO_TOKEN is already scoped to the upload step.

🤖 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
`@apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md`
around lines 104 - 120, Add workflow-level permissions restricting repository
contents to read-only, and configure the Checkout step’s actions/checkout@v6
usage with persist-credentials disabled before running install/build commands.
Leave the existing CAPGO_TOKEN scoping unchanged.

Source: MCP tools

Comment on lines +122 to +125
- name: Upload bundle to Capgo
run: npx @capgo/cli@latest bundle upload --channel=production
env:
CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- target content ---'
sed -n '95,140p' apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md
printf '%s\n' '--- repository references to fail-on-incompatible ---'
rg -n --hidden --glob '!node_modules' --glob '!dist' 'fail-on-incompatible|bundle upload|--channel=production' .
printf '%s\n' '--- target file history summary ---'
git diff --stat
git status --short

Repository: Cap-go/website

Length of output: 42996


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- references to fail-on-incompatible and bundle upload ---'
rg -n --hidden --glob '!node_modules' --glob '!dist' 'fail-on-incompatible|bundle upload|channel=production' .

Repository: Cap-go/capacitor-updater

Length of output: 543


🌐 Web query:

Capgo CLI bundle upload --fail-on-incompatible documentation

💡 Result:

The --fail-on-incompatible option is a flag available for the Capgo CLI bundle upload command [1][2]. When used, it instructs the CLI to fail the upload process (exiting with a non-zero status) if the bundle being uploaded is found to be incompatible with the current native packages of the target channel [1][2]. Key details regarding this option: - Purpose: It serves as a safety mechanism in CI/CD pipelines to prevent the deployment of bundles that cannot safely update the current native build [2][3]. - Behavior in Interactive Terminals: In an interactive terminal environment, if a compatibility issue is detected, you are given the option to choose a native build; however, choosing to decline will result in the command failing [1][2]. - Limitations: This option cannot be combined with the --ignore-metadata-check flag [1][2]. You can implement this in your CI/CD pipeline by appending the flag to your upload command [2]: npx @capgo/cli@latest bundle upload [appId] --channel production --fail-on-incompatible

Citations:


🏁 Script executed:

#!/bin/bash
set -e
sed -n '95,140p' apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md
rg -n --hidden --glob '!node_modules' --glob '!dist' 'fail-on-incompatible|bundle upload|--channel=production' .

Repository: Cap-go/website

Length of output: 42883


🏁 Script executed:

#!/bin/bash
set -e
rg -n --hidden --glob '!node_modules' --glob '!dist' 'fail-on-incompatible|bundle upload|channel=production' .

Repository: Cap-go/capacitor-updater

Length of output: 482


🏁 Script executed:

#!/bin/bash
set -e
python3 - <<'PY'
from pathlib import Path
import re

target = Path("apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md").read_text()
compat = Path("apps/docs/src/content/docs/docs/live-updates/compatibility.mdx").read_text()
reference = Path("apps/docs/src/content/docs/docs/cli/reference/bundle.mdx").read_text()

commands = re.findall(r"npx `@capgo/cli`@latest bundle upload[^\n`]*", target)
print("target upload commands:")
for command in commands:
    print(command)
print("target production command lacks compatibility gate:",
      any("--channel=production" in command and "--fail-on-incompatible" not in command
          for command in commands))

start = compat.index("**Fail the upload in CI — `--fail-on-incompatible`**")
print("\ncompatibility documentation excerpt:")
print(compat[start:start + 650])

flag = re.search(r"\| \*\*--fail-on-incompatible\*\* \|.*", reference)
print("\nCLI reference flag:")
print(flag.group(0) if flag else "not found")
PY

Repository: Cap-go/website

Length of output: 1431


🏁 Script executed:

#!/bin/bash
set -e
python3 - <<'PY'
from pathlib import Path
import re

target = Path("apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md").read_text()
compat = Path("apps/docs/src/content/docs/docs/live-updates/compatibility.mdx").read_text()
reference = Path("apps/docs/src/content/docs/docs/cli/reference/bundle.mdx").read_text()

commands = re.findall(r"npx `@capgo/cli`@latest bundle upload[^\n`]*", target)
for command in commands:
    print(command)
print("production command without gate:",
      any("--channel=production" in command and "--fail-on-incompatible" not in command
          for command in commands))

start = compat.index("**Fail the upload in CI — `--fail-on-incompatible`**")
print(compat[start:start + 650])

flag = re.search(r"\| \*\*--fail-on-incompatible\*\* \|.*", reference)
print(flag.group(0) if flag else "not found")
PY

Repository: Cap-go/website

Length of output: 1329


Add --fail-on-incompatible to the production upload.

Without this flag, an incompatible bundle can be promoted to production. The flag makes CI exit non-zero and ship nothing when native packages do not match.

🤖 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
`@apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md`
around lines 122 - 125, Update the “Upload bundle to Capgo” workflow step to
include the --fail-on-incompatible option in the production bundle upload
command, ensuring CI fails and does not promote incompatible bundles.

Source: MCP tools

Comment on lines +130 to +132
**Vite `base` path:** Lovable Vite apps often need `base: './'` in `vite.config.ts` so assets load inside the native shell. If users see a white screen after an OTA update, fix `base`, publish again, and let the workflow redeploy.

**Encrypted bundles:** If you use [Capgo encryption](/docs/live-updates/encryption/), add `CAPGO_PRIVATE_KEY` as a second GitHub secret and pass `--key-data-v2 "${{ secrets.CAPGO_PRIVATE_KEY }}"` on the upload step.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- reviewed file ---'
sed -n '100,145p' apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md

printf '%s\n' '--- CAPGO_PRIVATE_KEY references in website ---'
rg -n -C 4 --hidden --glob '!node_modules' 'CAPGO_PRIVATE_KEY|key-data-v2' .

printf '%s\n' '--- related repository references ---'

Repository: Cap-go/website

Length of output: 23491


🏁 Script executed:

#!/bin/bash
set -eu

rg -n -C 4 --hidden --glob '!node_modules' 'CAPGO_PRIVATE_KEY|key-data-v2' .

Repository: Cap-go/capacitor-updater

Length of output: 162


🌐 Web query:

site:capgo.app/docs/live-updates/encryption "CAPGO_PRIVATE_KEY" "key-data-v2"

💡 Result:

The CAPGO_PRIVATE_KEY environment variable and the --key-data-v2 flag are components of the Capgo Live Updates encryption system, specifically designed for CI/CD pipelines [1]. When performing a bundle upload in a non-interactive CI/CD environment, you use these to provide the V2 private key without relying on a local file [1]. Usage Pattern: 1. Store the contents of your.capgo_key_v2 file (the private key) in an environment variable named CAPGO_PRIVATE_KEY [1]. 2. Pass this variable to the Capgo CLI during the bundle upload command using the --key-data-v2 option [1]. Example command: export CAPGO_PRIVATE_KEY="$(cat.capgo_key_v2)" npx @capgo/cli@latest bundle upload --key-data-v2 "$CAPGO_PRIVATE_KEY" Security Reminder: The private key (.capgo_key_v2) is used for encrypting bundles during the upload process and must never be committed to version control [1]. Only the public key (.capgo_key_v2.pub) is safe to commit, as it is required by the mobile app for decryption [1]. Always ensure the public key is saved into your Capacitor configuration using the npx @capgo/cli@latest key save --key./.capgo_key_v2.pub command [1].

Citations:


🌐 Web query:

GitHub Actions secrets multiline values inline run shell interpolation environment variable recommended

💡 Result:

The recommended approach for handling multiline secrets in GitHub Actions is to avoid inlining them directly into shell commands [1][2][3]. Instead, you should map the secret to an environment variable at the job or step level and then reference that environment variable within your run script [1][2]. Recommended Workflow Implementation: 1. Map the secret to an environment variable: Define the secret in an env block. Using the pipe (|) syntax in YAML can help handle multiline strings cleanly when defining environment variables [4][5]. steps: - name: Example Step env: MY_MULTILINE_SECRET: ${{ secrets.MY_MULTILINE_SECRET }} run: | # Reference the variable using standard shell syntax (e.g., $VAR) echo "$MY_MULTILINE_SECRET" > secret_file.txt 2. Why this is recommended: - Security: Referencing the secret via an environment variable avoids placing it in the command line arguments, where it could be exposed in process lists or logs [1][2]. - Masking: GitHub's log masker is designed to redact the raw secret value [6][3]. If you transform or interpolate the secret in ways that create new string representations (like base64 encoding or partial logging), the masker may fail to recognize and redact the sensitive data [6][3]. - Robustness: Directly inlining secrets often leads to shell parsing errors due to special characters or newlines [5][2]. Mapping to an environment variable isolates the value, preventing these issues [4][2]. Best Practices for Multiline Secrets: - Base64 Encoding: For complex secrets like JSON blobs or private keys, many experts recommend Base64-encoding the value before storing it as a GitHub secret [6][7]. You can then decode it within the runner: echo "$MY_BASE64_SECRET" | base64 --decode > secret.pem [5][7]. This ensures the secret is stored as a single, consistent line, improving reliability for the masking system [6][7]. - Avoid Logging: Never attempt to echo or print the secret to the logs for debugging purposes [1][3]. Even if masked, derived values or structural patterns can still leak information [6][3]. If you must verify a secret's presence, print its length, not its content [3].

Citations:


🌐 Web query:

GitHub Actions expression interpolation secret value inserted into run script before shell execution multiline quotes

💡 Result:

Direct interpolation of secrets into run scripts using the ${{ secrets.SECRET_NAME }} syntax is a significant security risk and is strongly discouraged [1][2][3]. When you use this syntax, GitHub Actions performs a literal string replacement before the script is executed [4][2]. This means the secret value is treated as part of the command line, exposing it to potential script injection attacks, shell word splitting, and process listing exposure [1][2][3][5]. Recommended Best Practice: Use Environment Variables To safely use a secret, pass it to the step using an environment variable [1][3][6]. This keeps the secret in memory rather than embedding it directly into the shell script command line [1][2]. Example: jobs: job1: runs-on: ubuntu-latest steps: - name: Use secret safely env: MY_SECRET: ${{ secrets.MY_SECRET }} run: | # Use the environment variable, always wrapped in double quotes echo "$MY_SECRET" Handling Multiline Secrets When using environment variables, multiline secrets are preserved and handled correctly by the shell [7]. Always enclose the environment variable in double quotes (e.g., "$MY_SECRET") to ensure the shell treats the value as a single string, which prevents word splitting or unexpected interpretation of special characters like newlines [7][1][3]. Why Avoid Direct Interpolation? 1. Script Injection: If a secret contains characters like quotes, backticks, or dollar signs, they will be interpreted by the shell as code, potentially leading to command execution [8][2][9]. 2. Process Visibility: Secrets passed directly on the command line can be visible to other users on the system via the ps command or other process auditing tools [10][3][5]. 3. Log Exposure: While GitHub attempts to mask secrets in logs, direct interpolation increases the risk that transformations or partial content may leak if the secret is manipulated by the shell before being passed to a process [8][11]. If you must process or manipulate the secret (e.g., escaping specific characters), perform this manipulation within the script using the environment variable, not by trying to manipulate the secret string during YAML interpolation [4][1]. For extremely complex secrets, consider base64 encoding the secret when storing it and decoding it within the script [7][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- encryption key format and CI guidance ---'
sed -n '150,235p' apps/docs/src/content/docs/docs/live-updates/encryption.mdx
sed -n '120,155p' apps/docs/src/content/docs/docs/live-updates/integrations/github-actions.mdx

printf '%s\n' '--- exact workflow block ---'
sed -n '65,88p' apps/docs/src/content/docs/docs/live-updates/integrations/github-actions.mdx

Repository: Cap-go/website

Length of output: 4505


🏁 Script executed (no clone):

#!/bin/bash
set -eu

python3 - <<'PY'
import os
import subprocess

multiline = "BEGIN\\nline-two\\nEND"
shell_sensitive = "safe$(printf INJECTED)\\nEND"

def run_inline(value):
    script = f'printf "%s" "{value}"'
    return subprocess.run(
        ["bash", "-c", script],
        text=True,
        capture_output=True,
        check=False,
    )

def run_env(value):
    return subprocess.run(
        ["bash", "-c", 'printf "%s" "$CAPGO_PRIVATE_KEY"'],
        env={**os.environ, "CAPGO_PRIVATE_KEY": value},
        text=True,
        capture_output=True,
        check=False,
    )

for label, value in [("multiline", multiline), ("shell-sensitive", shell_sensitive)]:
    inline = run_inline(value)
    env = run_env(value)
    print(f"{label}: inline_rc={inline.returncode} inline_stdout={inline.stdout!r}")
    print(f"{label}: env_rc={env.returncode} env_stdout={env.stdout!r}")
PY

Length of output: 375


Pass the private key through the step environment. GitHub expands the secret into the generated shell script before execution, so shell-sensitive content can change parsing or execute commands. Set CAPGO_PRIVATE_KEY in the upload step’s env and pass --key-data-v2 "$CAPGO_PRIVATE_KEY".

🤖 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
`@apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md`
around lines 130 - 132, Update the encrypted bundles guidance to configure
CAPGO_PRIVATE_KEY in the upload step’s env block and pass it as
"$CAPGO_PRIVATE_KEY" to --key-data-v2, rather than interpolating the GitHub
secret directly in the shell command.

Source: MCP tools

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

4 issues found across 2 files

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="apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md">

<violation number="1" location="apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md:51">
P2: Consider linking to the RBAC API-key page (https://console.capgo.app/settings/organization/api-keys) and explicitly instructing users to create an app-scoped key with the App Uploader role. The current generic wording and link could lead users to create an overly-privileged, organization-wide key instead of a scoped one.</violation>

<violation number="2" location="apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md:59">
P3: Replace the personal name "Kuldeep" with a generic reference like "the repository owner" or "the client" — it's an internal name that shouldn't appear in a public blog post.</violation>

<violation number="3" location="apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md:122">
P2: Without `--fail-on-incompatible`, an incompatible bundle can still be uploaded and promoted to the `production` channel — per the Capgo CLI docs, this flag makes the upload exit non-zero instead of shipping when native packages don't match, so add it to the production upload command.</violation>

<violation number="4" location="apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md:122">
P1: The upload command omits `--bundle`, so the CLI falls back to `plugins.CapacitorUpdater.version` or `package.json.version`. Since these values often don't change between publishes, subsequent uploads will reuse the same version, and per the Capgo CLI docs the bundle version must be unique — causing the upload to be rejected on repeated Publish clicks. Generate a unique SemVer per run (e.g. incorporating `GITHUB_RUN_ID`/`GITHUB_RUN_ATTEMPT`) and pass it via `--bundle`.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

npm ci
npm run build

- name: Upload bundle to Capgo

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: The upload command omits --bundle, so the CLI falls back to plugins.CapacitorUpdater.version or package.json.version. Since these values often don't change between publishes, subsequent uploads will reuse the same version, and per the Capgo CLI docs the bundle version must be unique — causing the upload to be rejected on repeated Publish clicks. Generate a unique SemVer per run (e.g. incorporating GITHUB_RUN_ID/GITHUB_RUN_ATTEMPT) and pass it via --bundle.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md, line 122:

<comment>The upload command omits `--bundle`, so the CLI falls back to `plugins.CapacitorUpdater.version` or `package.json.version`. Since these values often don't change between publishes, subsequent uploads will reuse the same version, and per the Capgo CLI docs the bundle version must be unique — causing the upload to be rejected on repeated Publish clicks. Generate a unique SemVer per run (e.g. incorporating `GITHUB_RUN_ID`/`GITHUB_RUN_ATTEMPT`) and pass it via `--bundle`.</comment>

<file context>
@@ -0,0 +1,172 @@
+          npm ci
+          npm run build
+
+      - name: Upload bundle to Capgo
+        run: npx @capgo/cli@latest bundle upload --channel=production
+        env:
</file context>

npm ci
npm run build

- name: Upload bundle to Capgo

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Without --fail-on-incompatible, an incompatible bundle can still be uploaded and promoted to the production channel — per the Capgo CLI docs, this flag makes the upload exit non-zero instead of shipping when native packages don't match, so add it to the production upload command.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md, line 122:

<comment>Without `--fail-on-incompatible`, an incompatible bundle can still be uploaded and promoted to the `production` channel — per the Capgo CLI docs, this flag makes the upload exit non-zero instead of shipping when native packages don't match, so add it to the production upload command.</comment>

<file context>
@@ -0,0 +1,172 @@
+          npm ci
+          npm run build
+
+      - name: Upload bundle to Capgo
+        run: npx @capgo/cli@latest bundle upload --channel=production
+        env:
</file context>


## Step 1 — Create a Capgo API key

1. Open [console.capgo.app/apikeys/](https://console.capgo.app/apikeys/)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Consider linking to the RBAC API-key page (https://console.capgo.app/settings/organization/api-keys) and explicitly instructing users to create an app-scoped key with the App Uploader role. The current generic wording and link could lead users to create an overly-privileged, organization-wide key instead of a scoped one.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md, line 51:

<comment>Consider linking to the RBAC API-key page (https://console.capgo.app/settings/organization/api-keys) and explicitly instructing users to create an app-scoped key with the App Uploader role. The current generic wording and link could lead users to create an overly-privileged, organization-wide key instead of a scoped one.</comment>

<file context>
@@ -0,0 +1,172 @@
+
+## Step 1 — Create a Capgo API key
+
+1. Open [console.capgo.app/apikeys/](https://console.capgo.app/apikeys/)
+2. Create an API key with permission to upload bundles for your app
+3. Copy the key once. You will not see the full value again
</file context>


## Step 2 — Add `CAPGO_TOKEN` in GitHub (the only env step)

This is the step you send to Kuldeep and any client who owns the repo.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: Replace the personal name "Kuldeep" with a generic reference like "the repository owner" or "the client" — it's an internal name that shouldn't appear in a public blog post.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/src/content/blog/en/automate-capgo-live-updates-from-lovable-github-actions.md, line 59:

<comment>Replace the personal name "Kuldeep" with a generic reference like "the repository owner" or "the client" — it's an internal name that shouldn't appear in a public blog post.</comment>

<file context>
@@ -0,0 +1,172 @@
+
+## Step 2 — Add `CAPGO_TOKEN` in GitHub (the only env step)
+
+This is the step you send to Kuldeep and any client who owns the repo.
+
+1. Open the GitHub repository Lovable syncs to
</file context>

@riderx
riderx merged commit 7055b43 into main Aug 6, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant