> ## Documentation Index
> Fetch the complete documentation index at: https://docs.revyl.com/llms.txt
> Use this file to discover all available pages before exploring further.

# GitHub

> Preview builds, proof checks, and workflows on every pull request

The Revyl GitHub App connects your repositories to Revyl. Once installed, it
can create pull-request preview builds, run proof checks, and run saved
workflows against those builds.

## Connect GitHub

GitHub installation is repository-wide. Configuration belongs to an individual
Revyl project root within that repository.

```bash theme={null}
revyl github connect                # Install or verify repository access
revyl github status                 # Show the current installation status
revyl github setup                  # Configure the selected project root
```

`revyl github connect` only installs or verifies access. It never changes or
publishes `.revyl/config.yaml`. From a coding agent, run
`revyl github connect --no-open --no-wait --json`: it prints the install link as
`install_url` and returns at once, so the agent can hand that link to you.
Install from it with your own GitHub login and check with `revyl github status`.

You can also create and configure projects entirely in the dashboard. Open
**Settings → Integrations → GitHub**, select a repository, and configure its
first project. The first project defaults to the repository root (`.`). Use
**Add project** and enter an explicit repository-relative root such as
`apps/mobile` for each additional project.

The dashboard edits the same complete configuration as the CLI: session setup,
build framework and profiles, iOS and Android recipes, PR triggers, proof,
workflows, and strict checks. Apps, workflows, Cursor models, and build images
are selected by name; their internal IDs are never shown. Saving a new project
publishes it in manual authority. Saving an existing manual project replaces
the complete aggregate with conflict protection. Git-owned projects are
read-only in the dashboard.

For Expo projects, a newly enabled dashboard recipe defaults to native
`expo prebuild` plus Xcode or Gradle, which does not require Expo
authentication and never adds `EXPO_TOKEN`. Customers who manually author an
EAS build command for unattended remote execution must also reference an
encrypted `EXPO_TOKEN`.
Required secrets can be created and selected inline without putting their
values in project configuration. New projects enable proof of changes with the
Revyl harness by default.

`revyl github setup` starts from the nearest `.revyl/config.yaml`, guides
you through a Revyl-managed profile or finished-artifact CI-upload policy,
writes the resulting `pr_review` policy into that file, and validates it. Under
manual authority, setup confirms publication and publishes the selected project
root's complete server-backed configuration. Under Git authority, setup instead
prepares the designated file for commit without publishing. For a managed
profile, each recipe used by PR automation must reference an existing active app
or an app selected or created during setup. Unrelated profiles are left alone.
New projects enable proof of changes with the Revyl harness by default.

Authenticated interactive `revyl init` can continue into this same flow after
it writes the local config. Non-interactive initialization stops after
local config creation and never enters GitHub setup.

In a monorepo, run setup from each project you intend to publish:

```bash theme={null}
revyl -C apps/consumer github setup
revyl -C apps/partner github setup
```

There is no repository-wide project selector. The nearest config determines
the project root, and GitHub setup never overrides that repository identity
with a command-line repository flag.

## Configuration publication

Under manual authority, the dashboard, GitHub setup, and `revyl config push`
publish the same typed configuration meaning. Under Git authority, GitHub setup
prepares the designated file for commit instead. Revyl does not store the raw
YAML document. It atomically materializes:

* `project.id`
* `session`, including the idle timeout, before-script declaration, and
  auth-bypass references
* `build.framework` and every normalized named profile, including each iOS or
  Android recipe, commands, output, optional app association, caches,
  non-secret environment values, secret references, image, and timeout
* optional `pr_review`, including triggers, managed-profile or CI-upload app
  selection, proof policy, workflow IDs, and strict build-check behavior

Formatting, comments, `.revyl/tests`, source contents, repository URLs,
invocation directories, transient profile selection, and secret values are not
stored in the project configuration aggregate.

Publication verifies the authenticated GitHub repository and exact
repository-relative project root. It uses the server configuration hash read
immediately before the write, so a concurrent edit fails instead of silently
overwriting newer state. `revyl config push` remains the explicit manual
publication and recovery path. It requires GitHub to be connected and never
scaffolds missing local configuration. If GitHub is disconnected, run
`revyl auth status` to verify the active account and organization, then run
`revyl github connect`. If it is connected but the repository is unavailable,
grant that repository to the existing Revyl GitHub App, run
`revyl github status`, and retry `revyl config push`. Local identity or config
shape errors direct you to the applicable exact recovery: migrate legacy input,
pull a registered project identity, or locate and repair invalid canonical YAML
before running `config validate`. If the checkout has no supported GitHub
origin, follow the emitted project-root-scoped `git remote add origin` or
`git remote set-url origin` command, then retry publication.

To adopt a dashboard-created project in a checkout that has no local config,
run `revyl config pull` from that project root or one of its subdirectories.
The CLI verifies the GitHub repository, selects the nearest configured ancestor
root, verifies the configuration hash, and atomically creates that root's
`.revyl/config.yaml`. It does not overwrite an existing, invalid, or legacy
file. After that, `config push` and `config pull` provide ordinary manual
two-way synchronization; they never merge concurrent edits.

## Default-branch authority

Default-branch configuration reconciles registered projects and can bootstrap a
new Git-managed project from a newly added canonical config. The config must have
a valid, untaken `project.id`, an unclaimed project root and config path, and
valid owned references. Creation happens only from the actual default-branch
commit, never from an unpublished pull-request head. The config path defines the
project root: `.revyl/config.yaml` selects the repository root, and
`apps/consumer/.revyl/config.yaml` selects `apps/consumer`. Revyl rejects invalid
or conflicting candidates without creating or replacing a project, and repeated
delivery of the same push is idempotent.

Setup or manual publication can also designate an existing project's exact
config path before that file reaches the default branch. Recording the path
does not itself make Git authoritative.

After Revyl successfully validates and reconciles that designated file from
the repository's default branch, the project enters `git_default_branch`
authority. Each project root is reconciled independently, so one repository
can contain several Revyl projects without one config replacing another.
Fetch errors, invalid configuration, identity conflicts, or rejected references
do not change an existing project's authority or active aggregate.

## Pull-request configuration lifecycle

Revyl evaluates project configuration before selecting or running review work.
The lifecycle follows four rules:

* If a Revyl configuration is managed in your Git repo, pull requests use the
  Git configuration from the exact pull request head.
* Invalid or unavailable candidates fail the **Revyl configuration** check when
  review automation is enabled or the pull request adds an unregistered project
  configuration. Revyl never falls back to another configuration.
* Deleting a Git-managed config switches the project to manual management while
  retaining that config.
* Manual projects without a Git-managed configuration file leverage their frozen
  server configuration.

While a project is Git-owned, manual UI, ordinary API, and ordinary
`revyl config push` writes are rejected. `revyl github setup` prepares the
designated file for commit without publishing. Edit the designated file, run
`revyl config validate`, and commit it instead. For a deliberate one-time
exception, `revyl config push --force` replaces the current aggregate without
changing Git ownership or its reconciliation position; a later newer
default-branch configuration can replace it. Under manual authority, the flag
is a no-op. `revyl config pull` remains available for recovery.

Removing the designated file from the default branch returns only that project
to `manual` authority. Revyl retains the last accepted aggregate so a later
manual update has a safe base. Removing only the `pr_review` group keeps the
file authoritative and removes the current server review policy when that
configuration is successfully reconciled.

Project roots are immutable. A manual project can be deleted from the GitHub
integration in the Revyl web app and recreated at a different root. Deletion
preserves the repository connection, report destination, historical results,
and already-frozen review and build work, which may finish. Pending Cursor proof
that has not launched fails closed because deletion removes its live
authorization. A Git-owned project must first return to manual authority through
the designated-file flow above; removing the file alone does not delete it.
After deleting the last project, the repository shows **No projects configured**
and **Add project**. There is no CLI delete command.

## Configure PR previews

Revyl-managed PR builds select one published profile. The profile owns its iOS
and Android recipes; the PR policy does not duplicate build commands or project
roots.

```yaml theme={null}
project:
  id: "00000000-0000-4000-8000-000000000000"

build:
  framework: expo
  profiles:
    pull-request:
      ios:
        app_id: "00000000-0000-4000-8000-000000000001"
        setup_commands:
          - bun install
          - bunx expo prebuild --platform ios
          - cd ios && pod install
        build_commands:
          - xcodebuild -workspace ios/App.xcworkspace -scheme App -configuration Debug -sdk iphonesimulator -derivedDataPath build
        output_path: "build/Build/Products/Debug-iphonesimulator/*.app"
      android:
        app_id: "00000000-0000-4000-8000-000000000002"
        setup_commands:
          - bun install
          - bunx expo prebuild --platform android
        build_commands:
          - cd android && ./gradlew assembleDebug
        output_path: "android/app/build/outputs/apk/debug/*.apk"

pr_review:
  enabled: true
  review_triggers:
    paths: ["apps/mobile/**"]
    labels: []
    drafts: false
  build:
    kind: revyl
    profile: pull-request
  proof_of_changes:
    enabled: true
    harness:
      kind: revyl
  workflow_ids: []
  strict_ci_check:
    build: true
```

`build.framework` applies to every profile in the project. Build-level `env`,
`secrets`, and `caches` are inherited by each recipe; recipe values can add or
override them. Secret values never belong in the config.

<Note>
  Explicit `review_triggers.paths` globs are matched against the paths GitHub
  reports, so they are repository-root-relative. This lets a project deliberately
  include shared code outside its own root. When the list is empty, Revyl defaults
  a nested project to `<project-root>/**`; a project rooted at `apps/mobile`
  therefore reviews `apps/mobile/**` changes without requiring that authored
  value. Only the repository-root project defaults to every changed path.
</Note>

### Review policy fields

| Field | Description |
| - | - |
| `enabled` | Enables the project's PR automation policy. |
| `review_triggers.paths` | Optional repository-root-relative globs. Explicit values replace the project-root default and can include shared paths. Empty defaults a nested project to `<project-root>/**`; the repository-root project remains repo-wide. |
| `review_triggers.labels` | Includes or excludes reviews based on trimmed, non-empty, unique label names. Prefix an exclusion with `!`. |
| `review_triggers.drafts` | When `true`, draft pull requests are eligible. |
| `build` | Selects a Revyl-managed profile or declares finished-artifact CI uploads. |
| `proof_of_changes` | Configures proof execution and its requested harness. |
| `workflow_ids` | Saved Revyl workflows to run against compatible project build platforms. Mixed-platform workflows execute only matching-platform tests for each target. |
| `strict_ci_check.build` | When `true`, a failed managed build produces a failing build check. |

## Proof of changes

New project setups enable proof checks with the Revyl harness. Keep them on when
you want Revyl to exercise the pull-request build and report whether the change
behaves as expected:

```yaml theme={null}
pr_review:
  build:
    kind: revyl
    profile: pull-request
  proof_of_changes:
    enabled: true
    always_verify:
      - "App launches to the home screen"
      - "Login with email succeeds"
    system_prompt: "Prefer a short launch-screen smoke proof."
```

The pull-request diff supplies the authoritative change-specific proof intent.
With the Revyl harness, the author-written portion of the pull-request
description is also available as supporting context for the intended behavior
and proposed test plan.
`always_verify` is for invariants that must hold on every run, not a substitute
for inspecting the change.

The result is a write-up the agent authors: what it exercised and what it saw,
an evidence-grounded confidence score from 1/5 to 5/5, captioned screenshots
of the moments that matter, and a link to the full session recording anyone
can open without signing in. The findings block links confirmed problems to
their screenshot-grounded Atlas threads. When the run finds no defects, it
says so and still links to Atlas scoped to the exercised build. Each configured
build target gets its own proof run and a headed write-up on the same Revyl block
as preview builds. A later push replaces the proof for the new commit rather
than appending another comment. Until the new run publishes a write-up, the
previous screenshots stay visible and the block marks that proof as
updating and the last write-up as outdated.

Every proof run uses its frozen project root and build target. When
the project's config declares one, its `session.before_script` must be committed
and executable.

If the pull request is closed or merged while automation is still running,
Revyl cancels the unfinished work and updates the shared surface once to show
that it closed. Late build, workflow, or proof callbacks cannot reopen it.

### Choose which agent proves the change

By default the **Revyl agent** proves the change: it checks the pull request
out in an isolated sandbox, drives cloud devices against the preview build, and
posts its write-up on the same Revyl block as preview builds, under a headed
section for that platform. The checkout is disposable
and never holds a credential that can write to your repository. See
[Proof of changes (Revyl agent)](/develop/proof-of-changes) for the full
lifecycle, outcomes, and what reviewers see.

You can hand proof runs to a Cursor cloud agent instead, either from the
**Agent** selector in the repository's proof settings or in the config file:

```yaml theme={null}
pr_review:
  build:
    kind: revyl
    profile: pull-request
  proof_of_changes:
    enabled: true
    harness:
      kind: cursor
      model_id: null
```

Cursor opens the pull request in a cloud agent that runs the checks on a Revyl
cloud device. Its write-up lands in the same Revyl block as preview builds —
one surface per pull request, updated in place, with a headed section per
platform. The write-up is attributed to the Cursor agent that
produced it. Details contains only **View proof logs**, which opens the same
Revyl run page as a Revyl-agent proof. The write-up attribution links to the
Cursor thread when Cursor accepted an agent, so you can open it while the run
is in flight and revisit it afterwards. Because the run reports through Revyl, it
settles with the same finding-aware verdict as a Revyl-agent proof: ready,
ready with polish, ready with issues, failed for an open blocker, or
inconclusive when the change could not be exercised. A claimed ready outcome
still requires a recorded device session.

Choosing Cursor does not grant permission to launch it. Cursor authorization
is live server state and is never written into YAML. The authorizing user must
have a current Revyl role and Cursor access to the GitHub repository; Revyl
rechecks both when launching proof and provides project-scoped reauthorization
when access is missing or lost.
The Cursor agent never holds the authorizing user's Revyl credential: each
proof run gets its own Revyl key that acts only in your organization and only
for that run, and Revyl revokes it when the run settles.

## Prepare the session

Apps that need a fresh credential before a session can declare a before script
and auth-bypass references under `session`:

```yaml theme={null}
session:
  before_script:
    script_path: "./scripts/prepare-test-session.sh"
    timeout_seconds: 120
  auth_bypass:
    launch_vars: [E2E_AUTH_TOKEN]
    deep_link: "myapp://auth?token=${E2E_AUTH_TOKEN}"
```

The executable is resolved relative to the selected Revyl project root. It may
use `../` only while remaining inside the Git worktree. Secret values are
resolved at session start and are not included in configuration publication.
See [Auth And Session Prep](/cli/device/auth-and-session-prep) for execution
details.

## Run workflows

Add saved workflow IDs to the project policy:

```yaml theme={null}
pr_review:
  build:
    kind: revyl
    profile: pull-request
  workflow_ids:
    - "00000000-0000-4000-8000-000000000003"
```

After every required project target is ready, each workflow runs only against
build platforms represented by its active tests. A mixed iOS/Android workflow
executes its iOS tests against the iOS build and its Android tests against the
Android build. A deleted, empty, inaccessible, or project-platform-mismatched
selection remains visible as inconclusive instead of silently disappearing.
Workflow results appear alongside preview build status in the PR report.

## Use your own CI

If your pipeline already produces the `.app` or `.apk`, upload the finished
artifact and declare the expected app IDs instead of a managed profile:

```yaml theme={null}
pr_review:
  build:
    kind: ci_upload_to_revyl
    app_ids:
      android: "00000000-0000-4000-8000-000000000002"
  proof_of_changes:
    enabled: true
    harness:
      kind: cursor
```

The CI-upload policy contains no executable build recipe and has no saved
profile association. Your CI uploads the finished artifact with the PR head
metadata so Revyl can match it to the review. GitHub Actions supports automatic
detection. On other runners, pass `--repo` and `--commit` together.

<CodeGroup>
  ```bash Other CI runners theme={null}
  revyl build upload \
    --file path/to/app.apk \
    --app "$REVYL_ANDROID_APP_ID" \
    --platform android \
    --repo acme/mobile \
    --commit "$PR_HEAD_SHA" \
    --yes \
    --json
  ```

  ```bash GitHub Actions theme={null}
  revyl build upload \
    --file path/to/app.apk \
    --app "$REVYL_ANDROID_APP_ID" \
    --platform android \
    --version "$GITHUB_SHA" \
    --yes \
    --json
  ```
</CodeGroup>

Supply `REVYL_API_KEY` as a CI secret. In the generic example, map `PR_HEAD_SHA`
to your pipeline's full PR head SHA, not a synthetic merge commit. `--repo`
names the base GitHub repository receiving the PR, including for forks.
These flags also work with `--url` and override detected SCM identity.
Revyl matches a unique review by repository and head SHA. This supports any
runner for GitHub PRs; it does not add GitLab or Bitbucket reviews. A version
label alone does not establish PR identity.

You can also trigger an ordinary Revyl remote build from CI when you want Revyl
runners. That path still resolves a local named profile and does not publish or
send saved-profile identity:

```bash theme={null}
revyl build --profile pull-request --platform android --remote --json
```

Prefer Cursor Automations without the Revyl GitHub App? Use the same upload
step with a hand-rolled Automation —
[Cursor proof (BYO-CI)](/guides/cursor-proof).

## Related

* [Configuration Files](/cli/configuration) — complete local config and publication semantics
* [Remote Builds](/remote-builds/index) — execute a selected local recipe remotely
* [Apps](/builds/index) — where uploaded and managed artifacts are registered
* [Workflows](/tests/workflows) — create the workflow IDs referenced by `workflow_ids`


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.