Skip to content
This repository was archived by the owner on May 25, 2026. It is now read-only.

Commit fe19784

Browse files
feat(ci): schema-drift CI gate against live GitLab (DOT-559) (#112)
Closes Phase 4 of the response-schemas initiative (DOT-555). Adds a weekly GitHub Actions workflow plus a standalone Bun script that calls every response-schema-bearing GitLab REST endpoint and parses each response against its declared Zod schema with .parse() (strict). Phase 1 deliberately made runtime parsing lenient (.safeParse + log + pass-through) so users aren't blocked by minor drift; this is the strict counterpart that catches API drift in CI. The script auto-discovers fixture IDs from list endpoints — no per-endpoint env var sprawl. Re-uses loadConfig() and GitLabClient from the runtime. Required env vars: GITLAB_API_URL, GITLAB_PERSONAL_ACCESS_TOKEN, GITLAB_PROJECT_ID.
1 parent e27d574 commit fe19784

4 files changed

Lines changed: 420 additions & 1 deletion

File tree

.github/workflows/schema-drift.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: Schema Drift
2+
3+
# Phase 4 of the response-schemas initiative (DOT-555 / DOT-559).
4+
# Calls real GitLab against every response-schema-bearing endpoint and
5+
# fails the workflow on any Zod schema mismatch — catches GitLab API
6+
# drift the moment it ships, not the moment a user trips over it.
7+
#
8+
# Required repo secrets:
9+
# GITLAB_API_URL — e.g. https://gitlab.com
10+
# GITLAB_PERSONAL_ACCESS_TOKEN — read_api scope only
11+
# GITLAB_PROJECT_ID — fixture project with at least one
12+
# MR, commit, issue, and pipeline.
13+
14+
on:
15+
schedule:
16+
- cron: "0 6 * * 1" # Mondays 06:00 UTC
17+
workflow_dispatch:
18+
19+
concurrency:
20+
group: ${{ github.workflow }}
21+
cancel-in-progress: false
22+
23+
permissions:
24+
contents: read
25+
26+
jobs:
27+
drift:
28+
runs-on: blacksmith-4vcpu-ubuntu-2404
29+
30+
steps:
31+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
32+
33+
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
34+
with:
35+
bun-version: latest
36+
37+
- name: Install dependencies
38+
run: bun install --frozen-lockfile
39+
40+
- name: Check schema drift
41+
env:
42+
GITLAB_API_URL: ${{ secrets.GITLAB_API_URL }}
43+
GITLAB_PERSONAL_ACCESS_TOKEN: ${{ secrets.GITLAB_PERSONAL_ACCESS_TOKEN }}
44+
GITLAB_PROJECT_ID: ${{ secrets.GITLAB_PROJECT_ID }}
45+
LOG_LEVEL: warn
46+
run: bun run scripts/check-schema-drift.ts

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,10 @@ It's [redacted by default](#secret-redaction) for safety. To get it back, pass `
504504

505505
For `list_issues`, `list_merge_requests`, etc.: GitLab's global endpoints (when no `project_id` is supplied) historically defaulted to `scope: created_by_me`. To see everything, pass `scope: "all"` explicitly. If you supply `project_id`, the call routes to the project-scoped endpoint and this default doesn't apply.
506506

507+
### `GitLab response failed schema validation` in MCP server logs
508+
509+
GitLab responses on the server's schematized read endpoints (users, projects, merge requests, commits, issues, pipelines, repository tree) run through a Zod schema. On a mismatch the server logs `WARN GitLab response failed schema validation; passing through unchanged` with the field path and Zod error code, then passes the response on to your LLM unchanged — so the call still succeeds, but you've got a signal that GitLab returned a shape we don't know about. Causes are usually GitLab API drift (new field, type change, removal) or a self-hosted EE instance returning EE-only fields. If you see one of these warnings, [open an issue](https://github.com/detailobsessed/efficient-gitlab-mcp/issues) with the `path`, `code`, and which tool triggered it — that's our cue to update the schema.
510+
507511
---
508512

509513
## Development
@@ -525,6 +529,27 @@ bun run check
525529
bun run build
526530
```
527531

532+
### Schema-drift CI
533+
534+
The runtime path through `parseGitLabResponse` is intentionally lenient (`.safeParse()` + log warning + pass through) so an unexpected GitLab field never blocks an MCP tool call. The drift gate is the strict counterpart: a Bun script that calls every response-schema-bearing GitLab REST endpoint and `.parse()`s each response against its declared Zod schema, failing on any mismatch.
535+
536+
Run it locally:
537+
538+
```bash
539+
export GITLAB_API_URL=https://gitlab.com
540+
export GITLAB_PERSONAL_ACCESS_TOKEN=glpat-... # read_api scope only
541+
export GITLAB_PROJECT_ID=12345 # must have ≥1 MR/commit/issue/pipeline
542+
bun run drift
543+
```
544+
545+
When to run it manually:
546+
547+
- **Before merging a PR that touches `src/schemas/`** — catches schema bugs against a real instance, complementing the fixture-based unit tests.
548+
- **After upgrading a self-hosted GitLab** — quick sanity check that nothing in the response shape moved.
549+
- **Triaging suspicious LLM behavior** — if responses look wrong but the tool returned OK, a schema mismatch silently passed through; drift check confirms or rules that out.
550+
551+
The same script runs in CI via `.github/workflows/schema-drift.yml``schedule` Mondays 06:00 UTC and `workflow_dispatch` on demand. The same three env vars are wired as repo secrets.
552+
528553
---
529554

530555
## Upstream Tracking

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
"dev": "bun run build && bun start",
3434
"test": "bun test",
3535
"check": "biome check --write",
36-
"clean": "rm -rf dist"
36+
"clean": "rm -rf dist",
37+
"drift": "bun run scripts/check-schema-drift.ts"
3738
},
3839
"dependencies": {
3940
"@modelcontextprotocol/sdk": "^1.29.0",

0 commit comments

Comments
 (0)