CAP.PRESCAN.INDIRECT_CALL — Indirect call from a migration
- Category: safety
- Level: 0
- Stability: preview
- Suites: lint
This migration reaches its effect through your own code — (new Backfill)->run(), app(Importer::class)->handle(), or a plain new Backfill() whose constructor does the work — the most common way around the side-effect catalog. The call resolves cleanly and appears in no framework catalog, so left alone it would be a silent pass while the effect happens one level below.
The pre-scan is pattern recognition, not taint analysis: it flags the call without following it into the class, because it cannot know whether run() sends a notification or formats a string. It flags conservatively because an undetermined costs you a shadow run while a silent pass could cost a sent message. A constructor counts as a call wherever it stands, an argument included, because it runs your code as much as a method does. A call on the migration's own method ($this->helper()), the framework, pure value classes (Carbon, DateTime, DateTimeZone, Uuid, stdClass) and an object built to be thrown are not flagged. When you know a class is safe, add it to sqlens.capture.prescan.indirect_calls.allowlist — that is the intended way to state a known harmlessness.
Preview, and what that means here
This check is preview, so a run with the shipped 'stability' => [] does not run it. Such a run executes a call into your own code in pretend mode, as it is written, whatever that code does: the pre-scan does not hold it back. A run's header names the tiers it admitted: admitted_stability reads ["stable"] when this check did not run. If your migrations call into your own code, admit the tier with 'stability' => ['preview'], or capture those migrations in shadow mode against a throwaway database.
Flagged
public function up(): void
{
Schema::table('users', fn (Blueprint $t) => $t->string('slug')->nullable());
// run() might send a notification or hit an API — pretend runs it
// for real and the capture cannot see what it does.
(new UserBackfill)->run();
}
Preferred
public function up(): void
{
Schema::table('users', fn (Blueprint $t) => $t->string('slug')->nullable());
}
// The backfill is a job the deploy dispatches after the migration.
// Shadow mode holds a flagged call back as well. If the class is
// known-safe, add it to
// sqlens.capture.prescan.indirect_calls.allowlist.