illuminated/console-mutex
Prevent overlapping Laravel console commands with a simple mutex. This package adds atomic locking to Artisan commands so scheduled or manual runs never execute concurrently, helping avoid duplicate work and race conditions.
Architecture fit. Unchanged. The package still does one thing: it puts an atomic lock around the execution of a Laravel console command so two copies of the same command can't run at once. It remains a thin decorator over two stable Laravel contracts — the console command lifecycle and Illuminate\Contracts\Cache\LockProvider (i.e. Cache::lock()). No service provider of consequence, no queue, no daemon, no schema, no new infrastructure. It composes into an existing command via a trait, so it does not force a base-class inheritance decision. The blast radius is still one command class at a time, which is the property that matters most: incremental adoption, per-command backout.
Integration feasibility. Still high, with one new caveat created by this release. 13.0.0's only changelog entry is "Laravel 13 support," but the major version bump is the signal: in practice these packages move the illuminate/* constraint floor when they add a new major, which means older Laravel majors drop out of the supported matrix even though the changelog reads as purely additive. So the effort is unchanged for a team already on Laravel 13 (composer bump, one use statement), and blocked for a team on an earlier major, who must stay on 12.x. The real work remains policy — deciding what happens when a second instance loses the race — not wiring.
Technical risk.
withoutOverlapping(), and Cache::lock() is first-class. The genuine differentiator remains narrow but real: commands invoked outside the scheduler — manual artisan runs, commands dispatched from jobs, multi-scheduler-instance setups, deploy hooks, ad-hoc operator invocations. If the scheduler is your only overlap risk, this package is redundant.Key questions.
Stack fit. Unchanged: a conventional Composer-installed Laravel package, no PHP extensions, no PECL, no Node, no sidecar. The one thing this release changes is that the composer.json constraint check is no longer a formality — with a major bump it is the sole gate on whether the package installs at all against your pinned framework version.
Migration path. Purely additive at the code level: no data migration, no schema change, no config file to publish, no behavior to unwind, and no new API surface introduced by 13.0.0. The "migration" for this release is an upgrade-order decision, not a refactor. The policy refactor described below still stands as the substantive work: deciding which commands become mutually exclusive and what they do when they lose the race. Commands already covered by withoutOverlapping() can stay as they are; the package's job is the set the scheduler doesn't cover.
Compatibility. Two gates now, not one. The first is unchanged: the lock-capable cache store — confirm Redis/Memcached/DynamoDB/database is available in every environment including CI and local dev, because a developer on the file store sees different behavior than production and that is a debugging trap. The second is new: Laravel 13. Confirm the exact illuminate/* / php constraints in 13.0.0's composer.json against our pinned versions rather than trusting the changelog, and confirm that lock semantics in Laravel 13's cache contract are unchanged (they should be — Cache::lock() has been stable — but this is a one-line verification, not an assumption). Also confirm behavior when the cache backend is unreachable: acquisition fails, and you must decide whether to fail closed (safety) or fail open (availability).
Sequencing.
Cache::lock() is a half-day exercise, so the package is never a one-way door.Maintenance. Low ongoing cost, and marginally lower than before: a maintainer who ships a Laravel 13-compatible major on the framework's release cadence is demonstrating the specific maintenance behavior this dependency needs. Ownership is still real — single maintainer, near-zero dependent base, no foundation or corporate backing. Treat it as vendored utility code that happens to arrive via Composer. Pin the version, read the diff before bumping (small enough to actually do this), and do not let it drift on a loose constraint — a loose constraint is precisely how you'd get pulled onto a future major that drops your Laravel version without a code change on your side.
Support. GitHub issues only. No commercial support, no SLA, no security disclosure process worth relying on. Our own team is the support tier — fine, provided that is stated up front rather than discovered during an incident.
Scaling. Unchanged. One cache round trip to acquire, one to release — negligible next to any command worth locking. It scales horizontally if and only if the cache is shared: every host that can execute the command must talk to the same lock store. Multi-region or multi-cluster deployments need a deliberately shared lock backend, or you get per-region mutual exclusion and call it global. Lock keys are per-command by default, so no contention storm between unrelated commands.
Failure modes, roughly in order of how much they should worry you:
composer update either fails or, worse, resolves to an older package version and you don't notice you've been downgraded. Mitigate by pinning exactly, reviewing the diff on every bump, and reviewing the constraint at each Laravel upgrade.Ramp-up. Essentially zero, unchanged. A mid-level Laravel engineer reads the entire package in an hour and understands the concept in five minutes — one well-known primitive with a thin ergonomic shell. The only training that matters is the policy documentation: which commands are mutexed, what happens on contention, what the exit codes mean, and where the alert fires. Add one line to that runbook for this release: the supported Laravel matrix and the pinned version, so the next engineer knows why the constraint exists. That's a one-page runbook, and it remains the artifact that determines whether this package is an asset or a source of quiet, undetected gaps in your batch processing.
How can I help you explore Laravel packages today?