Skip to main content

CAP.L0.NOT_CAPTURABLE — No capturable SQL

  • Category: safety
  • Level: 0
  • Stability: stable
  • Suites: lint

The capture ran to completion but produced no SQL to lint. Before any rule can judge a migration's SQL, there has to be SQL — this is the lint suite's lowest assurance. A migration whose up() emits nothing (an empty method, one that calls no schema or DB builder) contributes nothing to a run, so it is reported rather than passed silently: a migration the run never actually checked is not a clean result.

This is distinct from an undetermined: a migration flagged by the pre-scan is never captured, so it never reaches this rule. CAP.L0.NOT_CAPTURABLE is exactly the case where capture was tried, succeeded, and got nothing.

When the emptiness is deliberate

A migration can be empty on one driver on purpose — a partial index is a PostgreSQL feature, a storage-engine change means nothing outside MySQL — and reporting that on every run is noise about a decision you already made.

Say so where the decision lives:

use Pushery\SQLens\Attributes\NoSqlOnDriver;

#[NoSqlOnDriver('sqlite', reason: 'partial indexes are a PostgreSQL feature; SQLite gets the full index')]
public function up(): void
{
if (DB::connection()->getDriverName() === 'sqlite') {
return;
}

// …
}

The driver you name is checked against the driver the run is on. Emptiness on a driver you did not name still reports, so the attribute cannot become a blanket switch that merely sits closer to the code — and a misspelled driver name excuses nothing anywhere, which is how the typo shows up instead of quietly disabling the check.

Put it on the class to cover both up() and down(), or on one method to cover only that one. It is repeatable, so a migration that is deliberately empty on two drivers can say both. The reason is required, and an empty one is treated as though the attribute were absent.

This is not the baseline, and the difference matters when somebody touches the file. A baseline entry is bound to a location and does not survive a rename, it lives somewhere other than the decision, and it keeps suppressing after the migration becomes empty by accident. The attribute moves with the code and stops applying the moment its condition stops holding.

Flagged

public function up(): void
{
// Nothing here emits SQL — the lint run has nothing to check,
// and a migration that checks nothing should not read as clean.
}

Preferred

public function up(): void
{
Schema::create('users', function (Blueprint $table): void {
$table->id();
$table->string('email');
});
}