Skip to main content

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 itIn the baselineIn the summary
undeterminedyes — a question you left open can stop a deploynoits own count
not_applicablenonoits 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

  1. 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 undetermined reasons point you to. See Capture modes.
  2. 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.
  3. 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.