CI & GitHub Actions

The CLI exits non-zero on any failure, so it drops into any pipeline as a required check.

The pattern

Run the CLI against your case directory. A non-zero exit fails the job, which fails the check, which blocks the merge — the same gate you already trust for unit tests, now covering release-critical integration behavior and flag proof.

.github/workflows/release-proof.yml
name: Release-proof gate

on:
  pull_request:

jobs:
  release-proof:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: "8.0.x"
      - run: dotnet build src/ReleaseTwin.Cli/ReleaseTwin.Cli.csproj -c Release --nologo
      - name: Run release-proof cases
        run: >
          dotnet run --project src/ReleaseTwin.Cli -c Release --no-build --
          cases/

With the versioned CLI image, the build step goes away and the gate is a single line:

- run: docker run --rm -v "$PWD/cases:/workspace:ro" ghcr.io/ernestoalejowitt22/releasetwin/cli:0.4.0 run /workspace

Already have a suite?

If your pipeline already runs Playwright, pytest, or Jest, you do not need a case file to get its results here. Upload the JUnit XML that run already writes — see Bring your existing suite.

Credentials

  • HTTP-only cases need nothing. Case files reference ${ENV_VAR}; set those from your CI's secret store.
  • A flag-proof leg needs its flag source's credentials as job env — e.g. LAUNCHDARKLY_API_TOKEN: ${{ secrets.LAUNCHDARKLY_API_TOKEN }}.
  • Connecting the hosted dashboard from GitHub Actions needs no secret. Give the job permissions: id-token: write and name your project; the CLI exchanges the job's own GitHub OIDC token for a short-lived, ingest-only credential. The project must be bound to the repository on its Settings page, so neither a stray repository nor a stray project can route an upload where it should not go.
  • Other CI systems, or a stored token by choice: add RELEASETWIN_API_TOKEN and RELEASETWIN_API_URL. A stored token always wins when both are configured.
GitHub Actions — token-free upload
permissions:
  contents: read
  id-token: write

jobs:
  release-proof:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: docker run --rm -v "$PWD:/workspace:ro" \
          -e ACTIONS_ID_TOKEN_REQUEST_URL -e ACTIONS_ID_TOKEN_REQUEST_TOKEN \
          -e RELEASETWIN_PROJECT_ID=<project id from the dashboard URL> \
          ghcr.io/ernestoalejowitt22/releasetwin/cli:0.4.0 run /workspace/cases

Naming a project is treated as intent: if the permission is missing or the exchange is refused, the run stops with one line naming the fix, and the summary records why so the PR comment can show it. Fork pull requests get no OIDC token from GitHub and upload nothing, exactly as before. Either way, run history and evidence then land on the dashboard for every CI run.

PR annotations

The integrations/github-action Action runs your cases and renders the result onto the pull request — a comment (updated in place on re-runs) with the pass/fail totals and flag-proof verdict, plus a ReleaseTwin check run you can make a required status check. It talks only to GitHub's own APIs with the workflow's GITHUB_TOKEN — no ReleaseTwin account. It is Apache-2.0, so you can fork and adapt it. The GitHub Marketplace listing is the install card for this Action, not how a stranger discovers the product — see the case file it produces, no account required.

.github/workflows/release-proof.yml
permissions:
  contents: read
  pull-requests: write
  checks: write

jobs:
  release-proof:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: releasetwin/releasetwin-action@v0
        with:
          cases-path: cases
          image: ghcr.io/ernestoalejowitt22/releasetwin/cli:0.4.0

The CLI still writes a machine-readable summary on its own with --summary-json <path> (or RELEASETWIN_SUMMARY_JSON) if you want to render it somewhere else.

Bitbucket, GitLab, and other CI

The GitHub Action is a convenience wrapper. The portable primitive is the non-zero exit code plus the --summary-json file — nothing about either is GitHub-specific. On Bitbucket Pipelines the gate is one step:

bitbucket-pipelines.yml
pipelines:
  pull-requests:
    '**':
      - step:
          name: Release-proof gate
          services: [docker]
          script:
            - >
              docker run --rm -v "$BITBUCKET_CLONE_DIR/cases:/workspace:ro" -v "$BITBUCKET_CLONE_DIR:/out"
              ghcr.io/ernestoalejowitt22/releasetwin/cli:0.4.0 run /workspace --summary-json /out/releasetwin-summary.json
          artifacts:
            - releasetwin-summary.json

A non-zero exit fails the step and blocks the merge with no extra wiring. To render the summary as a Bitbucket PR comment, parse releasetwin-summary.json and POST it to the /2.0/repositories/{workspace}/{repo}/pullrequests/{id}/comments API — the same shape as the GitHub Action's render.mjs, which you can adapt.

Merge gate from CI

The exit code proves the cases you just ran. The flag-proof merge gate asks a different question: has every flag this PR ships been proven for the release it targets, from the flag-proof evidence already uploaded? Call it from CI with the project's API token, the release label, and the flag keys; block the merge on the answer.

POST /api/cli/flag-proof-gate/check
curl -sS -X POST "$RELEASETWIN_API_URL/api/cli/flag-proof-gate/check" \
  -H "Authorization: Bearer $RELEASETWIN_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "release": "4.2", "flagKeys": ["checkout-v2"] }'
200 — one verdict for the release, one state per flag
{
  "release": "4.2",
  "verdict": "pass",
  "flags": [
    { "flagKey": "checkout-v2", "state": "proven", "caseIds": ["CHECKOUT-1"], "reason": null }
  ]
}
  • pass — every flag is proven: its backing cases are green in the release's recency window.
  • fail — at least one flag is not_proven: a backing case is failing.
  • incomplete — no flag is failing, but at least one is incomplete: no case declares it, its evidence is stale, or its case is flaky. The reason names which.

The verdict is computed from the same release rollup the dashboard shows, and every check that reaches a verdict is recorded on the project's Merge gate card. Both release and a non-empty flagKeys are required (400 otherwise). The entitlement is checked before any verdict is computed: a project whose tier lacks the gate gets a 403 and nothing is recorded.

403 — entitlement checked before the verdict
{ "error": "entitlement-required", "entitlement": "flagProofMergeGate" }

Gate the job on the verdict — for example, fail the step unless verdict is pass — and make it a required check, the same way as the ghcr.io/ernestoalejowitt22/releasetwin/cli:0.4.0 run above. Every tier grants the gate; the recorded history is on the project's dashboard, and readable over the programmatic API on Team.

Proven live, not just typed

The CLI is as stack-agnostic on the application side as it is on the CI side above: case files describe generic pipeline operations, not framework-specific adapters, so nothing here is Express-, React-, or Angular-specific either. These three different stacks are examples proving that, not the limit of what's supported.

releasetwin-ci-examples runs those three real demo apps (Express, React, Angular) through real CI platforms on every push, mirrored to releasetwin-ci-example-projects on Bitbucket — not illustrative snippets. Bitbucket Pipelines is verified green: build #1. Azure Pipelines is verified green too: build #239 — all three jobs (Express, React, Angular) passed, built directly from the GitHub repo via a service connection, no mirror needed.

Live example in this repo

.github/workflows/releasetwin-demo.yml runs the zero-credential HTTP case on every PR and on demand — a real, green release-proof gate you can copy. A passing run's step output:

Run release-proof cases
$ dotnet run --project src/ReleaseTwin.Cli -c Release --no-build -- demo/quickstart/cases

PASS HTTP-DEMO-1
1 passed, 0 failed

Next

Case files for what each case can assert, or Hosted platform to send CI run history to the dashboard.