Laravel 12.69.3: Timed-Out Worker Exit Codes and Named S3 Credential Providers
Laravel Framework v12.69.3 ships two backported changes, both from @kieranbrown, and both quietly significant for anyone running queue workers or S3-backed storage in containerised environments. The first makes the queue worker's timed-out exit code actually reachable — until now, a worker killed for exceeding its timeout could never report that fact to its parent process. The second teaches the filesystem layer about named S3 credential providers (ecs, instance) and lets Laravel Cloud provision disks that authenticate through the instance's IAM role instead of static keys. Both are backports of 13.x work and are labelled trivial, so upgrading is low-risk even if you never touch the new options.
When a timeout kills the messenger
When a job outlives its timeout, the worker's pcntl_alarm handler calls Worker::kill(). That method dispatches WorkerStopping, then — with ext-posix loaded — sends itself SIGKILL before the exit($status) on the next line can ever run. SIGKILL is immediate and uncatchable, so the status passed in is effectively dead code: a supervising parent sees signal death (exit 137 on Linux, i.e. 128 + 9) rather than any meaningful exit code. The PR proves this with a minimal script where an echo immediately before exit() never prints, ruling out the theory that PHP ran exit() and the shell merely reported the signal instead.
This is also why the memory-limit exit code works while a timeout code never could: the memory recycle goes through stop(), which returns the status up through Artisan. Only the timeout path goes through kill().
A kill hook that keeps SIGKILL semantics
The fix introduces two things: a Worker::$timedOutExitCode property (falling back to the old hardcoded EXIT_ERROR when unset) and a Worker::killUsing() hook that registers a callback fired just before the SIGKILL.
Laravel Cloud wires them together. Its queue connector sets Worker::$timedOutExitCode = 124 — the conventional timeout exit status — and registers a callback that calls pcntl_exec('/bin/sh', ['-c', 'exit 124']). The choice of exec over a plain exit() is deliberate: exit() runs registered shutdown functions and destructors on a frozen, mid-timeout job — precisely what the kill exists to prevent. A destructor that hangs would hang the worker indefinitely; one that flushes or commits could change behaviour under timeout. execve replaces the process image with no shutdown functions and no destructors, so the semantics match the SIGKILL while still delivering a real exit status — and the PID is preserved, so a supervising parent's wait() returns normally.
The design is defensive on every axis. With no callback registered, nothing changes. If pcntl_exec is unavailable or the exec fails, execution falls through to the existing posix_kill — worst case is exactly today's behaviour. WorkerStopping still fires first, and kill() has exactly one caller in the framework: the SIGALRM timeout handler. Verification from a Go parent shows the worker reporting exit status 124 while a registered shutdown function and a destructor both stay silent; a control using plain exit(124) runs both.
Named S3 credential providers
FilesystemManager::formatS3Config() now accepts a credentials entry that is either a named provider string or an array with a provider key plus options. Two names are supported: ecs resolves through CredentialProvider::ecsCredentials() and instance through CredentialProvider::instanceProfile() — both memoized, so the container metadata endpoint is hit once and the result cached. Unknown names throw an InvalidArgumentException immediately.
Existing configurations are untouched: credentials => false, explicit key/secret pairs, callables, and the key/secret/token fallthrough all behave exactly as before. The motivating scenario is a disk running in an ECS task or on an EC2 instance, where there are no static keys to configure — and where ambient credentials in the environment (say, R2 keys used by another disk) must not silently leak into a disk that should authenticate as the container itself. The test suite demonstrates exactly that: a disk configured with 'credentials' => 'ecs' fails against an invalid container host rather than falling back to ambient keys.
Cloud IAM disks
On the Cloud side, Cloud::configureDisks() now forwards each managed disk's region (defaulting to auto instead of hardcoding it) and its credentials into the underlying S3 configuration. A Cloud-provisioned disk can therefore target an access point in a specific region with a cacheable ECS credential provider attached — and the resulting configuration stays cacheable itself; the integration test round-trips it through var_export to prove it survives serialization.
Takeaways
- Timeouts now report properly on Laravel Cloud — managed queue workers exit with status
124on timeout instead of dying to an uncatchable SIGKILL that swallows the exit status. - The mechanism is a general hook —
Worker::killUsing()andWorker::$timedOutExitCodeare off by default, fail safe, and available to any supervisor that needs a real exit status from a killed worker. - S3 disks can use IAM credential chains —
'credentials' => 'ecs'or'instance', with optional per-provider options, resolve memoized AWS credential providers; no static keys required. - Nothing else changed — existing
key/secret/tokenconfigs,credentials => false, and callables are all preserved, and unknown provider names fail fast.