Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Console Mutex Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

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.

  • Correctness is delegated to the cache store. Unchanged and still the dominant risk. Redis, Memcached, DynamoDB, and the database store give a genuinely shared lock across hosts. The file store gives a lock local to a single machine — on a multi-node deployment it silently provides no mutual exclusion. A configuration mistake, not a code defect, which makes it the easiest thing to get wrong and the hardest to notice.
  • Value overlap with the framework. Unchanged. Laravel's scheduler ships 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.
  • Lock TTL vs. runtime. Unchanged. If expiry is shorter than worst-case runtime, the lock evaporates mid-run and you get the overlap you were preventing — silently.
  • Adoption signal and maintenance. Improved on the maintenance axis, unchanged on the adoption axis. 147 stars, zero dependents, no third-party production validation and no downstream pressure on the maintainer — but the maintainer shipping a Laravel 13-compatible major promptly is exactly the behavior you want from a single-maintainer package, and it materially lowers the "goes stale on the next framework major" risk that was previously the main abandonment trigger. It does not change the bus factor. The codebase is still auditable in an afternoon.
  • Version-matrix risk (new). With 12.x now effectively the legacy line, the support window for non-Laravel-13 consumers is whatever the maintainer chooses, and there is no stated policy. If your upgrade cadence lags the framework's, you may be stranded on an unmaintained minor with no security backport path.
  • Exit-code semantics. Unchanged, and not addressed by this release. Contention must map to some exit status: success means monitoring can never distinguish "ran fine" from "skipped"; failure means false alerts. Defensible either way, but must be deliberate.

Key questions.

  1. Which cache store backs the lock in each environment, and is it genuinely shared across every host that can run this command?
  2. Are we protecting commands invoked only by the scheduler (native solution available) or also by operators, jobs, and deploy scripts (package earns its place)?
  3. On contention, do we want fail-fast, bounded wait, or skip-and-log — and what does the observability stack need to see?
  4. What is our worst-case command runtime, and how does the lock TTL compare?
  5. Are we on Laravel 13 today? If yes, take 13.0.0 and confirm the constraint. If no, we cannot consume this release at all — pin 12.x, record the pin as technical debt with an owner and a target upgrade date, and confirm what (if anything) the maintainer intends to backport.
  6. What is the maintainer's supported Laravel/PHP matrix and stated support-window policy for prior majors against our upgrade cadence?
  7. Is the team willing to own a ~200-line fallback trait if the package goes stale? (Spoiler: yes, it's a weekend — and this release makes that call less likely to be needed.)

Integration Approach

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.

  1. Land the Laravel 13 upgrade first, on its own branch, with the mutex package still on 12.x. Do not co-mingle a framework major and a package major in one PR — if something breaks you want to know which one did it.
  2. Bump the package to 13.0.0 as a separate, single-line change, then re-run the test suite and a manual smoke test of one mutexed command. If the app is not on Laravel 13, stop here and record the 12.x pin as tracked debt.
  3. Inventory commands with real overlap risk: long-running reports, syncs, imports, anything scheduled more frequently than its typical runtime, anything an operator might run by hand during a deploy.
  4. Confirm or provision a lock-capable shared cache store and make it explicit rather than relying on the app default.
  5. Pilot on one low-blast-radius command with logging around contention.
  6. Decide and document the contention policy and exit codes per command class.
  7. Roll out to the remaining commands; each is an independent, revertible change.
  8. Add a metric/alert on "lock not acquired" so silent skipping is visible.
  9. Keep the written escape hatch: replacing the trait with a small in-house wrapper around Cache::lock() is a half-day exercise, so the package is never a one-way door.

Operational Impact

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:

  • Silent skip. A command loses the lock, exits cleanly, nobody notices the report didn't run. The dangerous one, because it looks like health. Mitigate with explicit logging plus an alert on contention.
  • File-store false confidence. Multi-host deployment, local locks, zero protection, no error. Mitigate by asserting the cache driver in config review or a smoke test.
  • Version stranding (new emphasis). A future major silently raises the framework floor; a routine 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.
  • Stale lock. A process dies holding the lock; the command is blocked until TTL expiry. Choose TTLs that bound this to an acceptable window and ensure TTL comfortably exceeds worst-case runtime.
  • Cache outage. Acquisition throws or fails; behavior depends on the fail-open/fail-closed choice. Decide in advance and test it.
  • Abandonment. Package stops receiving updates. Consequence remains bounded: the primitive is framework-native and the wrapper is trivial to reimplement. This release lowers the near-term likelihood but does not eliminate the tail.

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.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager