From 8d54ff6ca076ccb42b5b3adfdac3a29659312874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Tue, 12 May 2026 08:43:35 +0200 Subject: [PATCH] fix(ci): probe real schema in breaking-changes server readiness check (#20465) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary The `GraphQL and OpenAPI Breaking Changes Detection` workflow has been posting graphql-inspector stack traces as PR comments — see [#20445 comment](https://github.com/twentyhq/twenty/pull/20445#issuecomment-4421142635) for an example. ### Root cause - The wait step probed readiness with `curl -s URL > /dev/null 2>&1`, which exits 0 for **any** HTTP response — including 5xx and GraphQL error JSON. NestJS opens the HTTP listener before the workspace schema cache is fully populated, so the wait often completed while the server still served auth/metadata error JSON. - The introspection download therefore wrote a small (~154-byte) error payload instead of the real schema. `jq empty` in the validation step only checks JSON *syntax*, so `{"errors":[...]}` passed validation. - `graphql-inspector diff` then failed with `Unable to read JSON file: ... Not valid JSON content`, the workflow swallowed the error into the diff markdown, and the bot posted that stack trace verbatim on the PR. In the failing run, the main-branch files were 154 B (GraphQL) and 112 B (REST 500); the current-branch files in the same run were 600 KB–2.8 MB. ### Fix - Wait steps now POST an authenticated introspection (`{ __schema { queryType { name } } }`) and require `.data.__schema` plus a 2xx response from `/rest/open-api/core` (`curl -f`) before declaring the server ready. - Validation step now checks for the expected shape (`.data.__schema` for GraphQL, `.openapi`/`.swagger` for OpenAPI) and includes the first 200 bytes of any bad payload in the warning, so when something genuinely goes wrong the next debugger has a real lead instead of a generic stack trace. ## Test plan - [ ] CI runs against this branch — the workflow's own readiness probes are now exercised against the real server, so a green run validates the new check. - [ ] If the readiness probe still passes but downloads regress, the strengthened validation step will surface the payload in the workflow logs instead of posting a graphql-inspector stack trace on the PR. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- .github/workflows/ci-breaking-changes.yaml | 50 ++++++++++++++++------ 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci-breaking-changes.yaml b/.github/workflows/ci-breaking-changes.yaml index 2876ea41940..fe1c4c27c26 100644 --- a/.github/workflows/ci-breaking-changes.yaml +++ b/.github/workflows/ci-breaking-changes.yaml @@ -164,9 +164,17 @@ jobs: interval=5 elapsed=0 + ADMIN_TOKEN=$(jq -r '.APPLE_JANE_ADMIN_ACCESS_TOKEN' packages/twenty-server/test/integration/constants/test-tokens.json) + while [ $elapsed -lt $timeout ]; do - if curl -s "http://localhost:${{ env.CURRENT_SERVER_PORT }}/graphql" > /dev/null 2>&1 && \ - curl -s "http://localhost:${{ env.CURRENT_SERVER_PORT }}/rest/open-api/core" > /dev/null 2>&1; then + GRAPHQL_RESPONSE=$(curl -s -X POST "http://localhost:${{ env.CURRENT_SERVER_PORT }}/graphql" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${ADMIN_TOKEN}" \ + -d '{"query":"{ __schema { queryType { name } } }"}' 2>/dev/null || echo '{}') + + if echo "$GRAPHQL_RESPONSE" | jq -e '.data.__schema' > /dev/null 2>&1 && \ + curl -fsS "http://localhost:${{ env.CURRENT_SERVER_PORT }}/rest/open-api/core" \ + -H "Authorization: Bearer ${ADMIN_TOKEN}" > /dev/null 2>&1; then echo "Current branch server is ready!" break fi @@ -177,10 +185,9 @@ jobs: done if [ $elapsed -ge $timeout ]; then - echo "Timeout waiting for current branch server to start" + echo "::warning::Timed out waiting for current branch server to serve a valid schema. Validation will skip the API diff." echo "Current server log:" cat /tmp/current-server.log || echo "No current server log found" - exit 1 fi - name: Download GraphQL and REST responses from current branch @@ -324,9 +331,17 @@ jobs: interval=5 elapsed=0 + ADMIN_TOKEN=$(jq -r '.APPLE_JANE_ADMIN_ACCESS_TOKEN' packages/twenty-server/test/integration/constants/test-tokens.json) + while [ $elapsed -lt $timeout ]; do - if curl -s "http://localhost:${{ env.MAIN_SERVER_PORT }}/graphql" > /dev/null 2>&1 && \ - curl -s "http://localhost:${{ env.MAIN_SERVER_PORT }}/rest/open-api/core" > /dev/null 2>&1; then + GRAPHQL_RESPONSE=$(curl -s -X POST "http://localhost:${{ env.MAIN_SERVER_PORT }}/graphql" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${ADMIN_TOKEN}" \ + -d '{"query":"{ __schema { queryType { name } } }"}' 2>/dev/null || echo '{}') + + if echo "$GRAPHQL_RESPONSE" | jq -e '.data.__schema' > /dev/null 2>&1 && \ + curl -fsS "http://localhost:${{ env.MAIN_SERVER_PORT }}/rest/open-api/core" \ + -H "Authorization: Bearer ${ADMIN_TOKEN}" > /dev/null 2>&1; then echo "Main branch server is ready!" break fi @@ -337,10 +352,9 @@ jobs: done if [ $elapsed -ge $timeout ]; then - echo "Timeout waiting for main branch server to start" + echo "::warning::Timed out waiting for main branch server to serve a valid schema. Validation will skip the API diff." echo "Main server log:" cat /tmp/main-server.log || echo "No main server log found" - exit 1 fi - name: Download GraphQL and REST responses from main branch @@ -407,11 +421,23 @@ jobs: valid=true for file in main-schema-introspection.json current-schema-introspection.json \ - main-metadata-schema-introspection.json current-metadata-schema-introspection.json \ - main-rest-api.json current-rest-api.json \ + main-metadata-schema-introspection.json current-metadata-schema-introspection.json; do + if [ ! -f "$file" ]; then + echo "::warning::Missing GraphQL schema file: $file" + valid=false + elif ! jq -e '.data.__schema' "$file" >/dev/null 2>&1; then + echo "::warning::File $file is not a valid GraphQL introspection result. First 200 bytes: $(head -c 200 "$file")" + valid=false + fi + done + + for file in main-rest-api.json current-rest-api.json \ main-rest-metadata-api.json current-rest-metadata-api.json; do - if [ ! -f "$file" ] || ! jq empty "$file" 2>/dev/null; then - echo "::warning::Invalid or missing schema file: $file" + if [ ! -f "$file" ]; then + echo "::warning::Missing OpenAPI spec file: $file" + valid=false + elif ! jq -e '.openapi // .swagger' "$file" >/dev/null 2>&1; then + echo "::warning::File $file is not a valid OpenAPI spec. First 200 bytes: $(head -c 200 "$file")" valid=false fi done