Skip to content
Integrations

CI integration

Run `stack doctor --json` on every push and nightly, backed by Phantom Cloud for secret pull. One command scaffolds the workflow.

The shape

Stack's CI story is deliberately small:

  1. Pull the encrypted vault from Phantom Cloud using two repository secrets.
  2. Run stack doctor --json. Exit code is 0 if all services are healthy, 1 if any service fails.
  3. Upload the JSON report as a build artifact for debugging.

That's it. No secrets in GitHub's environment, no plaintext in the logs. The vault stays encrypted end-to-end — Phantom Cloud holds ciphertext, and only the CI job (with the right passphrase) can decrypt locally.

Scaffold the workflow

stack ci init

This writes .github/workflows/stack-ci.yml with a correct-by-default workflow. Pass --force to overwrite an existing file.

The generated workflow

name: Stack doctor

on:
  push:
    branches: [main]
  pull_request:
  schedule:
    # Nightly drift check — catches when an upstream key gets revoked
    # out of band.
    - cron: "0 7 * * *"
  workflow_dispatch:

jobs:
  doctor:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Phantom Secrets
        run: npm install -g phantom-secrets

      - name: Install Stack CLI
        run: npm install -g @ashlr/stack

      - name: Pull vault from Phantom Cloud
        env:
          PHANTOM_CLOUD_TOKEN: ${{ secrets.PHANTOM_CLOUD_TOKEN }}
          PHANTOM_VAULT_PASSPHRASE: ${{ secrets.PHANTOM_VAULT_PASSPHRASE }}
        run: phantom cloud pull --force

      - name: Run stack doctor
        run: stack doctor --json > stack-doctor.json

      - name: Upload doctor report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: stack-doctor-report
          path: stack-doctor.json

Repository secrets

The workflow needs two secrets. Create them once locally, then copy them into Settings → Secrets and variables → Actions on the repo:

Secret Where it comes from
PHANTOM_VAULT_PASSPHRASE The passphrase for Phantom's encrypted-file vault fallback. Phantom uses this in CI where macOS Keychain / libsecret isn't available. Choose one when you run phantom login.
PHANTOM_CLOUD_TOKEN The GitHub OAuth device token Phantom minted when you ran phantom cloud push. View it with phantom cloud status --show-token.

Exit-code behaviour

# success — every service healthy
$ stack doctor --json
{ "reports": [ ... ] }
$ echo $?
0

# failure — at least one service errored
$ stack doctor --json
{ "reports": [ { "services": [ { "status": "error", "detail": "..." } ] } ] }
$ echo $?
1

GitHub Actions will fail the job on a non-zero exit, so the default workflow above is already "red on broken credentials." Pair it with workflow_dispatch so you can re-run manually once you rotate a key.

JSON report shape

{
  "reports": [
    {
      "project": "cwd",
      "path": "/home/runner/work/webapp/webapp",
      "services": [
        {
          "name": "supabase",
          "status": "ok",
          "latencyMs": 218
        },
        {
          "name": "posthog",
          "status": "error",
          "detail": "key invalid"
        }
      ]
    }
  ]
}

Each service reports a status of ok, warn, error, or unchecked (the provider didn't implement a healthcheck yet). Parse this in downstream steps if you want to post a summary to Slack or fail only on specific services.

Other CI systems

stack ci init only scaffolds GitHub Actions today. For other providers, the two essential steps to replicate are:

# 1. Pull the vault (needs PHANTOM_CLOUD_TOKEN + PHANTOM_VAULT_PASSPHRASE)
phantom cloud pull --force

# 2. Run the check
stack doctor --json > stack-doctor.json

Adapters for GitLab CI, CircleCI, and Buildkite are on the roadmap. Open an issue on GitHub if you want yours prioritised.

Rotating secrets from CI

Don't run stack add <service> --force from CI — it'd re-run the full OAuth / PAT flow, which isn't meaningful in a headless job. Instead, rotate the underlying credential at the provider (e.g. generate a new Supabase service key), then run stack login <provider> locally to refresh your vault. Then phantom cloud push so CI picks up the new value on the next run.