kregel/nova-failed-jobs-tool
A Laravel Nova tool for viewing and managing failed queue jobs directly from your admin panel, with retry and delete actions so you can resolve failures without leaving Nova.
Install via Composer in your Laravel app that already runs Nova:
composer require kregel/nova-failed-jobs-tool
Pin the version explicitly (kregel/nova-failed-jobs-tool:^1.0) so a future dependency-only bump can't shift what you tested against.
Register the tool in app/Providers/NovaServiceProvider.php:
public function tools()
{
return [
new \Kregel\NovaFailedJobsTool\NovaFailedJobsTool(),
];
}
If the package doesn't auto-discover its service provider, add Kregel\NovaFailedJobsTool\NovaFailedJobsToolServiceProvider to config/app.php. Check the repo README first — for a low-star, 2021-era tool the README is the single best source of truth on the exact namespace and registration class name.
Ensure the failed_jobs table exists. If you don't already have it:
php artisan queue:failed-table
php artisan migrate
(Laravel 8+ ships this migration by default.)
Look here first: open your Nova sidebar, click the "Failed Jobs" tool, and confirm it lists rows from failed_jobs. Compare against the CLI baseline:
php artisan queue:failed
If the Nova list matches queue:failed, the tool is wired up correctly. If it's empty in Nova but non-empty in CLI, you have a connection/table/guard mismatch — check config/queue.php and the failed_jobs table name.
First real use case: force a failure to test the workflow. Push a job that throws (e.g. a job whose handle() calls throw new \Exception('test')), run php artisan queue:work --once, then open the tool and inspect the decoded payload and exception. That teaches you what the tool actually renders before you rely on it in an incident.
Daily triage loop. Treat the Nova tool as the read/decide surface and the CLI as the bulk-action surface. A repeatable workflow:
failed_at descending.php artisan queue:retry all (or a filtered set of IDs), since the tool is per-job oriented.Integrate with your logging stack. The exception column is often truncated in the UI. Copy the job uuid and grep your structured logs (storage/logs/laravel.log or your log aggregator) for that UUID — many teams add the job UUID to log context in a JobProcessing/JobFailed event listener, which makes the Nova row a jumping-off point rather than the sole source of truth.
Deploy-safe retry pattern. Failed jobs are serialized snapshots of old code. After a deploy that changes a job's constructor or dependencies, retrying an old failed job can fail again with a ModelNotFoundException or unserialize error. The practical rule: only retry jobs whose failure predates the deploy if you know the payload shape didn't change. Otherwise, delete and let the originating process re-enqueue.
Surface failed jobs on a dashboard. The tool is a detail view; for at-a-glance awareness add a Nova metric that counts failed_jobs rows since midnight and a scheduled command (Schedule::command('queue:prune-failed --hours=168') plus an alert when count > 0). Use the tool to drill into the specific rows the metric flagged.
Multi-queue environments. If you run separate queues (e.g. default, emails, webhooks), the tool lists them together unless it offers a filter. Establish a convention — either filter by the queue column in the UI or query failed_jobs directly when you need queue-scoped bulk retries (queue:retry --queue=emails).
Pair with queue:monitor. Laravel's queue:monitor command alerts on queue size, not failures. Combine it with a failed_jobs count check so you're alerted on both backlogs and failures, and use the Nova tool for the human investigation step.
Nova is a hard dependency. This package is useless without a licensed Laravel Nova install. It also needs Nova's asset build pipeline — run php artisan nova:publish and rebuild (npm run dev/prod) if the tool's compiled JS doesn't appear.
v1.0.0 is a dependency bump, not a feature release. The only change is "truly support >= php 7.1" — i.e. a widened/normalized php constraint in composer.json. Nothing else moved: no Nova 4/5 support, no Laravel 9/10/11 declaration, no new filters or actions. Do not read the 1.0.0 semver bump as a stability signal or as evidence the package was ported forward; it was previously shipping a version string below 1.0 while the code was unchanged.
The widened PHP floor is mostly cosmetic for a modern app. php >=7.1 is a lower bound — it helps legacy installs, but it says nothing about upper bounds or about laravel/nova / illuminate/* constraints. On a PHP 8.2 + Laravel 11 + Nova 5 app you'll still hit the Nova/Illuminate constraint wall before the PHP one. Inspect the repo's composer.json for laravel/nova and illuminate/* before composer require; if they pin ^3.0 or ^8.0, avoid --ignore-platform-reqs in production — that's a red flag, not a workaround. Treat it as a fork candidate or a small in-house Nova tool.
Low stars / zero dependents is a signal, not a dealbreaker. Very little community validation. Budget time to read the source (it's small) before trusting its retry/delete behavior, and pin it explicitly in composer.json. A dependency-only 1.0.0 release does not change this calculation.
The payload column is not human-readable in the DB. It's a base64-encoded serialized job object. If the tool renders it raw or truncated, use php artisan queue:failed for the decoded view, or decode it yourself:
$job = json_decode(DB::table('failed_jobs')->find($id)->payload, true);
$command = unserialize(base64_decode($job['data']['command']));
Never unserialize untrusted payloads blindly.
Retrying requires the job class to still be resolvable. If the class was renamed, deleted, or its constructor signature changed since the failure, retry will fail again immediately (often with a new, more confusing exception). Verify the class exists and the payload deserializes before retrying en masse.
Retries go back to the original connection/queue stored in the payload, not your current defaults. If a queue name was decommissioned, retried jobs can silently sit in a queue with no workers. Confirm queue:work --queue=... is running on the right names.
exception is a longtext and stack traces get enormous. Rendering a 200KB stack trace in a Nova detail view can freeze the browser tab. If you hit this, check the package's truncation behavior; otherwise open the record in the DB directly.
Deleting is permanent. The tool's delete/forget action removes the row — there's no soft delete. Take a mysqldump/pg_dump of failed_jobs (or SELECT * INTO OUTFILE) before clearing a large batch if you might need the payloads for a post-mortem.
failed_at is stored in UTC. Nova may render it in the app's display timezone depending on its config. Don't assume the timestamp matches your local clock when correlating with logs.
Authorization is whatever Nova enforces. The tool inherits Nova's gate (Nova::auth), but if you use per-tenant scoping or custom policies, confirm the tool doesn't bypass them — failed jobs contain serialized models and potentially sensitive payloads, so an over-broad viewNova gate is a data-leak risk. Restrict via Nova::auth() middleware or a policy before enabling in multi-tenant installs.
Prune proactively. Add queue:prune-failed (or queue:prune-failed --hours=168) to your scheduler. The tool will happily render 500k rows and make the whole Nova app feel slow; the failed_jobs table is one of the fastest-growing tables in a busy app.
Extension points are limited. Expect the tool to be a thin UI over the failed_jobs table with list/detail/retry/delete. If you need custom columns or filters, the realistic options are (a) fork the repo and add a Nova Resource, or (b) skip the tool and write a 60-line Nova Resource for a FailedJob model — that's often less work than patching a stale package, and you get Nova 5 compatibility and full-text search for free. v1.0.0 gives you no new hooks, so this trade-off is unchanged.
Debugging checklist when the tool misbehaves: verify failed_jobs table name matches config, verify the DB connection the tool uses matches config/queue.php's failed driver, check storage/logs/laravel.log for asset/route registration errors after composer require, and run php artisan nova:asset / php artisan route:list | grep failed to confirm routes registered. After upgrading to 1.0.0, diff composer.lock — the resolved laravel/nova and illuminate/* versions may have moved as a side effect of the constraint change, which is the most likely source of any post-upgrade breakage.
How can I help you explore Laravel packages today?