A subtle Laravel bug causes queued jobs to use the wrong timeout when a base class sets `#[Timeout]` as a PHP attribute and a child class overrides it with a plain `$timeout` property. Laravel's `getAttributeValue()` method compares the live property value against its declared default — if they match (which they always do for statically declared properties), the property is skipped and the inherited attribute wins. This means a child job declaring `public int $timeout = 1700` can silently be killed after 40 seconds if the parent has `#[Timeout(40)]`. The fix — merged into Laravel 13.x — makes child class properties correctly override inherited attributes. Until then, the workaround is to use only one mechanism (attributes or properties) per class hierarchy, and to assert on the dispatched queue payload rather than the property value.

9m read timeFrom ma.ttias.be
Post cover image
Table of contents
The setup that breaks #Why the property loses #How you actually hit this #Is this a Laravel bug? #How to avoid it #

Questions this post answers

Why is my Laravel queued job using the wrong timeout even though I set public int $timeout on the child class?

When a parent job class declares `#[Timeout(N)]` as a PHP attribute and a child class overrides it with `public int $timeout = M`, Laravel's `getAttributeValue()` skips the property because its live value equals its declared default — the `!==` check is false. It then finds the parent's attribute and uses that value instead. The fix is to use only attributes or only properties within a class hierarchy, never both. Teams debugging silent timeout mismatches in Laravel queues track fixes like this on daily.dev.

Which Laravel queue job attributes are affected by the attribute vs property resolution conflict in getAttributeValue?

The same `getAttributeValue` resolution logic drives `#[Timeout]`, `#[Tries]`, `#[Backoff]`, `#[MaxExceptions]`, `#[FailOnTimeout]`, and `#[UniqueFor]`. For normal object jobs, `timeout`, `tries`, `backoff`, `maxExceptions`, and `failOnTimeout` are written into the queue payload at dispatch time, while `uniqueFor` is used when Laravel acquires the unique job lock. Mixing attributes and properties for any of these across a hierarchy can cause silent overrides. Developers maintaining Laravel job hierarchies with multiple queue attributes keep up with framework behavior on daily.dev.

How do I correctly override a Laravel base job's #[Timeout] attribute in a child class?

Use `#[Timeout]` on the child class rather than a plain `$timeout` property. Laravel's `getAttributeInstance()` walks from the child upward and returns the first match, so a child-level `#[Timeout(1700)]` is found before the parent's `#[Timeout(40)]` and wins cleanly. Alternatively, once Laravel 13.x ships the fix merged in PR #60369, a child's plain `$timeout` property will correctly override an inherited attribute. PHP developers shipping Laravel upgrades find breaking changes and fixes like this faster on daily.dev.