diff --git a/apps/docs/src/content/docs/docs/cli/reference/bundle.mdx b/apps/docs/src/content/docs/docs/cli/reference/bundle.mdx
index 1978656a9..9e82f0e2e 100644
--- a/apps/docs/src/content/docs/docs/cli/reference/bundle.mdx
+++ b/apps/docs/src/content/docs/docs/cli/reference/bundle.mdx
@@ -49,6 +49,7 @@ With `--auto-bump ai`, Capgo Cloudflare Workers AI compares local bundle files t
| **-p** | string | Path of the folder to upload, if not provided it will use the webDir set in capacitor.config |
| **-c** | string | Channel to link to. Use commas for multiple channels, for example production,beta |
| **--rollout** | string | Set the uploaded bundle as this channel's rollout target at a percentage from 0 to 100 |
+| **--stable** | boolean | On a rollout-configured channel, replace the stable fallback instead of setting the rollout target |
| **--rollout-percentage-bps** | string | Set the uploaded bundle rollout percentage in basis points from 0 to 10000 |
| **--rollout-cache-ttl-seconds** | string | Cloudflare rollout decision cache TTL in seconds |
| **-e** | string | Link to external URL instead of upload to Capgo Cloud |
diff --git a/apps/docs/src/content/docs/docs/live-updates/progressive-rollouts.mdx b/apps/docs/src/content/docs/docs/live-updates/progressive-rollouts.mdx
index 04176e278..509709161 100644
--- a/apps/docs/src/content/docs/docs/live-updates/progressive-rollouts.mdx
+++ b/apps/docs/src/content/docs/docs/live-updates/progressive-rollouts.mdx
@@ -7,98 +7,222 @@ sidebar:
import { Aside, Steps } from '@astrojs/starlight/components';
-A progressive rollout keeps a channel's **stable bundle** in place while delivering a separate **rollout target** to a controlled subset of devices. It is useful when you want real production validation without switching the whole channel at once.
+A progressive rollout keeps a channel's **stable fallback** in place while delivering a separate **rollout target** to a controlled subset of devices. Most production users stay on stable. Only the rollout cohort receives the candidate until you **Complete rollout** or **Rollback rollout**.
-Configure the rollout target, percentage, cache duration, and auto-pause policy from a channel's Information tab.
+The **Progressive rollout** section on a channel's Information tab shows **Rollout target** and **Stable fallback**, percentage controls, and rollout actions.
-## How progressive rollouts work
+## How progressive rollouts work end to end
-Each channel can have two bundle links:
+Each channel can have two bundle links at the same time:
-- **Stable bundle** — the normal bundle assigned to the channel.
-- **Rollout target** — the candidate bundle that is released gradually.
+| Console label | API / CLI | Who receives it |
+| --- | --- | --- |
+| **Stable fallback** | Stable bundle (`version` on [Channels POST](/docs/public-api/channels/)) | Every device on the channel that is **not** in the rollout cohort. |
+| **Rollout target** | Rollout target (`rolloutVersion` / `rollout_version`) | Only devices Capgo randomly selects into the rollout cohort while the rollout is **enabled** and not paused. |
-A rollout needs a stable bundle. In the dashboard, assign the stable bundle before choosing the target; with the Public API, an existing channel can supply `version` and `rolloutVersion` in the same POST request. Capgo makes a random decision for each eligible device, then caches it using a hash of the device ID and the rollout ID. That makes the cohort sticky for the configured cache duration instead of changing on every update check.
+### What happens on each update check
+
+1. The device asks Capgo for an update on its channel.
+2. If the rollout is **disabled**, or there is no target, Capgo serves the **stable fallback**.
+3. If the rollout is **enabled** and **paused**, devices already in the cohort keep the **rollout target**; everyone else receives **stable fallback**.
+4. If the rollout is **enabled** and not paused, Capgo checks whether this device is in the rollout cohort:
+ - **In the cohort** → rollout target.
+ - **Not in the cohort** → stable fallback.
+
+Capgo makes a random decision for each eligible device, then caches it using a hash of the device ID and the rollout ID. That makes the cohort **sticky** for the configured cache duration instead of re-rolling on every update check.
+### Enable a rollout
+
+A rollout needs a stable fallback first. In the dashboard, assign the stable bundle before choosing the rollout target. With the [Public Channels API](/docs/public-api/channels/), an existing channel can supply `version` and `rolloutVersion` in the same POST request.
+
+To start delivery:
+
+1. Set the **Rollout target** (or upload/assign a bundle — see [Upload and assign bundles](#upload-and-assign-bundles)).
+2. Choose a **percentage** and click **Apply percentage**.
+3. Click **Enable rollout**.
+
+Until you enable the rollout, configuring a target and percentage does not change what devices receive.
+
### Percentage changes
-Capgo preserves existing decisions when it can:
+In the console, edit the percentage and click **Apply percentage** to save it. Capgo preserves existing decisions when it can:
- **Increase a percentage** — devices already selected stay selected; only a random subset of previously unselected devices is added.
-- **Decrease a percentage** — a random subset of the selected cohort returns to the stable bundle.
+- **Decrease a percentage** — a random subset of the selected cohort returns to the stable fallback.
- **Set the percentage to 0%** — no new devices are selected. Devices already running the enabled rollout target continue to receive that target until you disable, roll back, or change the target.
-- **Pause** — stops new devices from entering while devices already running the enabled target remain on it.
-- **Disable** — stops resolving the rollout target; update checks use the stable bundle.
+- **Pause rollout** — stops new devices from entering while devices already running the enabled target remain on it.
+- **Disable rollout** — stops resolving the rollout target; update checks use the stable fallback.
-## Run a rollout
+## Upload and assign bundles
-
+On channels **with** progressive rollout configured, new uploads and bundle links go to the **rollout target** by default. **Stable fallback** stays unchanged unless you explicitly choose to replace it.
-1. Upload and assign a tested bundle as the channel's stable bundle.
-2. Upload the candidate bundle without replacing the stable bundle.
-3. Open the channel's **Information** tab, set the candidate as the **Target**, choose a small percentage, and enable the rollout.
-4. Monitor the rollout in [Observe](/docs/webapp/observe/), [Log Insights](/docs/webapp/log-insights/), and the channel history. Increase the percentage only after the cohort is healthy.
-5. When it is ready, promote the target to stable. If it is not healthy, pause or roll it back.
+
+> New uploads and bundle links on this channel go to the rollout target by default. Stable stays unchanged unless you choose to replace it.
+
+Choose where the bundle should land:
+
+| Console choice | When to use |
+| --- | --- |
+| **Auto (recommended)** | Default. Same as **Rollout target** when progressive rollout is configured; same as **Replace stable** when it is not. Matches API `target: "auto"` and CLI uploads without `--stable` or `--rollout`. |
+| **Rollout target** | Ship a candidate; stable fallback stays as-is. |
+| **Replace stable** | Intentionally replace production stable (escape hatch). |
+
+The hint **Auto uses rollout when progressive is configured** applies to API `target: "auto"` and to CLI uploads that do not pass `--stable` or `--rollout`.
+
+
+### Default upload routing
+
+| Channel state | Default upload / assign | Stable fallback | Rollout target |
+| --- | --- | --- | --- |
+| **No** progressive rollout configured | Lands on the channel | **Set to the upload** | N/A |
+| **Progressive rollout configured** | Lands on the channel | **Unchanged** | **Set to the upload** |
+
+### CLI and API paths
+
+| How you ship the bundle | Stable fallback | Rollout target | Notes |
+| --- | --- | --- | --- |
+| `bundle upload --channel ` (no extra flags) | Unchanged on rollout channels; **set** when no rollout | **Set** on rollout channels | Matches console default. |
+| `bundle upload --channel --rollout ` | Unchanged | **Set**; rollout **enabled** at that percentage | One-step upload + enable. |
+| `bundle upload --channel --stable` | **Replaced** | Unchanged | Escape hatch — intentional stable replace. |
+| `bundle upload` with **no** `--channel` | Unchanged | Unchanged | Register only; link with `channel set` or API. |
+| Console link dialog → **Auto (recommended)** | Unchanged on rollout channels | **Set** on rollout channels | Default choice. |
+| Console link dialog → **Rollout target** | Unchanged | **Set** | Explicit rollout target. |
+| Console link dialog → **Replace stable** | **Replaced** | Unchanged | Escape hatch. |
+| [PUT `/bundle/`](/docs/public-api/bundles/) with `target: "auto"` | Unchanged on rollout channels | **Set** on rollout channels | API default. |
+| [PUT `/bundle/`](/docs/public-api/bundles/) with `target: "rollout"` | Unchanged | **Set** | Explicit rollout target. |
+| [PUT `/bundle/`](/docs/public-api/bundles/) with `target: "stable"` | **Replaced** | Unchanged | Escape hatch. |
+| `channel set --bundle ` | **Replaced** | Unchanged | Explicit stable override. |
+| `channel set --rollout-bundle ` (+ `--rollout-percentage`, `--rollout-enable`) | Unchanged | **Set or changed** | Configure target without uploading. |
+| [Channels POST](/docs/public-api/channels/) `version` | **Replaced** when set | Unchanged | Explicit stable override. |
+| [Channels POST](/docs/public-api/channels/) `rolloutVersion` | Unchanged | **Set or changed** when set | Programmatic target assignment. |
+
+### CLI examples
+
+Default upload on a rollout-configured channel (rollout target; stable unchanged):
+
+```bash
+bunx @capgo/cli@latest bundle upload com.example.app \
+ --path ./dist \
+ --channel production
+```
+
+Upload, set rollout target, and enable at 5%:
+
+```bash
+bunx @capgo/cli@latest bundle upload com.example.app \
+ --path ./dist \
+ --channel production \
+ --rollout 5
+```
-### CLI example
+Replace stable explicitly:
+
+```bash
+bunx @capgo/cli@latest bundle upload com.example.app \
+ --path ./dist \
+ --channel production \
+ --stable
+```
-Start a rollout for bundle `1.3.0` at 5%:
+Upload without linking, then configure rollout:
```bash
-npx @capgo/cli@latest channel set production com.example.app \
+bunx @capgo/cli@latest bundle upload com.example.app --path ./dist -b 1.3.0
+
+bunx @capgo/cli@latest channel set production com.example.app \
--rollout-bundle 1.3.0 \
--rollout-percentage 5 \
--rollout-enable
```
-Increase it without reseating devices already selected:
+API assign with explicit target (`auto` | `stable` | `rollout`):
```bash
-npx @capgo/cli@latest channel set production com.example.app \
- --rollout-percentage 25
+curl -X PUT \
+ -H "authorization: your-api-key" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "app_id": "com.example.app",
+ "version_id": 456,
+ "channel_id": 789,
+ "target": "rollout"
+ }' \
+ https://api.capgo.app/bundle/
```
-The bundle upload command can set its upload as a rollout target in one step when the channel already has a stable bundle:
+Increase exposure without reseating devices already selected:
```bash
-npx @capgo/cli@latest bundle upload com.example.app \
- --path ./dist \
- --channel production \
- --rollout 5
+bunx @capgo/cli@latest channel set production com.example.app \
+ --rollout-percentage 25
```
-See the complete [channel CLI reference](/docs/cli/reference/channel/) and [bundle CLI reference](/docs/cli/reference/bundle/) for every rollout option.
+Use `--rollout-percentage-bps` when you need finer steps than whole percents (for example `50` = 0.5%).
-## Pause, roll back, or promote
+See the [channel CLI reference](/docs/cli/reference/channel/) and [bundle CLI reference](/docs/cli/reference/bundle/) for every rollout option. Console UX ships in [capgo.app#3313](https://github.com/Cap-go/capgo.app/pull/3313).
-| Action | What it does |
-| --- | --- |
-| **Pause** | Stops new devices from entering the rollout. Devices already on the enabled target stay there. |
-| **Resume** | Allows new eligible devices to enter again. |
-| **Disable** | Stops resolving the rollout target and uses the stable bundle. |
-| **Roll back** | Clears the target, disables the rollout, resets the percentage to 0%, and returns devices to stable. |
-| **Promote** | Makes the rollout target the stable bundle, then clears rollout state. |
+## Progressive rollout section (dashboard)
+
+Open an app → **Channels** → choose the channel → **Information** → **Progressive rollout**.
-Use one terminal action at a time:
+The section shows **Rollout target** and **Stable fallback** for the two bundle links. Use **Change rollout target** to pick a different candidate bundle. After editing the percentage, click **Apply percentage** to save it.
+
+| Control | What it does | Devices already in the cohort | New eligible devices |
+| --- | --- | --- | --- |
+| **Change rollout target** | Sets `rolloutVersion`. Changing it starts a new rollout ID and a new cohort. Uploading or assigning a bundle to the channel (default) also sets the rollout target. | Keep the previous target until they check in again and match the new decision rules. | Selected by the percentage against the new target. |
+| **Apply percentage** | Saves the percentage you entered. | Preserved when you increase; a random subset may leave when you decrease. | Added or excluded according to the percentage rules above. |
+| **Enable rollout** | Turns on rollout delivery for the configured target and percentage. | No immediate change until the next update check. | May be selected on the next check. |
+| **Pause rollout** | Stops new devices from entering. | **Stay on the rollout target.** | **Receive stable fallback** on the next check. |
+| **Resume rollout** | Clears the pause. | Unchanged. | May be selected again on the next check. |
+| **Disable rollout** | Stops resolving the rollout target; Capgo uses stable fallback for everyone. | **Move to stable fallback** on the next check. | **Receive stable fallback.** |
+| **Complete rollout** (API: `promoteToStable`; CLI: `--rollout-promote`) | Makes the rollout target the new stable fallback, then clears rollout state. Confirmed by **Complete progressive rollout?** — the target becomes stable for everyone, the previous stable fallback is replaced, and the percentage resets. | **Become stable fallback** for everyone. | **Receive the completed bundle** as stable. |
+| **Rollback rollout** (API: `rollback`; CLI: `--rollout-rollback`) | Clears the target, disables the rollout, resets percentage to 0%, and returns devices to stable fallback. | **Return to stable fallback** on the next check. | **Receive stable fallback.** |
+
+Use one terminal action at a time (**Complete rollout** and **Rollback rollout** cannot be combined in a single API call):
```bash
# Stop adding devices while you investigate
-npx @capgo/cli@latest channel set production com.example.app --rollout-pause
+bunx @capgo/cli@latest channel set production com.example.app --rollout-pause
-# Make the candidate stable for everyone
-npx @capgo/cli@latest channel set production com.example.app --rollout-promote
+# Allow new devices to enter again
+bunx @capgo/cli@latest channel set production com.example.app --rollout-resume
-# Discard the candidate and return to stable
-npx @capgo/cli@latest channel set production com.example.app --rollout-rollback
+# Complete rollout — candidate becomes stable for everyone
+bunx @capgo/cli@latest channel set production com.example.app --rollout-promote
+
+# Rollback rollout — discard candidate and return to stable
+bunx @capgo/cli@latest channel set production com.example.app --rollout-rollback
```
-Keep the target bundle available until you promote it or roll it back. Bundles linked as a stable bundle or rollout target are protected from deletion.
+Keep the rollout target bundle available until you **Complete rollout** or **Rollback rollout**. Bundles linked as stable fallback or rollout target are protected from deletion.
+
+## Safe workflow
+
+Progressive rollout is designed so you can validate in production without switching the whole channel at once. A practical sequence:
+
+
+
+1. **Confirm stable fallback is healthy.** It should be the version you are willing to keep serving to most users.
+2. **Upload the candidate** to the rollout-configured channel (`bundle upload --channel ` lands on **Rollout target** and leaves **Stable fallback** alone) or use `--rollout ` to enable exposure in the same step.
+3. **Start small** — 1–5% is enough for first signal. Click **Apply percentage**, then **Enable rollout**. Turn on auto-pause if you want Capgo to stop exposure when failure rates spike.
+4. **Monitor before you widen.** Use [Observe](/docs/webapp/observe/) for version health, [Log Insights](/docs/webapp/log-insights/) for error patterns, and the channel **History** tab for configuration changes. Compare rollout target with stable fallback; do not raise the percentage until the cohort looks healthy.
+5. **Increase gradually** — edit the percentage, click **Apply percentage**, and step up (for example 5% → 25% → 50% → 100%). Capgo keeps devices already in the cohort selected when you increase.
+6. **Complete rollout when confident** — confirm **Complete progressive rollout?** so the candidate becomes stable fallback for everyone.
+7. **If something is wrong, Pause rollout first** — stops new exposure while you investigate. Devices already on the bad build stay on it until you **Rollback rollout** or **Disable rollout**.
+
+
+
+
+
## Auto-pause policy
Capgo can evaluate an enabled rollout every five minutes and act when its failure signal crosses your threshold. It measures installs and failures for the rollout target in the selected channel and time window, then uses a Wilson confidence lower bound rather than the raw failure rate alone.
@@ -112,10 +236,10 @@ Auto-pause is disabled by default. Configure these fields in the channel's **Inf
- **Cooldown** in minutes (default: 60), which prevents repeated actions.
- **Action**: `pause`, `rollback`, or `notify`.
-`pause` stops new rollout exposure, `rollback` clears the target and returns to stable, and `notify` sends a rollout alert without changing rollout delivery.
+`pause` stops new rollout exposure, `rollback` clears the target and returns to stable fallback, and `notify` sends a rollout alert without changing rollout delivery.
```bash
-npx @capgo/cli@latest channel set production com.example.app \
+bunx @capgo/cli@latest channel set production com.example.app \
--auto-pause-enabled \
--auto-pause-failure-rate-bps 500 \
--auto-pause-window-minutes 60 \
@@ -126,13 +250,10 @@ npx @capgo/cli@latest channel set production com.example.app \
--auto-pause-cooldown-minutes 120
```
-## API and dashboard controls
-
-You can manage the same feature through the [Public Channels API](/docs/public-api/channels/) or the dashboard:
+## API and dashboard entry points
-1. Open an app, then **Channels**.
-2. Choose the channel.
-3. Open **Information**.
-4. Use the **Progressive rollout** section to choose the target, percentage, cache duration, and auto-pause policy.
+- **Dashboard:** app → **Channels** → channel → **Information** → **Progressive rollout**
+- **Channels API:** [Public Channels API](/docs/public-api/channels/) for rollout fields, promote, and rollback
+- **Bundle assign API:** [PUT `/bundle/`](/docs/public-api/bundles/) with `target`: `auto`, `stable`, or `rollout`
For general channel routing and device precedence, see [Channels](/docs/live-updates/channels/). For emergency bundle recovery outside a progressive rollout, see [Rolling back a live update](/docs/live-updates/rollbacks/).
diff --git a/apps/docs/src/content/docs/docs/public-api/bundles.mdx b/apps/docs/src/content/docs/docs/public-api/bundles.mdx
index b56257e55..5d8d75684 100644
--- a/apps/docs/src/content/docs/docs/public-api/bundles.mdx
+++ b/apps/docs/src/content/docs/docs/public-api/bundles.mdx
@@ -557,9 +557,16 @@ interface SetChannelBody {
app_id: string
version_id: number // bundle (version) id
channel_id: number
+ target?: "auto" | "stable" | "rollout" // default: auto
}
```
+`target` controls where the bundle is linked when the channel has progressive rollout configured:
+
+- `auto` — rollout target when progressive rollout is configured; stable otherwise (default).
+- `rollout` — always set the rollout target; stable fallback unchanged.
+- `stable` — replace stable fallback (escape hatch).
+
#### Example Request
```bash
diff --git a/apps/docs/src/content/docs/docs/webapp/bundles.mdx b/apps/docs/src/content/docs/docs/webapp/bundles.mdx
index 381bbe2e6..60a6907fe 100644
--- a/apps/docs/src/content/docs/docs/webapp/bundles.mdx
+++ b/apps/docs/src/content/docs/docs/webapp/bundles.mdx
@@ -95,7 +95,7 @@ The modal provides:
3. **Available channels**: List of all channels with their details (ID, visibility, platforms, creation date). Channels marked as "current" indicate where this bundle is already linked. Public channels show a link icon.
4. **Set bundle to channel**: Confirms the selection and assigns this bundle to the selected channel
-**Set bundle to channel:** Assigns this bundle as the active version for a chosen channel. Devices subscribed to that channel will then receive this bundle.
+**Set bundle to channel:** Assigns this bundle to a chosen channel. On rollout-configured channels, the dialog title is **This channel uses progressive rollout** and you choose **Auto (recommended)**, **Rollout target**, or **Replace stable**. The banner explains that new uploads and links go to the rollout target unless you replace stable. See [Progressive rollouts](/docs/live-updates/progressive-rollouts/#upload-and-assign-bundles).
To open a channel's dedicated page, click on the channel name link in the Channel row.
diff --git a/apps/docs/src/content/docs/docs/webapp/channels.mdx b/apps/docs/src/content/docs/docs/webapp/channels.mdx
index 963cbee2b..9a26bc4d2 100644
--- a/apps/docs/src/content/docs/docs/webapp/channels.mdx
+++ b/apps/docs/src/content/docs/docs/webapp/channels.mdx
@@ -184,11 +184,11 @@ Valid values: `all`, `zip`, `delta`, `zip_from_builtin`, `delta_from_builtin`.
## Progressive rollouts
-The channel **Information** tab includes a **Progressive rollout** section. Set a target bundle, choose the percentage and decision-cache duration, then enable the rollout. From the same section you can pause or resume exposure, promote the target to stable, roll it back, or configure an automatic pause, rollback, or notification when failure signals cross a threshold.
+The channel **Information** tab includes a **Progressive rollout** section with **Rollout target** and **Stable fallback**, **Change rollout target**, **Apply percentage**, and actions **Complete rollout**, **Rollback rollout**, **Enable rollout**, **Disable rollout**, **Pause rollout**, and **Resume rollout**.
Progressive rollout controls appear at the top of the channel Information tab.
-Read [Progressive rollouts](/docs/live-updates/progressive-rollouts/) before enabling one. It explains sticky cohorts, safe percentage changes, and auto-pause behavior.
+Read [Progressive rollouts](/docs/live-updates/progressive-rollouts/) before enabling one. That guide covers upload routing (**Auto (recommended)** / **Rollout target** / **Replace stable**; CLI `--stable` / API `target: "stable"` to override), the **This channel uses progressive rollout** bundle-link dialog, and a safe workflow with [Observe](/docs/webapp/observe/) and [Log Insights](/docs/webapp/log-insights/).
## Keep going from Channels