From 374ebc6fe8ceba41cf95289ba7097f23a9c48bb2 Mon Sep 17 00:00:00 2001 From: Bram Jans Date: Thu, 19 Feb 2026 10:51:39 +0100 Subject: [PATCH 1/6] chore: Add a schema compatibility check to the CI for the OH CLI A first attempt at trying to catch the "Breaking changes detected" warning in the CLI ahead of a new release. --- .github/workflows/ci.yml | 23 +++++++++- openhexa/cli/api.py | 6 +-- openhexa/graphql/__init__.py | 3 ++ scripts/check_schema_compatibility.py | 65 +++++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 6 deletions(-) create mode 100644 scripts/check_schema_compatibility.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6704d65b..f8695f41 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,12 +35,31 @@ jobs: uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - + - name: "Install dependencies" run: pip install ".[dev]" - + - name: Run tests run: pytest --cov=. --cov-report html --cov-report term --cov-fail-under=25 + + schema-compatibility: + name: Check schema compatibility with demo server + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: "3.13" + + - name: Install dependencies + run: pip install ".[dev]" + + - name: Check schema compatibility + run: python scripts/check_schema_compatibility.py + conda-build: name: Conda build check runs-on: ubuntu-latest diff --git a/openhexa/cli/api.py b/openhexa/cli/api.py index 539e172b..cbdfe3dd 100644 --- a/openhexa/cli/api.py +++ b/openhexa/cli/api.py @@ -24,7 +24,7 @@ from jinja2 import Template from openhexa.cli.settings import settings -from openhexa.graphql import BaseOpenHexaClient +from openhexa.graphql import BUNDLED_SCHEMA_PATH, BaseOpenHexaClient from openhexa.sdk.pipelines import get_local_workspace_config from openhexa.sdk.pipelines.runtime import get_pipeline from openhexa.utils import create_requests_session, stringcase @@ -134,9 +134,7 @@ def _detect_graphql_breaking_changes_if_needed(token): def _detect_graphql_breaking_changes(token): """Detect breaking changes between the schema referenced in the SDK and the server using graphql-core.""" - stored_schema_obj = build_schema( - (Path(__file__).parent.parent / "graphql" / "schema.generated.graphql").open().read() - ) + stored_schema_obj = build_schema(BUNDLED_SCHEMA_PATH.read_text()) server_schema_obj = build_client_schema( _query_graphql(get_introspection_query(input_value_deprecation=True), token=token) ) diff --git a/openhexa/graphql/__init__.py b/openhexa/graphql/__init__.py index 42359acc..c97da20f 100644 --- a/openhexa/graphql/__init__.py +++ b/openhexa/graphql/__init__.py @@ -1,5 +1,8 @@ """GraphQL package.""" +from pathlib import Path from .base_openhexa_client import BaseOpenHexaClient # noqa: F401 -> Expose base client class from .graphql_client import * # noqa: F403 -> Expose autogenerated types + +BUNDLED_SCHEMA_PATH = Path(__file__).parent / "schema.generated.graphql" diff --git a/scripts/check_schema_compatibility.py b/scripts/check_schema_compatibility.py new file mode 100644 index 00000000..af165172 --- /dev/null +++ b/scripts/check_schema_compatibility.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +""" +Check for breaking changes between the SDK's bundled GraphQL schema and live server schemas for our production and demo environments. + +Exits 0 regardless of outcome; prints a warning summary if breaking changes are found. +""" + +import requests +from graphql import build_client_schema, build_schema, get_introspection_query +from graphql.utilities import find_breaking_changes + +from openhexa.graphql import BUNDLED_SCHEMA_PATH + +URLS = [ + "https://api.demo.openhexa.org", + "https://api.openhexa.org", +] + + +def fetch_server_schema(graphql_url: str): + """Fetch the live GraphQL schema from the server via introspection.""" + response = requests.post( + graphql_url, + json={"query": get_introspection_query(input_value_deprecation=True)}, + timeout=30, + ) + response.raise_for_status() + body = response.json() + if "errors" in body: + raise RuntimeError(f"Introspection query returned errors: {body['errors']}") + return build_client_schema(body["data"]) + + +def check_url(stored_schema, url: str) -> list: + """Check breaking changes for a single server URL. Returns the list of breaking changes.""" + graphql_url = url.rstrip("/") + "/graphql/" + server_schema = fetch_server_schema(graphql_url) + + breaking_changes = find_breaking_changes(stored_schema, server_schema) + if breaking_changes: + print(f" ⚠️ {len(breaking_changes)} breaking change(s) detected:") + for change in breaking_changes: + print(f" - {change.description}") + else: + print(" ✅ No breaking changes detected.") + return breaking_changes + + +def main(): + stored_schema = build_schema(BUNDLED_SCHEMA_PATH.read_text()) + + all_breaking_changes = [] + for url in URLS: + all_breaking_changes.extend(check_url(stored_schema, url)) + + if all_breaking_changes: + print( + "\nThe server schema has diverged from openhexa/graphql/schema.generated.graphql." + "\nUpdate the bundled schema by copying the latest schema from the OpenHEXA monorepo" + " and re-running: python -m ariadne_codegen" + ) + + +if __name__ == "__main__": + main() From eeab7b28f1fc26f260f84bf8d509b49a78a282ff Mon Sep 17 00:00:00 2001 From: Bram Jans Date: Thu, 19 Feb 2026 14:45:30 +0100 Subject: [PATCH 2/6] fix: Add warning for GH action and satisfy ruff --- scripts/check_schema_compatibility.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/check_schema_compatibility.py b/scripts/check_schema_compatibility.py index af165172..a9a5e11d 100644 --- a/scripts/check_schema_compatibility.py +++ b/scripts/check_schema_compatibility.py @@ -5,12 +5,16 @@ Exits 0 regardless of outcome; prints a warning summary if breaking changes are found. """ +import os + import requests from graphql import build_client_schema, build_schema, get_introspection_query from graphql.utilities import find_breaking_changes from openhexa.graphql import BUNDLED_SCHEMA_PATH +GITHUB_ACTIONS = os.getenv("GITHUB_ACTIONS") == "true" + URLS = [ "https://api.demo.openhexa.org", "https://api.openhexa.org", @@ -41,12 +45,15 @@ def check_url(stored_schema, url: str) -> list: print(f" ⚠️ {len(breaking_changes)} breaking change(s) detected:") for change in breaking_changes: print(f" - {change.description}") + if GITHUB_ACTIONS: + print(f"::warning title=GraphQL schema breaking change ({url})::{change.description}") else: print(" ✅ No breaking changes detected.") return breaking_changes def main(): + """Execute main function.""" stored_schema = build_schema(BUNDLED_SCHEMA_PATH.read_text()) all_breaking_changes = [] From c50ddbd3375428b60e7f62879c01096281a0047c Mon Sep 17 00:00:00 2001 From: Bram Jans Date: Thu, 19 Feb 2026 14:50:50 +0100 Subject: [PATCH 3/6] wip --- .github/workflows/ci.yml | 1 + scripts/check_schema_compatibility.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8695f41..8ac99848 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,6 +45,7 @@ jobs: schema-compatibility: name: Check schema compatibility with demo server runs-on: ubuntu-latest + continue-on-error: true steps: - name: Checkout uses: actions/checkout@v6 diff --git a/scripts/check_schema_compatibility.py b/scripts/check_schema_compatibility.py index a9a5e11d..55268fa7 100644 --- a/scripts/check_schema_compatibility.py +++ b/scripts/check_schema_compatibility.py @@ -2,10 +2,12 @@ """ Check for breaking changes between the SDK's bundled GraphQL schema and live server schemas for our production and demo environments. -Exits 0 regardless of outcome; prints a warning summary if breaking changes are found. +Exits 1 if breaking changes are found so that the CI step is visibly marked as failed. +The CI job uses continue-on-error: true so it does not block merging. """ import os +import sys import requests from graphql import build_client_schema, build_schema, get_introspection_query @@ -66,6 +68,7 @@ def main(): "\nUpdate the bundled schema by copying the latest schema from the OpenHEXA monorepo" " and re-running: python -m ariadne_codegen" ) + sys.exit(1) if __name__ == "__main__": From 8862486dca768d282a8efa2feabe05ead566ff84 Mon Sep 17 00:00:00 2001 From: Bram Jans Date: Thu, 19 Feb 2026 16:20:39 +0100 Subject: [PATCH 4/6] try with file annotation --- .github/workflows/ci.yml | 1 - scripts/check_schema_compatibility.py | 11 ++++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ac99848..f8695f41 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,7 +45,6 @@ jobs: schema-compatibility: name: Check schema compatibility with demo server runs-on: ubuntu-latest - continue-on-error: true steps: - name: Checkout uses: actions/checkout@v6 diff --git a/scripts/check_schema_compatibility.py b/scripts/check_schema_compatibility.py index 55268fa7..28f6fe92 100644 --- a/scripts/check_schema_compatibility.py +++ b/scripts/check_schema_compatibility.py @@ -2,12 +2,12 @@ """ Check for breaking changes between the SDK's bundled GraphQL schema and live server schemas for our production and demo environments. -Exits 1 if breaking changes are found so that the CI step is visibly marked as failed. -The CI job uses continue-on-error: true so it does not block merging. +Exits 0 regardless of outcome. Emits ::warning:: annotations when running in GitHub Actions +so breaking changes are visible without affecting the job status. """ import os -import sys +from pathlib import Path import requests from graphql import build_client_schema, build_schema, get_introspection_query @@ -16,6 +16,8 @@ from openhexa.graphql import BUNDLED_SCHEMA_PATH GITHUB_ACTIONS = os.getenv("GITHUB_ACTIONS") == "true" +REPO_ROOT = Path(__file__).parent.parent +SCHEMA_RELATIVE_PATH = BUNDLED_SCHEMA_PATH.relative_to(REPO_ROOT) URLS = [ "https://api.demo.openhexa.org", @@ -48,7 +50,7 @@ def check_url(stored_schema, url: str) -> list: for change in breaking_changes: print(f" - {change.description}") if GITHUB_ACTIONS: - print(f"::warning title=GraphQL schema breaking change ({url})::{change.description}") + print(f"::warning file={SCHEMA_RELATIVE_PATH},line=1,title=GraphQL schema breaking change ({url})::{change.description}") else: print(" ✅ No breaking changes detected.") return breaking_changes @@ -68,7 +70,6 @@ def main(): "\nUpdate the bundled schema by copying the latest schema from the OpenHEXA monorepo" " and re-running: python -m ariadne_codegen" ) - sys.exit(1) if __name__ == "__main__": From 1798187b87fc32cd59a8c2e77e4c50b6fc60b92e Mon Sep 17 00:00:00 2001 From: Bram Jans Date: Mon, 23 Feb 2026 12:48:27 +0100 Subject: [PATCH 5/6] changed approach: 2 checks, one on release PRs and one... cron each morning. Only check on prod and do a Slack notification in case of failure. --- .github/workflows/ci.yml | 1 + .../workflows/schema-compatibility-cron.yml | 34 ++++++++++++++ scripts/check_schema_compatibility.py | 45 +++++++------------ 3 files changed, 52 insertions(+), 28 deletions(-) create mode 100644 .github/workflows/schema-compatibility-cron.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8695f41..0e45b321 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,6 +45,7 @@ jobs: schema-compatibility: name: Check schema compatibility with demo server runs-on: ubuntu-latest + if: startsWith(github.head_ref, 'release-please') steps: - name: Checkout uses: actions/checkout@v6 diff --git a/.github/workflows/schema-compatibility-cron.yml b/.github/workflows/schema-compatibility-cron.yml new file mode 100644 index 00000000..08b93a83 --- /dev/null +++ b/.github/workflows/schema-compatibility-cron.yml @@ -0,0 +1,34 @@ +name: Schema compatibility check (daily) +on: + schedule: + - cron: "0 9 * * *" # daily at 09:00 UTC + workflow_dispatch: # allow manual trigger + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-python@v6 + with: + python-version: "3.13" + + - run: pip install openhexa.sdk + + - run: pip install requests + + - name: Check schema compatibility + run: python scripts/check_schema_compatibility.py + + - name: Notify Slack on failure + if: failure() + uses: slackapi/slack-github-action@v2.1.0 + with: + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: incoming-webhook + payload: | + { + "channel": "#openhexa-alerts", + "text": "⚠️ GraphQL schema compatibility check failed.\nThe latest released OpenHEXA CLI has breaking changes against production.\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View run>" + } diff --git a/scripts/check_schema_compatibility.py b/scripts/check_schema_compatibility.py index 28f6fe92..db949eee 100644 --- a/scripts/check_schema_compatibility.py +++ b/scripts/check_schema_compatibility.py @@ -1,12 +1,13 @@ #!/usr/bin/env python """ -Check for breaking changes between the SDK's bundled GraphQL schema and live server schemas for our production and demo environments. +Check for breaking changes between the SDK's bundled GraphQL schema and the production server schema. -Exits 0 regardless of outcome. Emits ::warning:: annotations when running in GitHub Actions -so breaking changes are visible without affecting the job status. +Exits with code 1 when breaking changes are found. Emits ::warning:: annotations when running +in GitHub Actions so breaking changes are visible in the PR. """ import os +import sys from pathlib import Path import requests @@ -19,10 +20,7 @@ REPO_ROOT = Path(__file__).parent.parent SCHEMA_RELATIVE_PATH = BUNDLED_SCHEMA_PATH.relative_to(REPO_ROOT) -URLS = [ - "https://api.demo.openhexa.org", - "https://api.openhexa.org", -] +PRODUCTION_URL = "https://api.openhexa.org" def fetch_server_schema(graphql_url: str): @@ -39,37 +37,28 @@ def fetch_server_schema(graphql_url: str): return build_client_schema(body["data"]) -def check_url(stored_schema, url: str) -> list: - """Check breaking changes for a single server URL. Returns the list of breaking changes.""" - graphql_url = url.rstrip("/") + "/graphql/" - server_schema = fetch_server_schema(graphql_url) +def main(): + """Execute main function.""" + stored_schema = build_schema(BUNDLED_SCHEMA_PATH.read_text()) + server_schema = fetch_server_schema(f"{PRODUCTION_URL}/graphql/") breaking_changes = find_breaking_changes(stored_schema, server_schema) if breaking_changes: - print(f" ⚠️ {len(breaking_changes)} breaking change(s) detected:") + print(f"⚠️ {len(breaking_changes)} breaking change(s) detected:") for change in breaking_changes: - print(f" - {change.description}") + print(f" - {change.description}") if GITHUB_ACTIONS: - print(f"::warning file={SCHEMA_RELATIVE_PATH},line=1,title=GraphQL schema breaking change ({url})::{change.description}") - else: - print(" ✅ No breaking changes detected.") - return breaking_changes - - -def main(): - """Execute main function.""" - stored_schema = build_schema(BUNDLED_SCHEMA_PATH.read_text()) - - all_breaking_changes = [] - for url in URLS: - all_breaking_changes.extend(check_url(stored_schema, url)) - - if all_breaking_changes: + print( + f"::warning file={SCHEMA_RELATIVE_PATH},line=1,title=GraphQL schema breaking change ({PRODUCTION_URL})::{change.description}" + ) print( "\nThe server schema has diverged from openhexa/graphql/schema.generated.graphql." "\nUpdate the bundled schema by copying the latest schema from the OpenHEXA monorepo" " and re-running: python -m ariadne_codegen" ) + sys.exit(1) + else: + print("✅ No breaking changes detected.") if __name__ == "__main__": From 1a8e19955c946034b9cecc7f821f6cee9caa55d6 Mon Sep 17 00:00:00 2001 From: Bram Jans Date: Mon, 23 Feb 2026 13:14:38 +0100 Subject: [PATCH 6/6] fix: Typo --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e45b321..07327ca8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,7 @@ jobs: run: pytest --cov=. --cov-report html --cov-report term --cov-fail-under=25 schema-compatibility: - name: Check schema compatibility with demo server + name: Check schema compatibility with production server runs-on: ubuntu-latest if: startsWith(github.head_ref, 'release-please') steps: @@ -91,4 +91,4 @@ jobs: env: VERSION: ${{ env.VERSION }} run: | - conda build . --channel conda-forge --channel bioconda \ No newline at end of file + conda build . --channel conda-forge --channel bioconda