Skip to content

[Infra] Automated schema.prisma sync and drift detection#24705

Merged
yuneng-berri merged 4 commits intomainfrom
litellm_auto_schema_sync
Mar 28, 2026
Merged

[Infra] Automated schema.prisma sync and drift detection#24705
yuneng-berri merged 4 commits intomainfrom
litellm_auto_schema_sync

Conversation

@yuneng-berri
Copy link
Copy Markdown
Collaborator

@yuneng-berri yuneng-berri commented Mar 27, 2026

Summary

Problem

schema.prisma exists in 3 locations that must stay identical. Engineers manually sync these and frequently forget, causing schema drift. This drift has led to issues when publishing litellm-proxy-extras — the published package schema doesn't match the proxy schema. The current publish workflow also depends on long-lived PyPI tokens and manual version bumping.

This PR is the first step toward automating litellm-proxy-extras releases: ensuring schema consistency so that automated publishes are safe.

Changes

  • Synced all 3 schema.prisma files to match litellm/proxy/schema.prisma (the most up-to-date version), adding source_url, BYOM lifecycle fields, and @@index([approval_status])
  • Added sync-schema.yml — auto-copies root schema.prisma to the other 2 locations when a PR modifies the root schema
  • Added check-schema-sync.yml — CI gate that fails if any of the 3 schemas diverge, blocking merge
  • Mirrored source-only changes from litellm_table_patch (excluding build artifacts — build binaries will be handled by the upcoming automated publish workflow):
    • Removed destructive DROP COLUMN migration (20260311180521_schema_sync)
    • Bumped litellm-proxy-extras 0.4.60 → 0.4.61
    • Added regression test to block future DROP COLUMN migrations
    • Fixed double error handling in getTeamPermissionsCall

Security measures

These workflows were audited for supply chain risks. Hardening applied:

  • No tokens: both workflows use only the default GITHUB_TOKEN — no PATs, no PyPI tokens, no secrets
  • SHA-pinned actions: actions/checkout pinned to full 40-char SHA, preventing tag-mutation attacks
  • Checkout by commit SHA: sync-schema.yml checks out github.event.pull_request.head.sha instead of github.head_ref to prevent ref injection via crafted branch names
  • Protected branch guard: rejects PRs from branches named main/master to prevent the sync workflow from pushing to protected branches
  • Symlink guard: both workflows reject symlinked schema files, preventing an attacker from bypassing the diff check by symlinking one schema to another
  • Fork PR exclusion: sync workflow only runs on same-repo PRs (fork PRs have read-only tokens and can't push back)
  • Scoped git add: only stages the 2 specific copy files, never git add -A
  • Read-only check workflow: check-schema-sync.yml has contents: read only — zero write permissions
  • Minimal permissions: sync-schema.yml uses contents: write (minimum required for git push), no other permissions granted
  • No build artifacts from incident period: litellm_table_patch changes were cherry-picked as source only, excluding binaries

Recommended repo setting

Enable "Dismiss stale pull request approvals when new commits are pushed" on main branch protection to ensure the auto-sync commit triggers re-review.

Testing

  • Verify all 3 schema files are identical after merge
  • Open a test PR that modifies only schema.prisma — confirm sync-schema.yml auto-copies to the other 2
  • Open a test PR that modifies litellm/proxy/schema.prisma without updating root — confirm check-schema-sync.yml fails
  • Mark check-schema-sync as a required status check on main

Type

🚄 Infrastructure
✅ Test

Sync all 3 schema.prisma copies and add GHA workflows to keep them in sync automatically.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Mar 27, 2026 11:48pm

Request Review

@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq bot commented Mar 27, 2026

Merging this PR will not alter performance

✅ 16 untouched benchmarks


Comparing litellm_auto_schema_sync (a074d1d) with main (d949085)

Open in CodSpeed

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 27, 2026

Greptile Summary

This PR introduces automated schema.prisma sync and drift detection: a new CI gate (check-schema-sync.yml) blocks merges when the three schema copies diverge, and a new auto-sync workflow (sync-schema.yml) commits the root schema to the other two locations when a PR touches it. The three schema files are also manually synced to match litellm/proxy/schema.prisma — adding source_url, BYOM lifecycle fields, and @@index([approval_status]) to the root and proxy-extras schemas — and litellm-proxy-extras is bumped to 0.4.61. A new CI test enforces no DROP COLUMN in future migrations.

Key findings:

  • Deleted migration without a migration-history safety net (P1): 20260311180521_schema_sync/migration.sql is removed. If any deployment already applied this migration with litellm-proxy-extras==0.4.60, Prisma will refuse all future prisma migrate deploy runs with a history-conflict error. The existing March-9 forward migrations (20260309000000 / 20260309000001) that re-add the dropped columns via IF NOT EXISTS will never execute while the history conflict is unresolved. A neutralized placeholder file or a compensating forward migration is needed.
  • Silent permission-fetch failure (P2): getTeamPermissionsCall in networking.tsx now returns empty arrays on API error instead of calling handleError, silently hiding failures from the user.

Confidence Score: 4/5

Safe to merge only after confirming that litellm-proxy-extras 0.4.60 was never deployed to a database that ran prisma migrate deploy; the deleted migration file creates an unrecoverable Prisma history conflict for any such deployment.

One P1 remains: the deleted migration 20260311180521_schema_sync will break prisma migrate deploy for any deployment that applied it under 0.4.60. The PR author should either confirm the bad migration was never applied in production or add a remediation path (placeholder file or compensating migration) before merging.

litellm-proxy-extras/litellm_proxy_extras/migrations/20260311180521_schema_sync/migration.sql (deleted) — primary concern.

Important Files Changed

Filename Overview
litellm-proxy-extras/litellm_proxy_extras/migrations/20260311180521_schema_sync/migration.sql Deleted migration that contained DROP COLUMN statements; deletion without a compensating migration or neutralized placeholder will cause prisma migrate deploy to fail for any existing 0.4.60 deployment.
.github/workflows/check-schema-sync.yml New CI gate that diffs all three schema.prisma copies against root; uses read-only permissions, SHA-pinned checkout, and symlink guard — well-hardened.
.github/workflows/sync-schema.yml New auto-sync workflow that copies root schema on PR; prior threads cover the GITHUB_HEAD_REF injection risk and incomplete protected-branch guard — not re-flagged here.
litellm-proxy-extras/litellm_proxy_extras/schema.prisma Synced to root schema — adds source_url, changes approval_status to nullable with "active" default, adds @@index; covered by existing March-9 forward migrations that use IF NOT EXISTS.
schema.prisma Root schema now matches litellm/proxy/schema.prisma — adds source_url, approval_status (String? default "active"), BYOM lifecycle fields, and @@index([approval_status]) to LiteLLM_MCPServerTable.
tests/litellm-proxy-extras/test_litellm_proxy_extras_utils.py Additive test: new test_no_drop_column_statements enforces no DROP COLUMN in new migrations, with an allowlist for three pre-existing migrations; strengthens coverage without weakening existing tests.
ui/litellm-dashboard/src/components/networking.tsx getTeamPermissionsCall now silently returns empty permissions on API failure instead of calling handleError, hiding failures from the user.
litellm-proxy-extras/pyproject.toml Version bump from 0.4.60 to 0.4.61; both tool.poetry and tool.commitizen updated consistently.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[PR opens / schema.prisma touched] --> B{sync-schema.yml\nfork PR?}
    B -- fork --> C[Skip: read-only token]
    B -- same-repo --> D{Branch named\nmain/master?}
    D -- yes --> E[Skip: protected branch guard]
    D -- no --> F[Checkout by head SHA]
    F --> G[Symlink guard check]
    G -- symlink found --> H[Fail: symlink not allowed]
    G -- ok --> I[cp root schema to\nproxy + proxy-extras]
    I --> J{Git diff:\nany changes?}
    J -- no change --> K[Skip commit]
    J -- changed --> L[git checkout -B branch\ngit commit + push]

    A2[PR opens / any schema file touched] --> M[check-schema-sync.yml]
    M --> N[Symlink guard check]
    N -- symlink found --> O[Fail]
    N -- ok --> P{diff root vs\nproxy schema}
    P -- diverged --> Q[Fail: schemas out of sync]
    P -- identical --> R[Pass: all in sync]
Loading

Comments Outside Diff (1)

  1. litellm-proxy-extras/litellm_proxy_extras/migrations/20260311180521_schema_sync/migration.sql

    P1 Deleted migration will break prisma migrate deploy for existing 0.4.60 deployments

    The migration 20260311180521_schema_sync is being deleted, but if any deployment already ran prisma migrate deploy with litellm-proxy-extras==0.4.60, this migration is recorded in the _prisma_migrations table. Prisma will refuse to run any future migrations with:

    "The migration 20260311180521_schema_sync is recorded in the database but not found on the file system."

    Even though 20260309000000_add_mcp_approval_status and 20260309000001_add_mcp_source_url use ADD COLUMN IF NOT EXISTS to re-add the same columns, Prisma will not even attempt those migrations while the history conflict exists — blocking all future schema rollouts for affected deployments.

    The safer approaches are either:

    1. Keep the file but neutralize it — replace the body with a no-op comment so Prisma history remains consistent:
      -- This migration contained erroneous DROP COLUMN statements and has been
      -- superseded. It is kept as a no-op to preserve migration history integrity.
    2. Ship a forward migration — add a new migration that re-adds the dropped columns with ADD COLUMN IF NOT EXISTS so deployments that applied the bad migration can recover automatically.

    If 0.4.60 was never deployed to any database (i.e., the package was never published or never had migrations applied), please document that explicitly so reviewers can confirm it is safe to delete.

Reviews (4): Last reviewed commit: "[Infra] Mirror litellm_table_patch sourc..." | Re-trigger Greptile

byok_api_key_help_url String?
source_url String?
// BYOM submission lifecycle
approval_status String? @default("active")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Breaking default value change will silently drop existing MCP servers

The approval_status default changed from "approved" to "active", and the type changed from non-nullable String to nullable String?. Any existing LiteLLM_MCPServerTable rows that were created with the old default ("approved") will not be returned by the active-server query in mcp_server_manager.py:

# litellm/proxy/_experimental/mcp_server/mcp_server_manager.py:2425
db_mcp_servers = await get_all_mcp_servers(prisma_client, approval_status="active")

After this migration, all previously-active servers (with approval_status = "approved") will be silently absent from the runtime registry, making them unreachable without any error surfaced to the admin. A data migration is needed alongside this schema change, e.g.:

UPDATE "LiteLLM_MCPServerTable"
SET approval_status = 'active'
WHERE approval_status = 'approved';

Without this migration, existing deployments that already have MCP servers in the DB will lose them after upgrading.

Comment on lines +69 to +72
git checkout -B "$GITHUB_HEAD_REF"
git add -- litellm/proxy/schema.prisma litellm-proxy-extras/litellm_proxy_extras/schema.prisma
git commit -m "chore: sync schema.prisma copies from root"
git push origin "HEAD:$GITHUB_HEAD_REF"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 GITHUB_HEAD_REF usage contradicts the checkout-by-SHA rationale

The PR description (and the inline comment at line 33-35) explains that github.head_ref is "an attacker-controlled string (the branch name) and could contain unusual characters that cause unexpected git behavior" — which is why the checkout uses the head SHA. However, the commit step then uses $GITHUB_HEAD_REF directly in shell commands:

git checkout -B "$GITHUB_HEAD_REF"
git push origin "HEAD:$GITHUB_HEAD_REF"

Double quotes prevent shell word-splitting injection, but git still receives the raw branch name as the refspec argument for the push. A branch name with characters that git interprets specially (e.g. .., @{, leading -) could cause unexpected git behavior or workflow failure. Consider validating the branch name before use, e.g.:

# validate: only allow safe branch name characters
if [[ ! "$GITHUB_HEAD_REF" =~ ^[a-zA-Z0-9/_.-]+$ ]]; then
  echo "::error::Unsafe branch name: $GITHUB_HEAD_REF"
  exit 1
fi
git checkout -B "$GITHUB_HEAD_REF"
git push origin "HEAD:$GITHUB_HEAD_REF"

This is consistent with the security rationale already articulated for the SHA-based checkout.

Comment on lines +24 to +27
if: >-
github.event.pull_request.head.repo.full_name == github.repository
&& github.head_ref != 'main'
&& github.head_ref != 'master'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Protected-branch guard is incomplete — only covers main and master

The guard prevents sync-schema.yml from pushing to PRs from branches named exactly main or master:

&& github.head_ref != 'main'
&& github.head_ref != 'master'

However, other commonly-protected branch names (e.g. develop, release, staging, or repo-specific patterns like release/v*) are not covered. If those branches are also protected in this repository, a PR opened from them would still trigger the sync workflow and attempt a forced push. The actual last line of defense is GitHub's branch-protection rules, but the workflow comment implies the guard itself is the safeguard. Consider either expanding the list or relying solely on branch protection.

Comment on lines +3 to +9
on:
pull_request:
paths:
- 'schema.prisma'
- 'litellm/proxy/schema.prisma'
- 'litellm-proxy-extras/litellm_proxy_extras/schema.prisma'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 CI gate won't fire if none of the 3 schema files are touched

The check-schema-sync.yml workflow uses a paths filter, so it only runs when at least one of the three schema files is modified. A PR that adds Python code relying on the new source_url column (or any other schema field) without touching any schema file would skip the check entirely.

If the intent is to guarantee all three schemas are always identical on every PR (not just when schemas are changed), consider either:

  • Removing the paths filter so the check runs on every PR, or
  • Documenting explicitly that the gate only applies to schema-touching PRs, and relying on the sync-schema.yml auto-commit to keep them in sync.

Add persist-credentials: false to check-schema-sync (read-only, no push needed).
Explicitly set persist-credentials: true on sync-schema (required for git push).

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
The schema sync adopted the proxy version which includes source_url,
approval_status, and other BYOM fields. These were previously dropped
in migration 20260311180521 due to schema drift. This migration
restores them to match the now-unified schema.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Cherry-pick source-only changes from litellm_table_patch, excluding
build artifacts from the incident response period.

- Remove destructive DROP COLUMN migration (20260311180521_schema_sync)
- Remove now-unnecessary restore migration (20260327232350)
- Bump litellm-proxy-extras 0.4.60 → 0.4.61
- Add regression test to block future DROP COLUMN migrations
- Fix double error handling in getTeamPermissionsCall

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@yuneng-berri yuneng-berri merged commit fe080a8 into main Mar 28, 2026
42 of 88 checks passed
@yuneng-berri yuneng-berri deleted the litellm_auto_schema_sync branch March 28, 2026 00:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants