Understanding undetermined
Every SQLens result is one of three values: pass, fail, or undetermined. 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 three-valued 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.
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.