Detect and notify on failed queue jobs that have sat unretried #131
Labels
No labels
bug
devops
duplicate
enhancement
good first issue
layout
next major release
next minor release
question
research
testing
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lvl0/fedi-feed-router#131
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
SyncChannelPostsJobfailed on 2026-03-08 and sat in the failed-jobs table, unretried and unnoticed, for five months. It was discovered by accident while investigating #123. The duplicate-detection backstop was silently absent the entire time.Horizon keeps failed jobs for 7 days (
config/horizon.php:trim.failed = 10080), then prunes them. A job that exhausts its retries and is pruned by Horizon disappears without a trace — no notification, no log entry, no surviving record.Current state
config/horizon.phpsetstries = 1on the supervisor — jobs get one attempt, no retriesLongWaitDetectedevent exists (waits.redis:default = 60) but nothing listens for itCheckFeedStalenessJobdemonstrates the notification-deduplication pattern: query for an existing unread notification of the same type before sendingNotificationTypeEnumhasFEED_STALE,FEED_EMPTY,PUBLISH_FAILED,CREDENTIAL_EXPIRED— noJOB_FAILEDor equivalentfailed_jobstable is a Laravel built-in — it hasid,connection,queue,payload,exception,failed_atDesign
Approach: scheduled check against the
failed_jobstableHorizon prunes after 7 days, so the check must run more frequently than that. A daily scheduled job that queries
failed_jobsfor entries newer than the last run is simple and covers any queue, not justsync.Alternatives considered:
JobFailedevent — real-time but requires a listener that ships with Horizon; no simpler than the scheduled approach and misses jobs that failed before the listener was deployedQueue::failing()callback inAppServiceProvider— catches every failure but fires per-job, making deduplication harder and risking notification storms when a systemic issue fails every jobThe scheduled approach is consistent with how
CheckFeedStalenessJobalready works and composes well with the existing notification deduplication pattern.Notification deduplication
Reuse the
CheckFeedStalenessJobpattern: before sending, check for an existing unreadJOB_FAILEDnotification. A single notification per failure is enough — the user can clear it once acted on.What to include in the notification
Tasks
JOB_FAILEDcase toNotificationTypeEnumCheckFailedJobsJobscheduled job (daily,withoutOverlapping,onOneServer)failed_jobsfor entries since the last notification (or a configurable lookback window)NotificationServicewith deduplicationSetting—failed_job_notification_enabled+failed_job_lookback_hours, with0to disableSettingsControllerEdge cases
JOB_FAILEDnotification covers allAcceptance criteria
failed_jobsproduces a notification within 24 hoursJOB_FAILEDregardless of how many jobs have failedfailed_job_notification_enabled = falseRelated
SyncChannelPostsJobfailure was discovered