# GitHub

Configure pull request verification, preview deployments, GitHub Actions, and merge checks.

Connect a GitHub repository, choose how its preview reaches Verify, and keep a results comment and merge check up to date for every pull request. **PR #45**, tagged with its GitHub repository, opens the PR's run history, recordings, findings, and feedback.

Use [`composalai/verify@1`](https://github.com/ComposalAI/verify) in GitHub Actions or `com verify pull-requests` in your own pipeline. For GitLab merge requests, use the [GitLab configuration guide](/docs/verify/source-control/gitlab).

## Configure a GitHub repository once

You need Verify enabled for the organization, administrator access, and a GitHub App connection for the repository. The app needs pull-request read, issue-comment write, and checks write access; GitHub preview discovery also needs deployment read access. The user who saves the configuration must retain administrator access for automatic runs to continue.

1. Connect the GitHub repository to your Composal organization. Use its **Composal repository slug** for CLI commands; it may differ from its GitHub name.
2. Prepare a scenario pack describing your journeys, personas, and observable outcomes. For focused selection, map product areas to their source paths and dependencies. Follow [Expectations as Code](/docs/verification#expectations-as-code).
3. Prepare a Verify environment for the same repository. For URL discovery or automatic Composal preview deployment, use a ready **external environment template**. Its personas, credential references, and safety policy carry over to each PR URL. Save test credentials through [Verify → Credentials](/docs/verification#credentials), and confirm that a browser can reach and sign in to the target.
4. Open **Verify → Integrations → GitHub**. Select the repository, scenario pack, environment template, and one of the preview sources below. Use **Advanced Settings** for excluded file paths, browser concurrency, and the time budget, then choose **Save Settings**.
5. Enable **Automatic Verification** to create requests when PRs open, reopen, receive commits, or become ready for review. Drafts wait until ready. For an already-open PR, use **Verify a PR**. Later, use **Rerun verification** on the PR page or the CLI.

Turning automatic updates off leaves manual requests available. Previous runs remain in history. Repository selection and preview-source configuration live in the dashboard; the CLI uses that saved configuration to request runs and hand off ready previews.

The settings page is `/{org}/verify/integrations/github`; older `/{org}/verify/pr-testing` links redirect there. Choosing a repository adds `?repository_id={repository_id}`, so you can link directly to its configuration and return through browser history. Members can view settings; administrators can save them and start runs. Administrators add repositories from the same page. **Advanced Settings → Run Limits** accepts 1–20 concurrent browsers and a 30–3,600 second time budget; organization limits still apply. Save any edits before manually verifying a PR so the run uses the displayed configuration. Switching repositories or leaving the page prompts you to keep or discard unsaved edits. For **Your CI**, use **Copy CI Setup** to copy the repository-specific workflow step.

## Choose where the tested preview comes from

| Preview source | Configure in Verify | Who deploys the PR? |
| --- | --- | --- |
| **Composal Preview** | Existing preview profile, browser entry service, and preview access | Verify provisions and deploys an isolated Composal preview at the PR head. |
| **GitHub Deployment** | GitHub deployment environment name, such as `Preview` | Your existing pipeline or deployment provider publishes a successful GitHub deployment with an `environment_url`. |
| **Your CI** | Environment template and scenario pack | Your pipeline deploys the PR head, waits for readiness, then supplies a URL through the action or a ready Verify environment through `com`. |

### Have Composal deploy the preview

Configure the hosted app's [preview profile](/docs/hosting#previews), then choose **Composal Preview**. Select the public-facing service whose HTTPS URL opens your app. Verify uses the exact PR head commit, deploys the profile's services, and waits for healthy rollouts and the entry service's HTTPS domain before testing.

The profile's resource sharing and expiry settings apply. **Organization access** requires browser authentication that can pass Composal's preview protection. **Public access** makes the preview URL reachable directly; your app's own login still applies. Choose the visibility deliberately and use dedicated test accounts. The PR commit must be available in the connected Composal repository.

### Discover an existing GitHub preview

Choose **GitHub Deployment** and enter the deployment environment name exactly as your provider reports it. The default is `Preview`.

The deployment must belong to the exact PR head SHA, be non-production, and have a latest status of `success` with an `environment_url`. A URL posted only in a bot comment is insufficient for discovery. If your provider only exposes a workflow output, use the CI handoff below.

### Let CI supply the deployed target

Choose **Your CI** when your pipeline owns deployment. CI first deploys the PR head and confirms the target is ready. Then it supplies either:

- A URL through the GitHub Action's `preview-url`. Verify creates a PR-specific environment revision from your external template.
- A ready Verify preview environment slug through `com verify pull-requests preview` or the action's `environment` input. It must belong to the same organization and repository.

For external URLs, CI attests which commit was deployed. For Composal-managed previews, Verify also checks that the preview is serving that commit and its latest service deployments succeeded. If deployment fails or no matching preview arrives within one hour, verification is blocked. It does not substitute a shared environment or an older deployment.

## Understand what gets tested

PR planning uses the same ownership and dependency rules as Changes. It compares changed paths and available hunks with the scenario pack's product areas, and pins the PR head, base, and selection plan. The run explains its selected and excluded areas.

Unknown paths, cross-cutting changes, or incomplete diffs can broaden selection to the safe set. Narrative packs without file ownership run all their planned journeys. A pass covers the selected journeys for that pinned preview; it does not establish coverage of the whole product.

Configure **Paths That Need No Browser Testing** for explicit exclusions, such as `docs/**` and `README.md`, one per line or separated by commas. Verify skips only when every changed path matches, including a renamed file's previous path. Otherwise the product-selection rules apply. Leave this field empty if you want all changes assessed.

## Start and inspect a PR run with `com`

[Install the CLI](/docs/quickstart). For local use, sign in and select your organization:

```sh
com login
com whoami
com context switch acme
com verify pull-requests --help
```

The examples use organization `acme`, Composal repository `shop`, and GitHub PR `45`. Replace these with your own values. Pass `--org` and `--repo` explicitly in CI; local commands can infer them from a configured Composal checkout.

Start an existing PR using its saved preview source:

```sh
com verify pull-requests run 45 \
  --org acme --repo shop \
  --idempotency-key pr-45-review-1 --json
```

The response includes a PR `id`, `web_path`, and `requested_run_id`. A draft or closed PR has no new request. Reuse the key when retrying an uncertain response for the same revision; choose a new key for an intentional rerun.

Inspect the history using the returned PR ID:

```sh
com verify pull-requests get <pr-id> --org acme --json
```

A history item has a request `id`, pinned SHAs, `display_state`, selection plan, and, once launched, a `verify_sweep_id`. Use that sweep ID to inspect or follow the browser run:

```sh
com verify sweeps get <sweep-id> --org acme --repo shop --json
com verify sweeps watch <sweep-id> \
  --org acme --repo shop --timeout-seconds 900 --json
```

| Identifier | Meaning |
| --- | --- |
| PR number `45` | The GitHub PR you want to verify. In shell examples, use `45` or quote `'#45'`; an unquoted `#` starts a shell comment. |
| PR `id` | Composal's stable PR identity for `pull-requests get` and the history page. |
| `requested_run_id` | One verification request, pinned to a PR revision. It is the action's `run-id`. |
| `verify_sweep_id` | The browser run created after preview readiness and selection. It is the action's `sweep-id`. |
| Preview environment slug | A ready Verify environment, passed with `--environment`; it is not a hosted app's environment name or revision ID. |

Use the dedicated `pull-requests` commands for PRs. `sweeps plan --change '#123'` remains the Change flow; manual and scheduled plans use their selected environment and full pack.

## Supply a ready preview from your CI pipeline

Store a Composal administrator API token in your CI secret store and expose it as **`VEX_API_TOKEN`** to CLI steps. This is the environment variable read by `com`; the secret itself can be named `COMPOSAL_TOKEN`. CI does not need a browser login. See [CLI configuration](/docs/cli-configuration#config-layers) for endpoint and profile precedence.

Deploy the full PR head SHA and wait for readiness before handing it to Verify. On GitHub, use `github.event.pull_request.head.sha`; `github.sha` on a PR event can identify a [synthetic merge commit](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#pull_request).

If your pipeline already readies a Verify preview environment, use this sequence:

```sh
com verify pull-requests run 45 \
  --org acme --repo shop \
  --head-sha <full-deployed-pr-head-sha> \
  --idempotency-key pr-45-ci-attempt-1 --json

# Run after deployment succeeds and the Verify environment is ready.
com verify pull-requests preview 45 \
  --org acme --repo shop \
  --head-sha <full-deployed-pr-head-sha> \
  --environment <verify-preview-slug> --json
```

Use `--head-sha` on `run` in CI to refuse a stale workflow before it can request verification for a newer commit. A stale head or an environment from another repository is refused. Retrying the same accepted handoff is safe; switching its target requires a fresh request. Keep the preview alive until the browser run and cleanup finish. For external URLs, CI attests that the target serves the submitted head; Verify cannot independently discover a generic application’s served Git commit. Use immutable per-commit URLs or an appropriate deployment health check. Composal-managed previews additionally validate the serving deployment’s commit.

### Register an external URL for anonymous coverage

For anonymous journeys, an already-deployed URL can become a ready Verify preview environment through the CLI:

```sh
printf '%s\n' '{"source_revision":"<full-deployed-pr-head-sha>"}' > preview-policy.json

com verify environments create --org acme --repo shop \
  --slug pr-45-attempt-1 --name 'PR 45 preview' \
  --target-url https://pr-45.preview.example.com \
  --target-kind preview \
  --policy preview-policy.json \
  --idempotency-key preview-pr-45-attempt-1 --json

com verify pull-requests preview 45 \
  --org acme --repo shop \
  --head-sha <full-deployed-pr-head-sha> \
  --environment pr-45-attempt-1 --json
```

Create the verification request with `pull-requests run` first, or use the automatic request already created for that revision. External environments supplied by slug must record the full deployed head SHA in `source_revision` when created. Verify rejects missing or different revisions at handoff, launch, and browser dispatch. Use a unique environment slug and key for each deployed target so earlier runs keep their pinned revisions. The action's `preview-url` input records the supplied SHA automatically.

URL registration starts with an anonymous persona. For authenticated journeys, supply a ready environment with the required personas and credential references, or use the action's `preview-url` path, which inherits your configured template. See [environments and scenario setup](/docs/cli#environments).

### Add the handoff to GitHub Actions

After a deployment step named `deploy` has produced a ready Verify environment slug in `verify_environment`, add these steps to the same job. Install a CLI release that supports `pull-requests`:

```yaml
- name: Install Composal CLI
  shell: bash
  run: |
    curl -fsSL https://dl.composal.ai/install.sh | sh
    echo "$HOME/.composal/bin" >> "$GITHUB_PATH"

- name: Hand the PR preview to Verify
  shell: bash
  env:
    VEX_API_TOKEN: ${{ secrets.COMPOSAL_TOKEN }}
    COMPOSAL_ORG: ${{ vars.COMPOSAL_ORG }}
    COMPOSAL_REPO: ${{ vars.COMPOSAL_REPO }}
    PR_NUMBER: ${{ github.event.pull_request.number }}
    PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
    VERIFY_PREVIEW_ENV: ${{ steps.deploy.outputs.verify_environment }}
  run: |
    set -euo pipefail
    com verify pull-requests run "$PR_NUMBER" \
      --org "$COMPOSAL_ORG" --repo "$COMPOSAL_REPO" \
      --head-sha "$PR_HEAD_SHA" \
      --idempotency-key "github:${GITHUB_RUN_ID}:${GITHUB_RUN_ATTEMPT}" --json
    com verify pull-requests preview "$PR_NUMBER" \
      --org "$COMPOSAL_ORG" --repo "$COMPOSAL_REPO" \
      --head-sha "$PR_HEAD_SHA" --environment "$VERIFY_PREVIEW_ENV" --json
```

This step submits the target; success means **accepted**, not **passed**. Inspect `display_state` through PR history or follow the resulting sweep. `sweeps watch` returns successfully when a sweep becomes terminal even if the assertions failed. To gate a job using the CLI, check the final outcome explicitly:

```sh
com verify sweeps watch <sweep-id> \
  --org acme --repo shop --timeout-seconds 900 --json
com verify sweeps get <sweep-id> --org acme --repo shop --json \
  | jq -e '.sweep.state == "terminal" and .sweep.outcome == "passed"'
```

This gate applies once a sweep exists. A PR request can be skipped or blocked before any sweep is created; check its history as well. The action below handles that lifecycle for you.

## Use the GitHub Action helper

Use the published [`composalai/verify@1`](https://github.com/ComposalAI/verify/releases/tag/1) action after your preview deployment is ready.

For a CI-owned deployment, select **Your CI** and an external template in Verify, then add the action after your ready deployment:

```yaml
- uses: composalai/verify@1
  id: verify
  with:
    token: ${{ secrets.COMPOSAL_TOKEN }}
    org: ${{ vars.COMPOSAL_ORG }}
    preview-url: ${{ steps.deploy.outputs.url }}
```

The action infers the PR number, head SHA, and GitHub repository name. Set `repository` if the Composal repository slug differs. It requires no checkout, installed CLI, or separate GitHub token; the connected GitHub App maintains the results comment.

With **Composal Preview** or **GitHub Deployment** configured, omit `preview-url`. This complete workflow can request and wait for verification without a deployment step:

```yaml
name: Verify PR
on:
  pull_request:
    types: [opened, synchronize, reopened, ready_for_review]
permissions:
  contents: read
concurrency:
  group: verify-${{ github.event.pull_request.number }}
  cancel-in-progress: true
jobs:
  verify:
    if: ${{ !github.event.pull_request.draft && github.event.pull_request.head.repo.full_name == github.repository }}
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - uses: composalai/verify@1
        id: verify
        with:
          token: ${{ secrets.COMPOSAL_TOKEN }}
          org: ${{ vars.COMPOSAL_ORG }}
```

Set `COMPOSAL_TOKEN` as an Actions secret containing a Composal administrator API token, and `COMPOSAL_ORG` as a variable containing the organization slug. GitHub [withholds repository secrets from fork-triggered workflows](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets); the example skips those jobs. Use your existing trusted deployment process for fork previews rather than exposing secrets with `pull_request_target`.

| Action input | Default | When to set it |
| --- | --- | --- |
| `token` | Required | Composal API token from a secret. |
| `org` | Required | Composal organization slug. |
| `preview-url` | Omitted | Ready URL deployed from the PR head, using CI preview mode. |
| `environment` | Omitted | Ready Verify preview environment slug, instead of `preview-url`. |
| `repository` | GitHub repository name | Composal repository slug differs. |
| `pr`, `sha` | PR event | Explicit PR number and full deployed head for another event type. A `workflow_run` identifying one PR is inferred; differing workflow/PR SHAs require an explicit deployed `sha`. |
| `wait` | `'true'` | Set `'false'` for an asynchronous handoff. |
| `timeout` | `'15'` | Wait limit in minutes, from 1 to 60; set the job timeout higher. |
| `api-url` | `https://composal.ai` | Another compatible HTTPS API origin. |

The action waits by default. **Passed** and **skipped** requests succeed; failed, blocked, superseded, cancelled, cleanup-blocked, and timed-out outcomes fail the step. A wait timeout also fails the step, but the server run continues and its history link remains available. Cancelling a workflow job likewise does not cancel a server run already launched.

Outputs are `url` (PR history), `pull-request-id`, `run-id` (verification request), `sweep-id` (when launched), and `status`. The job summary links to results. With `wait: 'false'`, a successful step means the request or handoff was accepted. Retries within the same workflow attempt reuse its request; rerunning the workflow creates a fresh one. The action refuses a stale workflow head before requesting a newer-head run.

## Read the GitHub comment and full history

**Composal Verify** appears in the PR’s merge checks for the exact head commit. It shows queued, waiting for preview, and running states, then succeeds for passed coverage, skips unnecessary testing, or reports a failure or setup blocker. New revisions and reruns replace the active check; earlier requests are cancelled. You can require this check through GitHub branch protection. Existing GitHub App installations may need to approve the checks write permission.

Verify maintains one results comment per PR. It shows the commit, tested preview, selected areas, selection reasons, current state, and problems to address. Critical and high-severity findings link to issue recordings. These are authenticated links into Verify; private videos are not embedded as public GitHub attachments.

In **Verify → Runs**, select **PR #45** to open its paginated history. Open a browser run to use **Live**, **Recordings**, findings, and feedback. Earlier head or base revisions are labelled so their results are not mistaken for coverage of the current PR. The history also retains skipped requests and setup blockers that never produced a sweep.

## Resolve setup and CI problems

| What you see | What to check |
| --- | --- |
| `pull-requests` is unrecognized | Install a CLI release containing PR verification; the action and API also need compatible releases. |
| Configuration has no GitHub repository | Confirm the GitHub App connection is active and has access to that repository in this organization. |
| GitHub preview never appears | Check the configured deployment environment name, exact head SHA, non-production flag, latest successful status, `environment_url`, and deployment-read permission. |
| **Waiting for preview** | CI must hand off a ready target, or the configured Composal deployment must finish and obtain its HTTPS domain. After one hour, request a rerun once the preview is ready. |
| Stale or conflicting handoff | Use the current PR head. Retry the same target for the same request; use a new request for an intentional target change. |
| Preview mismatch | Check that the managed preview is serving the submitted SHA and its latest service deployments succeeded. |
| Login or access blocker | Check preview visibility, declared personas, and credentials. Confirm browser access before treating it as an app defect. |
| **Skipped** | Read the exclusion or selection reason. No browser sweep may have been necessary. |
| CLI step passed, but Verify did not | Submission and terminal-state watching do not assert a pass; inspect the outcome or use the action's default waiting behavior. |
| Missing results comment | Check GitHub comment-write permission, connection health, and the configuring user's administrator access. |
| Action wait timed out | Open the returned history link. The run continues; increase the action and job timeouts for later runs if needed. |

Continue with the [Source Control overview](/docs/verify/source-control), [CLI reference](/docs/cli), [scenario and MCP reference](/docs/verification), or [preview hosting guide](/docs/hosting#previews).
