How to report schema plans in CI

Run delta-engine plan when a trusted same-repository pull request opens. The command reads the live Unity Catalog state, prints the target, semantic diff, sync report, and exact planned SQL without executing the planned DDL.

Pending valid changes exit successfully. Catalog-read, validation, foreign-key-resolution, connection, and configuration failures exit unsuccessfully, so one command can act as the PR check without making an intentional schema change look like a broken build.

Prepare one declaration collection

Expose the tables for the workflow as one non-empty ordered sequence:

# myproject/tables.py
from delta_engine.schema import Column, DeltaTable, Integer

orders = DeltaTable(
    "production",
    "silver",
    "orders",
    columns=(Column("id", Integer(), nullable=False),),
)

all_tables = [orders]

The command takes the collection’s exact MODULE:ATTRIBUTE reference:

pip install "delta-engine[cli]"
delta-engine plan myproject.tables:all_tables

The CLI accepts one reference only. A single table, empty or unordered collection, mixed sequence, and duplicate qualified table names fail before a connection opens. See the CLI reference for the complete contract.

Configure GitHub OIDC for this workflow

Create one Databricks service principal for plans and grant only the warehouse and catalog permissions needed to read the declared tables’ metadata. Do not grant schema-changing privileges.

Configure Databricks workload identity federation for the repository and pull request subjects you trust. The GitHub job needs permissions: id-token: write so GitHub supplies its OIDC request URL and token. Store these non-secret target values as repository variables:

  • DATABRICKS_HOST

  • DATABRICKS_PLAN_CLIENT_ID

  • DATABRICKS_SQL_WAREHOUSE_ID

The workflow selects GitHub OIDC through the Databricks SDK’s standard DATABRICKS_AUTH_TYPE setting. The CLI itself has no GitHub-specific authentication branch; it delegates credential resolution to the SDK.

Fork pull requests execute code controlled by another repository. Skip the live plan for forks so that code never receives the federated catalog identity; run the project’s ordinary offline lint and test jobs for those PRs instead.

Add the GitHub Actions workflow

This complete workflow has one read-only plan job, no apply path, and no write-capable identity:

name: schema-plan

on:
  pull_request:

jobs:
  plan:
    # Do not give fork-controlled code the live catalog identity.
    if: github.event.pull_request.head.repo.full_name == github.repository
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
    env:
      DATABRICKS_HOST: ${{ vars.DATABRICKS_HOST }}
      DATABRICKS_CLIENT_ID: ${{ vars.DATABRICKS_PLAN_CLIENT_ID }}
      DATABRICKS_AUTH_TYPE: github-oidc
      DATABRICKS_SQL_WAREHOUSE_ID: ${{ vars.DATABRICKS_SQL_WAREHOUSE_ID }}
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-python@v6
        with:
          python-version: "3.12"
          cache: pip
          cache-dependency-path: pyproject.toml
      - run: pip install "delta-engine[cli]"
      - name: Plan schemas
        shell: bash
        run: |
          set +e
          delta-engine plan myproject.tables:all_tables > plan-report.txt
          status=$?
          set -e

          {
            echo '## Delta Engine plan'
            echo
            echo '```text'
            cat plan-report.txt
            echo '```'
          } >> "$GITHUB_STEP_SUMMARY"

          cat plan-report.txt
          exit "$status"

The command’s complete report is stdout, so the step stores it before writing the fenced summary and replaying it to the job log. Imported-code output, logs, configuration errors, and tracebacks remain on stderr and therefore go directly to the log. The original exit status is preserved after the summary is written.

Interpret the result

Exit code

Result

0

The live plan completed, with or without pending changes

1

Configuration, authentication, reading, planning, or FK resolution failed

2

The command line was malformed, such as a missing argument or removed option

Review the DIFF section for semantic intent and PLANNED SQL for the DDL compiled from the catalog snapshot read by this plan. A PLANNING_FAILED, READ_FAILED, or FOREIGN_KEY_FAILED row is a failed check and includes detail in the report. A later write-capable sync re-reads and re-plans live state, so the preview is not a replay artifact and cannot predict execution failures.

delta-engine plan always calls the engine with dry_run=True; there is no apply command or flag to turn the generated plan into a write. Declaration modules are ordinary Python, which is why the workflow uses trusted code and a read-only identity. Applying declarations remains a separate Python API workflow with its own explicit connection and permissions.