Skip to main content

DEPLOY.CONTEXT.VERSION_SKEW — the version CI checked against is not the server this deploy meets

  • Category: safety
  • Severity: info
  • Level: 0
  • Downtime class: online — a deploy check reports on the STATE of a target, not on a DDL operation; nothing about a skew makes a deploy slower or locks anything, it makes the report wrong
  • Stability: stable
  • Suites: deploy
  • Applies to: PostgreSQL and MySQL — both engines pin the same way, so both are asked

What it reports

Whether the version SQLens reasoned about when it checked these migrations is the version the target server actually is.

The check opens nothing and reads no catalog view of its own. It is handed the two numbers it compares: the configured assume_server_version pin, and the version this run detected from the server it connected to — what the server actually is, read from it rather than assumed from configuration. The comparison answers in one of these ways:

What it foundAnswerSeverity of the finding
Pin and server agree on major and minorpass, no finding
Same major, different minorpass carrying a findinglow
Different majorfailhigh
No pin configured — missing, or empty once trimmedpass carrying a findinginfo
A pin this build cannot parseundetermined, with its reason
A server version this build could not readundetermined, with its reason

The finding is recorded against the connection's server version setting, not against a migration file: the subject is the target this deploy addresses, not the SQL about to run on it.

The severity in the header is the one the check is registered under in the catalog, and it is what a catalog listing or a severity filter sees. The severity a finding carries is the one in the table, chosen per answer — so judge a single result by the finding's own severity, never by the header.

Why it matters

CI lints against a pinned version, because a lint that asked the database would produce different answers on different days and stop being reproducible. That pin is the whole reason sqlens:lint is deterministic — and it is also a claim about a server CI never saw.

When the pin and the real instance disagree, every version-aware rule checked the wrong world. A rule that stayed silent because this is fixed in 18 said nothing about a 16 that is about to receive the migration. Nothing is red, nothing is missing, and the report is confidently wrong — which is worse than no report, because somebody acted on it.

That is why this is the first check in a deploy run rather than one among many. Every later check reasons about a server, and a run whose version is wrong has already answered every one of them against the wrong world.

Why a major skew fails and a minor skew does not

Minor versions do not move the rules. This package's version windows are declared in majors, so a pin of 18.1 against a real 18.4 changes no verdict. It is still worth saying — the pin is stale and will drift further — but it is a note, not a blocker, so it rides along on a pass.

A major skew is a different claim entirely. It means the version windows were evaluated against a server that does not exist here, and there is no way to know from the report alone which findings that changed. So the severity difference is not a gradient of the same thing; it is two different facts wearing one check id.

What to do about it

A different major — the case that blocks

The message names both versions. For a major skew it also names the version-aware rules the build declares — or, when the build declares none, says exactly that instead of listing anything. Then decide which of the two facts is the wrong one:

  • The pin is stale. Production moved and nobody moved the pin. Set assume_server_version to the major you deploy to, and re-run the lint so the report describes this server.
  • The target is not the server the pin describes. The pin is right and this deploy is pointed somewhere else. Point it at the server the pin names, or run the lint against this one.

Pin the real major, or run the lint against it — those are the two ways out, and the check does not choose between them for you.

// config/sqlens.php
'assume_server_version' => '18.0',

The format is strict: 18, 18.2 or 18.2.1, and nothing else, anchored at both ends. A pin is read exactly or refused with a named reason, never approximated — 18.x would otherwise be read as exactly 18.0, quietly narrower than the any 18 it means. The pin is also read as a single string: a configured value that is not one arrives at this check as no pin at all, so you get the no-pin note below instead of a comparison.

A different minor — the note

Nothing here stops the deploy and no verdict changed. Move the pin when it suits you; the reason it is reported at all is that a stale pin drifts further, and the next drift may be a major.

No pin at all

Running without a pin is a legitimate choice. It just means CI's answers moved with whatever server it happened to meet, and a lint result is only reproducible where the servers agree — the finding exists so that a person reading the report knows that, because nothing else in the report would have said so. Configure a pin if you want the run reproducible; leave it unset deliberately if you do not.

An answer of undetermined

Both undetermined shapes are honest refusals: an unreadable server version is not agreement, and a pin that cannot be read is worse than none because it looks like determinism and provides none. A deploy gate is fail-closed, so an undetermined result blocks. --allow-undetermined is the deliberate way out and opens exactly one door: a run whose only blockers could not answer proceeds. A fail — the major skew — still blocks.

What it does not claim

  • It does not re-check anything. It compares two version numbers and tells you what that means for the report you already have. No rule is re-evaluated against the real server here.
  • It cannot tell you which findings changed. For a major skew the message names the version-aware rules the build declares, and that is a list of what could have moved — the report alone cannot say which ones did. The list is the build's own declaration rather than anything derived from the rules that actually ran, so a build that declares none names nothing and says so.
  • A minor skew is not a wrong report. The low finding is about a stale pin, not about a verdict that changed, because this package's version windows are declared in majors.
  • An unread version is not a matching one. A server that did not answer with a version this build could read is reported undetermined with that reason, never as "no skew" — reporting it as agreement would be exactly the silent green the gate exists to refuse.
  • It cannot tell a deliberate pin from a stale one. It reports that the two numbers differ. Which of them is the one that should move is a decision it has no way to make for you.
  • It speaks about one instance. The comparison is against the server on the connection this run addresses; another instance behind the same deploy is not consulted here.
  • The downtime class says nothing about your migrations. The finding carries online because a skew blocks nothing and locks nothing. What it damages is the report, and that keeps the downtime axis meaning what it says.