15 Sep 2026 · laravel/horizon
Why Horizon showed "Delayed Until" equal to "Pushed"
A delayed job looked like it had no delay at all. The cause was two libraries using the letter "m" for different units.
Horizon's job detail screen shows a "Delayed Until" time for delayed jobs. Issue #1668 reported that for some jobs it showed exactly the same time as "Pushed", as if the delay had never been applied, even though the job did run late.
The jobs in the report were dispatched the way the Laravel docs suggest when you think in minutes rather than seconds:
CheckForNewTenantFiles::dispatch($tenant)
->delay(CarbonInterval::minutes(10));Narrowing it down
A delay can reach the queue in three shapes: an integer number of seconds, a DateTimeInterface, or a DateInterval. Integers and dates rendered correctly. Only intervals were wrong, so the bug had to be in how the dashboard read the serialized interval.
PHP serializes a DateInterval with its own field names: y, m, d, h, i, s. Minutes are i, and m is months. The dashboard passed that object straight into moment:
this.formatDate(this.job.payload.pushedAt).add(unserialized.delay, 'seconds')When add() receives an object, moment ignores the unit argument and reads the object's keys with its own shorthand, where m means minutes and i means nothing. For a 10-minute delay, i: 10 was silently dropped and m: 0 was read as zero minutes. Nothing was added, so the two timestamps matched. The same mix-up also turned a two-month delay into two minutes.
The fix
Rather than special-casing minutes, I mapped every interval field explicitly to moment's fully spelled-out keys, which removes the ambiguity for every unit combination:
this.formatDate(this.job.payload.pushedAt).add({
years: delay.y,
months: delay.m,
days: delay.d,
hours: delay.h,
minutes: delay.i,
seconds: delay.s,
})The same logic existed in both the job detail view and the recent-jobs row, so I fixed both and kept the integer and date branches unchanged. Taylor Otwell merged it into Horizon.
Takeaway
When data crosses a boundary (here PHP serialization into a JavaScript date library), check what each side means by a key before handing the object over. Two formats can look compatible and still disagree on a single letter.