Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ LINEAR_ACCESS_KEY=<your-key> ./linear-release sync

### AI-assisted setup

Use the Linear Release setup skill to generate CI configuration tailored to your project. It supports GitHub Actions, GitLab CI, CircleCI, and other platforms, and walks you through continuous vs. scheduled pipelines, monorepo path filtering, and more.
Use the Linear Release setup skill to generate CI configuration tailored to your project. It supports GitHub Actions, GitLab CI, CircleCI, RWX, and other platforms, and walks you through continuous vs. scheduled pipelines, monorepo path filtering, and more.

Copy the [SKILL.md](./skills/linear-release-setup/SKILL.md) into your project, or install it with [skills.sh](https://skills.sh):

Expand Down
24 changes: 24 additions & 0 deletions examples/rwx-continuous/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# RWX — Continuous Pipeline

Every deployment creates a completed release automatically.

## When to use

Use this when your team ships continuously — every push to main is a deploy, and each deploy should be tracked as its own release in Linear.

## How it works

On every push to `main`, the run downloads the Linear Release CLI in a separate task (so the install is cached and reused across runs) and then calls `sync`. This creates a new release from the commits since the last release and immediately marks it as complete.

## Setup

1. Create a release pipeline in Linear (Settings → Releases) and grab the access key.
2. Store the access key in an RWX vault: `rwx vaults secrets set linear-access-key=lin_pipeline_...`. The example references it as `${{ secrets.linear-access-key }}`.
3. Copy [`linear-release.yml`](linear-release.yml) into your `.rwx/` directory and edit the `repository` URL on the `code` task to match your repo.

## Customization

- **Branch name**: Change `event.git.branch == 'main'` in the trigger if your default branch is different.
- **Chaining after deploy**: Set `after: <your-deploy-task>` on the `linear-release` task so it only runs when deploy succeeds. The `linear-release-cli` install task runs in parallel with deploy regardless, so it doesn't slow the critical path.
- **ARM64 base images**: Swap `linear-release-linux-x64` for `linear-release-linux-arm64` in the install command.
- **Monorepo path filters**: Add `--include-paths` to the `sync` command to scope the release to specific directories.
47 changes: 47 additions & 0 deletions examples/rwx-continuous/linear-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Linear Release — RWX (Continuous Pipeline)
#
# Use when: Every deployment creates a completed release automatically.
# Trigger: On every push to the default branch.
# Customize: Repository URL, branch filter, path filters (--include-paths).
#
# Set the access key with: rwx vaults secrets set linear-access-key=lin_pipeline_...
#
# To chain after a deploy task in your existing pipeline, change the
# linear-release task to use `after: <your-deploy-task>` so it only runs
# after a successful deploy.

on:
github:
push:
if: ${{ event.git.branch == 'main' }}
init:
commit-sha: ${{ event.git.sha }}

base:
image: ubuntu:24.04
config: rwx/base 1.0.2

tasks:
- key: code
call: git/clone 2.0.7
with:
repository: https://github.com/YOUR-ORG/YOUR-REPO.git
ref: ${{ init.commit-sha }}
github-token: ${{ github.token }}
preserve-git-dir: true

- key: linear-release-cli
run: |
mkdir -p bin
curl -fsSL \
https://github.com/linear/linear-release/releases/latest/download/linear-release-linux-x64 \
-o bin/linear-release
chmod +x bin/linear-release
echo "$PWD/bin" > $RWX_ENV/PATH

- key: linear-release
use: [code, linear-release-cli]
cache: false
run: linear-release sync
env:
LINEAR_ACCESS_KEY: ${{ secrets.linear-access-key }}
51 changes: 51 additions & 0 deletions examples/rwx-scheduled/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# RWX — Scheduled Pipeline

Releases follow a branch cut model where changes collect over time and move through stages before shipping.

## When to use

Use this when your team cuts release branches for stabilization. Main collects changes into the current release, release branches sync with an explicit version and auto-promote on creation.

## How it works

- **Push to `main`**: Syncs issues to the current started release (no explicit version).
- **Push to `release/*`**: Derives the version from `event.git.branch` and syncs with that version. On branch creation (`event.github.push.created` is `true`), auto-promotes the release to "code freeze".
- **Dispatch triggers**: `linear-release-update` and `linear-release-complete` are dispatched manually for later stage transitions and final completion.

### Triggering manual actions

Use the [RWX CLI](https://www.rwx.com/docs/cli-reference/rwx-dispatch) to dispatch stage transitions and completion:

```bash
# Move release 1.2.0 to QA
rwx dispatch linear-release-update --param release-version=1.2.0 --param stage=qa

# Mark release 1.2.0 as complete
rwx dispatch linear-release-complete --param release-version=1.2.0
```

You can also dispatch from the [RWX UI](https://cloud.rwx.com/mint/deep_link/runs) or via the [Dispatches API](https://www.rwx.com/docs/api/dispatches).

## Setup

1. Create a release pipeline in Linear (Settings → Releases) and grab the access key.
2. Store the access key in an RWX vault: `rwx vaults secrets set linear-access-key=lin_pipeline_...`.
3. Copy [`linear-release.yml`](linear-release.yml) into your `.rwx/` directory and edit the `repository` URL on the `code` task to match your repo.
4. Rename the dispatch keys (`linear-release-update`, `linear-release-complete`) to be unique within your RWX organization — e.g. prefix them with your app name.

## Customization

- **Branch patterns**: Change `release/` in the `starts-with` filter and the `${BRANCH#release/}` version derivation to match your release branch convention.
- **Stage names**: Replace `code freeze` with whatever your first stage is called.
- **Version derivation**: The example strips `release/` from the branch name. Adjust if your branch naming differs.
- **Dispatch keys**: RWX dispatch keys must be unique across your entire organization — prefix them with your app or service name to avoid collisions.
- **ARM64 base images**: Swap `linear-release-linux-x64` for `linear-release-linux-arm64` in the install command.

## Monorepo note

To path-scope the main-branch sync without affecting release branches, split the run definition into two files in `.rwx/`:

1. **File 1 (main)**: Use the [`github/compare`](https://www.rwx.com/docs/packages/github/compare) package to skip when the relevant paths haven't changed.
2. **File 2 (release)**: Keep the `release/*` push trigger and dispatch triggers as-is.

Add `--include-paths` to all `sync` commands.
124 changes: 124 additions & 0 deletions examples/rwx-scheduled/linear-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Linear Release — RWX (Scheduled)
#
# Use when: Releases follow a branch cut model. Main collects changes into the
# current release, a release branch is cut for stabilization, and branch creation
# auto-promotes to "code freeze".
#
# Trigger: push to main (sync), push to release/* (sync + auto code freeze on
# creation), or `rwx dispatch linear-release-update`/`linear-release-complete`
# for later stages and final completion.
#
# Customize: Repository URL, branch patterns, stage names, version derivation,
# dispatch keys (RWX dispatch keys must be unique within your RWX organization,
# so prefix them — e.g. linear-release-update-<your-app>).
#
# Set the access key with: rwx vaults secrets set linear-access-key=lin_pipeline_...

on:
github:
push:
# Default branch: sync without --release-version (targets current started release)
- if: ${{ event.git.branch == 'main' }}
target: linear-release-sync-main
init:
commit-sha: ${{ event.git.sha }}

# Release branch: sync with explicit version + auto-promote on creation
- if: ${{ starts-with(event.git.branch, 'release/') }}
target: linear-release-sync-release
init:
commit-sha: ${{ event.git.sha }}
branch: ${{ event.git.branch }}
branch-created: ${{ event.github.push.created }}

dispatch:
# Move a release between stages (e.g. qa, in review, rc soak).
# Run with: rwx dispatch linear-release-update --param release-version=1.2.0 --param stage=qa
- key: linear-release-update
target: linear-release-update
params:
- key: release-version
name: Release version
description: The release version to update (e.g. 1.2.0).
required: true
- key: stage
name: Stage
description: The target deployment stage (e.g. qa, in review).
required: true
init:
commit-sha: ${{ event.git.sha }}
release-version: ${{ event.dispatch.params.release-version }}
stage: ${{ event.dispatch.params.stage }}

# Mark a release as complete.
# Run with: rwx dispatch linear-release-complete --param release-version=1.2.0
- key: linear-release-complete
target: linear-release-complete
params:
- key: release-version
name: Release version
description: The release version to complete (e.g. 1.2.0).
required: true
init:
commit-sha: ${{ event.git.sha }}
release-version: ${{ event.dispatch.params.release-version }}

base:
image: ubuntu:24.04
config: rwx/base 1.0.2

tasks:
- key: code
call: git/clone 2.0.7
with:
repository: https://github.com/YOUR-ORG/YOUR-REPO.git
ref: ${{ init.commit-sha }}
github-token: ${{ github.token }}
preserve-git-dir: true

- key: linear-release-cli
run: |
mkdir -p bin
curl -fsSL \
https://github.com/linear/linear-release/releases/latest/download/linear-release-linux-x64 \
-o bin/linear-release
chmod +x bin/linear-release
echo "$PWD/bin" > $RWX_ENV/PATH

- key: linear-release-sync-main
use: [code, linear-release-cli]
cache: false
run: linear-release sync
env:
LINEAR_ACCESS_KEY: ${{ secrets.linear-access-key }}

- key: linear-release-sync-release
use: [code, linear-release-cli]
cache: false
run: |
RELEASE_VERSION="${BRANCH#release/}"
linear-release sync --release-version="$RELEASE_VERSION"
if [ "$BRANCH_CREATED" = "true" ]; then
linear-release update --release-version="$RELEASE_VERSION" --stage="code freeze"
fi
env:
LINEAR_ACCESS_KEY: ${{ secrets.linear-access-key }}
BRANCH: ${{ init.branch }}
BRANCH_CREATED: ${{ init.branch-created }}

- key: linear-release-update
use: [code, linear-release-cli]
cache: false
run: linear-release update --release-version="${RELEASE_VERSION}" --stage="${STAGE}"
env:
LINEAR_ACCESS_KEY: ${{ secrets.linear-access-key }}
RELEASE_VERSION: ${{ init.release-version }}
STAGE: ${{ init.stage }}

- key: linear-release-complete
use: [code, linear-release-cli]
cache: false
run: linear-release complete --release-version="${RELEASE_VERSION}"
env:
LINEAR_ACCESS_KEY: ${{ secrets.linear-access-key }}
RELEASE_VERSION: ${{ init.release-version }}
7 changes: 5 additions & 2 deletions skills/linear-release-setup/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name: linear-release-setup
description: Generate CI/CD configuration for Linear Release. Use when setting up
release tracking, configuring CI pipelines for Linear, or integrating deployments
with Linear releases. Supports GitHub Actions, GitLab CI, CircleCI, and other platforms.
with Linear releases. Supports GitHub Actions, GitLab CI, CircleCI, RWX, and other platforms.
---

# Linear Release Setup
Expand All @@ -16,7 +16,7 @@ The [linear-release README](https://github.com/linear/linear-release/blob/main/R
Before generating config, confirm:

1. **Pipeline exists in Linear** — the user must have created a release pipeline in Linear first (Settings → Releases). Each pipeline has its own access key.
2. **Detect CI platform** — look for `.github/workflows/*.yml` (GitHub Actions), `.gitlab-ci.yml` (GitLab CI), `.circleci/config.yml` (CircleCI), or other CI config.
2. **Detect CI platform** — look for `.github/workflows/*.yml` (GitHub Actions), `.gitlab-ci.yml` (GitLab CI), `.circleci/config.yml` (CircleCI), `.rwx/*.yml` (RWX), or other CI config.
3. **Detect default branch** — check `git symbolic-ref refs/remotes/origin/HEAD` or the CI config. Don't assume `main`.

### Step 2: Map pipelines, then ask
Expand Down Expand Up @@ -55,6 +55,8 @@ Pick the matching example template, adapt it (branch patterns, stage names, path
| GitLab CI | Scheduled | [`gitlab-ci-scheduled/`](https://github.com/linear/linear-release/blob/main/examples/gitlab-ci-scheduled) |
| CircleCI | Continuous | [`circleci-continuous/`](https://github.com/linear/linear-release/blob/main/examples/circleci-continuous) |
| CircleCI | Scheduled | [`circleci-scheduled/`](https://github.com/linear/linear-release/blob/main/examples/circleci-scheduled) |
| RWX | Continuous | [`rwx-continuous/`](https://github.com/linear/linear-release/blob/main/examples/rwx-continuous) |
| RWX | Scheduled | [`rwx-scheduled/`](https://github.com/linear/linear-release/blob/main/examples/rwx-scheduled) |

Each scheduled example includes a **monorepo** note in the header explaining how to split workflows for path filtering per platform.

Expand All @@ -65,6 +67,7 @@ Tell the user to add the `LINEAR_ACCESS_KEY` secret to their CI environment:
- **GitHub Actions**: Repository Settings → Secrets and variables → Actions → New repository secret
- **GitLab CI**: Settings → CI/CD → Variables
- **CircleCI**: Project Settings → Environment Variables
- **RWX**: Store as a vault secret with `rwx vaults secrets set linear-access-key=...`, then reference as `${{ secrets.linear-access-key }}` in the run definition

The access key is created in Linear from the pipeline's settings page. Each pipeline has its own access key.

Expand Down
6 changes: 6 additions & 0 deletions src/ci-env.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ describe("detectCIEnvironment", () => {
delete process.env.TF_BUILD;
delete process.env.BUILDKITE;
delete process.env.TEAMCITY_VERSION;
delete process.env.RWX;
delete process.env.CI;
});

Expand Down Expand Up @@ -61,6 +62,11 @@ describe("detectCIEnvironment", () => {
expect(detectCIEnvironment()).toEqual({ name: "teamcity" });
});

it("detects RWX", () => {
process.env.RWX = "true";
expect(detectCIEnvironment()).toEqual({ name: "rwx" });
});

it("detects generic CI", () => {
process.env.CI = "true";
expect(detectCIEnvironment()).toEqual({ name: "ci" });
Expand Down
3 changes: 3 additions & 0 deletions src/ci-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export function detectCIEnvironment(): CIEnvironment | null {
if (process.env.TEAMCITY_VERSION) {
return { name: "teamcity" };
}
if (process.env.RWX === "true") {
return { name: "rwx" };
}
if (process.env.CI === "true") {
return { name: "ci" };
}
Expand Down
7 changes: 7 additions & 0 deletions src/user-agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe("buildUserAgent", () => {
process.env = { ...originalEnv };
// Ensure our CI environment is not detected
delete process.env.GITHUB_ACTIONS;
delete process.env.RWX;
delete process.env.CI;
});

Expand All @@ -36,4 +37,10 @@ describe("buildUserAgent", () => {
const userAgent = buildUserAgent();
expect(userAgent).toBe("linear-release/1.2.3 (gitlab-ci)");
});

it("builds user agent for RWX", () => {
process.env.RWX = "true";
const userAgent = buildUserAgent();
expect(userAgent).toBe("linear-release/1.2.3 (rwx)");
});
});
Loading