Understanding `undetermined`
Every SQLens result is one of four values: pass, fail, undetermined, or
not_applicable. The first two are familiar. The third is the one that makes the tool
trustworthy, and it is worth a page of its own — the fourth exists so that the third
keeps meaning what it says.
The model
A check that cannot run is never reported as a pass. If SQLens could not obtain a
migration's SQL, could not reach the server, or could not determine the server
version, it says so — as an undetermined result with a named reason — instead of
staying quiet and letting the run look clean. A green run that never actually
checked something is the most expensive kind of false confidence there is, so
SQLens does not produce one. A skip without a reason is a bug, not a feature.
An undetermined is not a failure and it is not your mistake. It means SQLens was
honest about a limit rather than guessing past it.
not_applicable — the check that had nothing to run against
An undetermined check wanted to run and could not. A not_applicable check had
nothing to look at in the first place: MySQL has no row-level security and no
pg_hba.conf, so the rules that judge those have no subject on that engine. A managed
provider that terminates TLS at its own proxy makes the server's ssl setting say
nothing about whether your connections are encrypted.
Those used to be reported as silence, and silence is the problem. A reader who sees no row-level-security finding concludes that tenant separation was examined and found in order. It was not examined at all.
The distinction is not bookkeeping. It decides your exit code:
--strict escalates it | In the baseline | In the summary | |
|---|---|---|---|
undetermined | yes — a question you left open can stop a deploy | no | its own count |
not_applicable | no | no | its own count, beside undetermined |
Escalating a not_applicable would put a line in your pipeline that no change of yours
can ever remove: your MySQL server is not going to grow row-level security. So
--strict leaves it alone, and the summary counts it separately, so "7 undetermined"
never quietly includes checks that were never applicable.
There is nothing to do about a not_applicable. It is there so you know what was not
looked at, and so that its absence from the report cannot be read as a pass.
The pre-scan is deliberately conservative
Most undetermined results in the lint suite come from the static pre-scan, which
reads a migration before it is captured and flags anything pretend mode cannot see
truthfully — a side effect it would fire, a query result it depends on, an
introspection guard, a call into your own code. The pre-scan is deliberately
conservative: it would rather flag a migration you know is fine than let one slip
through that quietly does something pretend could not observe. A flag is not an
accusation. It is SQLens saying "I cannot be sure about this one in pretend mode",
and the reasons below are how you make it sure.
A stated honesty limit. The pre-scan is syntactic pattern recognition, not
taint or data-flow analysis. It sees one level: it recognizes a call, but it does
not follow the call into the code it reaches. That is most visible in
CAP.PRESCAN.INDIRECT_CALL, which flags a
migration that reaches its effect through your own code — (new Backfill)->run() —
without knowing what run() does. It flags conservatively because the alternative
is a silent pass exactly where a real side effect could hide. When you know a class
is safe, the allowlist is the intended way to say so.
The three ways to resolve one
- Run it in shadow mode. Shadow mode executes the migration for real against a
disposable database, so the empty-result and guarded-block limits of pretend mode
disappear. This is the truth mode, and it is what most
undeterminedreasons point you to. See Capture modes. - Change the migration so pretend can capture it. Often the cleaner fix: move a data backfill out of the migration and into a job the deploy dispatches, or drop an introspection guard that is not actually needed. The migration then captures whole in pretend mode.
- Suppress it deliberately. If you have decided a flagged migration is fine — a helper you know is harmless, a pattern you accept — record that decision as a suppression. A suppression is a documented choice, and it is always shown as such in the report, so it never looks like an ordinary clean result.
The one thing you should never do is treat an undetermined as a pass by ignoring
it. It is the tool telling you where it could not see; the reasons above are how you
give it sight.