Skyline

Changelog

Every released version, with its date and what changed in it.

Every released version of Skyline, newest first, with what changed in each. Releases are published to the private Composer registry at laravel-skyline.composer.sh; composer update boring-o11y/laravel-skyline moves you to the newest release your version constraint allows.

Skyline follows semantic versioning within the 1.x line: patch releases are fixes and additive features that cost you nothing to take, and anything that changes existing behaviour is called out under Upgrade notes on the release that carries it. There have been two such changes so far — the Prometheus label rename in 1.3.2 and the per-queue pause rework in 1.1.1.

All releases#

Version Date Headline
1.3.3 5 Sep 2026 Job arguments, and search across them
1.3.2 31 Aug 2026 Prometheus label rework for balanced queue pools
1.3.1 25 Aug 2026 Stopped jobs fail immediately
1.3.0 5 Aug 2026 Prometheus endpoint and Grafana dashboard
1.2.5 4 Aug 2026 Stop a running job from the dashboard
1.2.4.1 29 Jul 2026 Pin the replaced Horizon version
1.2.4 20 Jul 2026 Bulk job actions and the Reserved tab
1.2.3 6 Jul 2026 Lifecycle logging covers the whole lifecycle
1.2.2 4 Jul 2026 JSON worker output
1.2.1 29 Jun 2026 Fix per-queue metric snapshot reads
1.2.0 27 Jun 2026 Previous-attempt failures and lifecycle logging
1.1.3 8 Jun 2026 Atomic metric snapshots
1.1.2 2 Jun 2026 Chart rendering no longer freezes under load
1.1.1 2 Jun 2026 Trends, weighted queues, front-of-queue dispatch
1.1.0 28 May 2026 Redis Cluster, delete a job, empty a queue
1.0 27 May 2026 First release

1.3.3 — 5 September 2026#

Added — job arguments. Every job listing now shows the constructor arguments the job was dispatched with underneath the job name, and the job's detail page gains an Arguments panel with the full set. Arguments are read from the serialized command with the queue's own bookkeeping properties (tries, delay, backoff, chained, batchId and friends) stripped out, so only what your application passed in is left. A job holding an Eloquent model is stored as the model class and its key, and a queued mailable, notification, listener or broadcast event shows the arguments of the object it wraps rather than the framework wrapper.

Added — search over arguments. The search box on every job listing now matches the job class name and the stored arguments, where it previously matched the class name alone. A phrase is a list of terms that must all hold:

Search Matches
SendInvoice jobs whose class name, argument names, or argument values contain "SendInvoice"
checkout_id: 3 jobs with an argument named checkoutId / checkout_id / checkout.id whose value is exactly 3
email: acme jobs with an email argument containing "acme" — a non-numeric value matches as a substring
name: "Ada Lovelace" quote a value containing spaces
SendInvoice checkout_id: 3 both must hold

Argument names match loosely on case and separators, and a nested value can be named by any trailing run of its path — id, checkout_id and order.checkout.id all name order.checkout.id. Numeric values must match exactly, so checkout_id: 3 never returns checkout 30, and integer ids beyond 253 still compare digit for digit.

Added — the job_arguments config block. Capture is on by default and bounded:

// config/horizon.php
'job_arguments' => [
    'enabled' => env('HORIZON_JOB_ARGUMENTS', true),
    'hidden' => [
        'password', 'secret', 'token', 'api_key', 'apikey', 'authorization',
        'credit_card', 'card_number', 'cvv', 'private_key',
    ],
    'ignored' => [],
    'max_depth' => 4,
    'max_items' => 25,
    'max_string' => 200,
    'max_length' => 4096,
],

Any argument whose name contains one of the hidden fragments is stored as [hidden]; ignored adds your own property names to the list stripped from every job. Note that masking covers the listing rows, the Arguments panel and the stored field — the job's Data panel still renders the serialized command exactly as it was queued, as it does in upstream Horizon, so treat this as noise reduction rather than a redaction guarantee.

Added — auto_tags. Jobs without a tags() method are tagged automatically with every Eloquent model they carry. Those tags drive the Monitoring tab and the tag filter on the Failed Jobs screen, and cost payload bytes, reflection on every dispatch, and one failed:{tag} Redis key per model instance for each failed job. Set auto_tags to false (or HORIZON_AUTO_TAGS=false) to keep only the tags your jobs declare themselves; explicit tags() methods, silenced_tags and monitoring are unaffected.

Added — the arguments field on every job-listing API response, alongside id, name, queue, payload and status.

Only new jobs

Arguments are captured when a job is pushed, so only jobs dispatched after upgrading are searchable by argument. Jobs already on the queue keep matching on class name.

1.3.2 — 31 August 2026#

Changed — the per-job Prometheus label is now job_class, not job. Prometheus reserves job for the scrape config's job_name and renames any label a target exposes under that name to exported_job — which made every series in Grafana come back as horizon, with the class buried in a label the dashboard never asked for.

Changed — queue series are split per queue. A supervisor running 'queue' => ['high', 'default'] with 'balance' => false serves both queues from one worker pool, which the dashboard shows as a single high,default row. The export now gives each queue its own series, tagged with the group it is balanced in. Queue length, oldest pending job and the paused flag are genuinely per queue; horizon_queue_processes repeats the pool's size on every queue in the group (deduplicate with max by (group)), and horizon_queue_time_to_clear_seconds is each queue's share of the group estimate (sum by (group) brings you back to the group figure).

Changed — the bundled Grafana dashboard was updated to match both of the above.

Added — release reasons are surfaced in the dashboard's job views.

Upgrade notes

Any PromQL you wrote against 1.3.0 or 1.3.1 that selects or groups on the per-job job label needs updating to job_class. Re-import the bundled Grafana dashboard to pick up both changes at once.

1.3.1 — 25 August 2026#

Changed — stopping an in-progress job now records the failure as soon as its worker is killed. Previously the job was failed at pop time, whenever its reservation happened to be migrated back, so the dashboard could sit for a full retry_after window showing a job as running after you had stopped it.

Fixed — compatibility with Laravel 12.11 and later, whose createPayload() takes an additional delay argument.

Changed — the replaced upstream version moved to laravel/horizon 5.48.3.

1.3.0 — 5 August 2026#

Added — a Prometheus scrape endpoint. The measurements behind the dashboard's metric graphs, plus workload and worker process counts, are exported in the Prometheus text exposition format at /horizon/prometheus. It is off by default and no route exists until you set HORIZON_PROMETHEUS_ENABLED=true.

Added — the prometheus config block, covering the path, domain, metric name prefix, additional middleware, and the IP allowlist that guards the endpoint in place of the viewHorizon gate — a Prometheus server has no session to authenticate with. The allowlist defaults to loopback only and accepts exact addresses, CIDR ranges, or *.

Added — a bundled Grafana dashboard, publishable with the horizon-grafana asset tag.

Changed — horizon:snapshot now folds the window it closes into a never-reset totals hash, so throughput, failures and retries export as true Prometheus counters that survive a snapshot reset. rate() and increase() behave correctly across snapshots, and nothing is double counted.

Added — the ExportsMeasurements contract, kept separate from MetricsRepository so an application binding its own metrics repository is not broken by the new methods. The exporter omits the per-job and per-queue families when the bound repository does not implement it.

1.2.5 — 4 August 2026#

Added — stop a running job. An in-progress job can be stopped from the dashboard, which signals the worker executing it. POST /api/jobs/stop/{id} stops one job and POST /api/jobs/stop stops several.

Added — cancel_expires. A stopped job is flagged so that any copy migrated back after its worker is killed is refused rather than run. This option sets how long (in minutes) that flag lives, and should comfortably exceed your longest retry_after / timeout window. Defaults to 60.

Changed — attempt history records releases too. attempt_exceptions previously recorded exceptions and timeouts; it now also records a third type, release, covering the releases no exception explains — a manual $job->release(), or middleware releasing the job before it runs. Only one entry is kept per attempt, and the release is dropped when an exception already covers that attempt.

1.2.4.1 — 29 July 2026#

Fixed — the package declared replace: {"laravel/horizon": "self.version"}, which told Composer that Skyline 1.2.4 satisfied a laravel/horizon requirement of ^1.2 — a version of Horizon that does not exist. The replaced version is now pinned to the upstream release Skyline actually forks (5.48.1), so applications and packages depending on laravel/horizon: ^5.0 resolve correctly.

Added — Horizon's dev-only commands are registered again.

1.2.4 — 20 July 2026#

Added — bulk job actions. Retry, Perform Now and Delete now accept a set of job ids, so you can act on a selection rather than one row at a time: POST /api/jobs/retry, POST /api/jobs/perform and DELETE /api/jobs.

Added — the Reserved tab and its GET /api/jobs/reserved endpoint, listing jobs a worker holds right now.

Changed — the job screens were reorganised around the tab set the dashboard has today: the per-queue screen became the In Progress view, and the separate Retries screen folded into it.

1.2.3 — 6 July 2026#

Added — lifecycle logging covers the whole lifecycle. 1.2.0 logged the transitions nothing else reported — unique-lock discards and releases. This release adds the rest: a job being queued, reserved, migrated from the delayed set, completed, and failed. Every line still carries the job id, and the channel's own log level is the volume dial.

Added — the unique job lock releaser. A ShouldBeUnique job deleted from the queue or dropped out-of-band used to leave its laravel_unique_job:* lock held until the TTL expired, silently discarding every subsequent dispatch. The lock is now released when the job leaves the queue.

1.2.2 — 4 July 2026#

Added — worker_output. Set it to json (HORIZON_WORKER_OUTPUT=json) and workers print one structured JSON object per line — job id, uuid, connection, queue, status, attempts and duration — instead of the human-readable terminal table. Requires Laravel 11 or later; the default cli is unchanged.

1.2.1 — 29 June 2026#

Fixed — the per-queue metrics read the wrong end of the snapshot series (zrange … -1, 1 rather than -1, -1), so queue runtime and throughput could fall back to stale or empty values on the dashboard.

1.2.0 — 27 June 2026#

Added — previous-attempt failure reasons. When a job throws or times out but still has retries left, that reason was previously lost — you saw a job sitting in Retries with nothing explaining why. Skyline now records it and shows the history under a Previous Attempts panel on the job's page, with the attempt number, the type, the timestamp, and the full stack trace for exceptions. The new attempt_exceptions option caps how many are kept per job (default 1, 0 disables).

Added — job lifecycle logging. The new log_channel option (HORIZON_LOG_CHANNEL) points at the channel that should receive lines for the transitions nothing else reports: jobs discarded at dispatch because a ShouldBeUnique lock was held, and jobs released back to the queue with the reason attributed.

Added — a drop-in WithoutOverlapping middleware. Import Laravel\Horizon\Middleware\WithoutOverlapping instead of the framework's — otherwise identical — and releases caused by an overlap are distinguished from manual ones in the logs.

1.1.3 — 8 June 2026#

Changed — metric snapshots are taken in a single atomic Lua call that reads each metrics hash, resets it, appends the snapshot and trims the series in one pass. Previously a snapshot spanning many queues could interleave with in-flight measurements and lose a window.

Changed — the aggregate metric charts were reworked so each plots one max-across-all line, which stays readable however many queues you run.

1.1.2 — 2 June 2026#

Fixed — the dashboard's line charts froze the tab under heavy throughput. The component deep-watched its data, which made Vue traverse Chart.js's own internal element graph on every refresh, and re-animated every chart on each poll. The watcher is now shallow and animations are off.

1.1.1 — 2 June 2026#

Added — workload and failure trends. A trends config block, a GET /api/trends endpoint, and dashboard charts showing how workload and failures moved over time. interval sets the bucket size and workload sampling cadence in minutes; retention sets how many hours of history to keep.

Added — weighted queues. With 'balance' => false, workers serve the listed queues in strict left-to-right priority. An optional queueWeights map on the supervisor softens that into a proportional policy: a queue weighted 2 is checked roughly twice as often as one weighted 1, so high-priority work is favoured without starving anything.

Added — front-of-queue dispatching. Add the InteractsWithFrontOfQueue trait to a job and call dispatch($job)->onFront() to have it LPUSHed onto the head of the ready list, so it is the next job a worker pops.

Changed — per-queue pausing moved onto Laravel's native queue-pause API. The previous implementation kept its own pause state in Redis and signalled supervisors; it now calls QueueManager::pause(), which the workers themselves honour.

Upgrade notes

Per-queue pausing now requires a framework version with the native queue-pause API and a shared cache store. Where either is missing, the endpoint responds 409 Conflict rather than reporting a pause that would not take effect. Global and per-supervisor pause are unaffected.

1.1.0 — 28 May 2026#

Added — Redis Cluster support. Every repository now runs its multi-command batches through a cluster-aware helper that issues a transaction on a clustered connection and a pipeline otherwise, and the Horizon connection is configured differently for a cluster than for a standalone server.

Added — delete a job and empty a queue from the dashboard, with a confirmation modal: DELETE /api/jobs/{id} and DELETE /api/queues/{connection}/{queue}. Only pending and delayed jobs can be deleted — a job a worker has already reserved is rejected with 422 rather than left half-executed.

1.0 — 27 May 2026#

First public release. Skyline forks Laravel Horizon and adds the tabbed Jobs view with per-queue drill-down and job-class search, pausing and resuming at three levels of granularity, Perform Now for delayed jobs, and improved delayed-job tracking and wait-time reporting. Everything Horizon does, it still does — see Migrating from Horizon.

Staying current#

# Move to the newest release your constraint allows
composer update boring-o11y/laravel-skyline

# Check what you are on
composer show boring-o11y/laravel-skyline

# List every published version
composer show boring-o11y/laravel-skyline --all

Nothing else is needed after an upgrade. Skyline serves its dashboard assets from the package itself, so there is no publish step to repeat — horizon:publish exists only to tell you it is no longer required. A release that adds a config option ships it with a working default, so republishing config/horizon.php is optional; copy the new block across from the package's own config file when you want to change it from the default.

Common questions

What is the latest version of Skyline for Laravel?

1.3.3, released on 5 September 2026. It adds job argument capture, argument search across every job listing, the job_arguments config block, and the auto_tags option for turning off automatic model tagging.

How do I update Skyline to the latest version?

Run composer update boring-o11y/laravel-skyline. That moves you to the newest release your version constraint allows — a constraint of ^1.1 covers every 1.x release. Then run php artisan horizon:publish to refresh the dashboard assets, which a stock Laravel composer.json already does for you through its post-update-cmd hook.

Are there breaking changes between Skyline versions?

Two so far, both within the 1.x line and both narrow. 1.3.2 renamed the per-job Prometheus label from job to job_class, so PromQL written against 1.3.0 or 1.3.1 needs updating. 1.1.1 moved per-queue pausing onto Laravel's native queue-pause API, which now responds 409 where the framework version or cache store cannot support it. Nothing else has changed existing behaviour.

Queue control, not just queue monitoring.

Skyline is a drop-in replacement for Laravel Horizon that lets you act on what you see — pause a queue, jump a job to the front, drain a backlog. $99 once, every app you run it on.

Buy Skyline — $99 once

Secure checkout by Anystack. 30 days to change your mind.