Laravel Deadlock helps you track temporary workarounds before they turn into permanent debt.
Annotate classes or methods with an expiration date, then enforce those deadlines in local development and CI without affecting production.
- Scans the codebase for
#[Workaround]attributes - Lists workarounds and their status
- Fails CI when a workaround has expired
- Blocks local execution of expired code
- Never enforces in production
composer require zidbih/laravel-deadlock- Laravel 10, 11, 12 with PHP 8.2+
- Laravel 13 with PHP 8.3+
Add #[Workaround] to a class or method with a clear description and expiration date.
use Zidbih\Deadlock\Attributes\Workaround;
#[Workaround(
description: 'Temporary bypass for legacy payment gateway',
expires: '2025-03-01'
)]
class PaymentService
{
// ...
}#[Workaround] supports classes and methods. Other scopes are ignored.
- Local Development: Execution is blocked with an exception
- CI/CD: Pipelines fail when running the check command
- Production: No effect
Controllers are discovered automatically and enforced at runtime.
Add the attribute; no additional calls are required.
namespace App\Http\Controllers;
use Zidbih\Deadlock\Attributes\Workaround;
#[Workaround(description: 'Legacy controller awaiting refactor', expires: '2025-06-01')]
final class UserController extends Controller
{
#[Workaround(description: 'Temporary validation bypass', expires: '2025-02-01')]
public function store()
{
// ...
}
}For non-controller classes, enforcement is explicit by design to avoid hidden runtime behavior.
namespace App\Services;
use Zidbih\Deadlock\Attributes\Workaround;
use Zidbih\Deadlock\Support\DeadlockGuard;
#[Workaround(description: 'Temporary legacy pricing service', expires: '2025-01-01')]
final class PricingService
{
public function __construct()
{
DeadlockGuard::check($this);
}
}namespace App\Services;
use Zidbih\Deadlock\Attributes\Workaround;
use Zidbih\Deadlock\Support\DeadlockGuard;
final class PricingService
{
#[Workaround(description: 'Temporary calculation logic', expires: '2025-02-01')]
public function calculate()
{
DeadlockGuard::check($this, __FUNCTION__);
return 42;
}
}List all detected workarounds and their current status.
php artisan deadlock:listExample output:
- Show only expired workarounds:
php artisan deadlock:list --expired- Show only active workarounds:
php artisan deadlock:list --active- Show workarounds expiring in 7 days or less:
php artisan deadlock:list --criticalThe command includes a summary line by default so totals are visible at a glance.
Fail CI when one or more workarounds have expired.
php artisan deadlock:checkIf an expired workaround is found, the command exits with code 1.
To fail before a workaround expires, use --fail-within:
php artisan deadlock:check --fail-within=7This fails when a workaround is already expired or expires within the next 7 days.
To also fail when invalid workaround usage is detected, use strict mode:
php artisan deadlock:check --strictStrict mode reuses the doctor checks and is useful in CI when you want unsupported targets, invalid attributes, or missing DeadlockGuard::check(...) calls to fail the pipeline.
For machine-readable output, use JSON mode:
php artisan deadlock:check --jsonExample JSON output:
{
"success": false,
"expired_count": 1,
"expired": [
{
"description": "Temporary payment gateway workaround",
"expires": "2025-02-10",
"location": "PaymentService::process",
"file": "/app/Services/PaymentService.php",
"line": 18,
"class": "PaymentService",
"method": "process"
}
]
}Example failure output:
Expired workarounds detected:
- Temporary payment gateway workaround | expires: 2025-02-10 | PaymentService::process
- Legacy admin controller | expires: 2025-01-31 | AdminController
Diagnose workaround usage that may look valid but will not behave as expected.
php artisan deadlock:doctorThe doctor command reports unsupported #[Workaround] targets, invalid attribute arguments, and missing or incorrect DeadlockGuard::check(...) calls for explicit runtime enforcement.
Example output:
Update the expires date of an existing #[Workaround] attribute in your source code.
It supports three target modes:
- Extend the workaround on a class
- Extend the workaround on one method
- Extend every workaround declared on a class
Use exactly one of these target options:
--class=App\Services\PricingService--controller=TestController
--controller is a shortcut for classes under App\Http\Controllers.
Examples:
php artisan deadlock:extend --controller=TestController --days=7
php artisan deadlock:extend --controller=Admin\TestController --method=index --months=1
php artisan deadlock:extend --class=App\Services\PricingService --days=7--class only:
- Targets the class-level
#[Workaround]on that class
--class or --controller with --method=...:
- Targets only the workaround on that method
--class or --controller with --all:
- Targets the class-level workaround
- Targets every method-level workaround declared on that class
You must provide either:
--days=N--months=N--date=YYYY-MM-DD
You may combine --days and --months in the same command.
php artisan deadlock:extend --class=App\Services\PricingService --months=1 --days=7--date is absolute and replaces the current expiry date directly.
php artisan deadlock:extend --class=App\Services\PricingService --date=2026-06-01--date cannot be combined with --days or --months.
Extend a class-level workaround:
php artisan deadlock:extend --class=App\Services\PricingService --days=7Extend a method-level workaround:
php artisan deadlock:extend --class=App\Services\PricingService --method=calculate --days=7Extend every workaround on a class:
php artisan deadlock:extend --class=App\Services\PricingService --all --months=1 --days=7Extend a controller workaround:
php artisan deadlock:extend --controller=TestController --days=7Extend a nested controller method workaround:
php artisan deadlock:extend --controller=Admin\TestController --method=index --date=2026-06-01- Use exactly one of
--classor--controller --methodand--allcannot be used together- Without
--methodor--all, the command updates only the class-level workaround --daysand--monthsmust be positive integers--datemust useYYYY-MM-DD- The target class must resolve to a real PHP file
- The targeted class or method must already have a
#[Workaround]
Run the check command in your pipeline:
php artisan deadlock:checkCI example:
- name: Deadlock check
run: php artisan deadlock:checkWhen expired code is accessed locally, a WorkaroundExpiredException is thrown with:
- Description
- Expiration date
- Exact code location
Example exception output:
Laravel Deadlock never enforces workaround deadlines in production.
- Runtime exceptions only occur in local environments
- CI blocks merges before debt reaches production
- Live users are never affected
See CONTRIBUTING.md


