<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Oban Pro Releases</title>
  <subtitle>Oban Pro release announcements</subtitle>
  <id>https://oban.pro/</id>
  <link rel="alternate" type="text/html" href="https://oban.pro" />
  <link rel="self" type="application/atom+xml" href="https://oban.pro/releases/feed" />
  <updated>2026-08-26T00:00:00Z</updated>
  <author>
    <name>Oban</name>
  </author>
  
    <entry>
      <title>Oban Pro v1.7.12</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.7.12" />
      <id>https://oban.pro/releases/pro/1.7.12</id>
      <published>2026-08-26T00:00:00Z</published>
      <updated>2026-08-26T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Bug Fixes</h3>
<ul>
<li>
<p>[Queues] Allow <code>dispatch_cooldown</code> in queue options</p>
<p>Oban itself accepts dispatch_cooldown as a per-queue option, but the Queues service rejected it
because the persisted queue schema didn't recognize the field. Now the option is validated,
persisted, and passed through to the queue's producer when it starts.</p>
</li>
<li>
<p>[Smart] Exclude dupe unique jobs from workflow counts</p>
<p>Workflow counters were incremented from all member changesets before unique de-dup ran, so a
member that deduped was counted but never inserted. The drift left workflows permanently stuck
with phantom suspended or available counts Now workflows are upserted after dupe detection and
deduplicated members are excluded from the counts.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.8.0-rc.1</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.8.0-rc.1" />
      <id>https://oban.pro/releases/pro/1.8.0-rc.1</id>
      <published>2026-08-25T00:00:00Z</published>
      <updated>2026-08-25T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Bug Fixes</h3>
<ul>
<li>
<p>[Cron] Include the Oban Pro compiler in the release package</p>
<p>The compiler task was omitted from the package, causing projects configured with the <code>:oban_pro</code>
compiler to fail with a missing task error.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.8.0-rc.0</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.8.0-rc.0" />
      <id>https://oban.pro/releases/pro/1.8.0-rc.0</id>
      <published>2026-08-25T00:00:00Z</published>
      <updated>2026-08-25T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Enhancements</h3>
<ul>
<li>
<p>[Backfill] Add a module for for cursor-based dataset backfills</p>
<p>Backfill walks a dataset with recursive jobs, advancing a keyset cursor one window per job. Each
iteration is a single Oban job that processes a batch and schedules its successor, so a backfill
makes steady progress without holding a long transaction or running a long-lived process.</p>
<p>The cursor walks any unique, ordered column—integer or UUID alike—so the same backfill works
across key types. Configure the key, batch limit, and order; throttle to pace the chain; bound
each window with a timeout; or dry run to confirm a backfill terminates without persisting any
writes.</p>
<p>Cursor helpers translate a query into the cursor's advance protocol: update_all/delete_all for
in-place writes, and each/fetch for per-row or batched work such as external API and inference
calls.</p>
<p>Trigger a backfill from a migration or console with start/2, and stop one with cancel/2. A
dedicated index on backfill_id keeps lookups fast.</p>
</li>
<li>
<p>[Chunk] Store only customized options in chunk job meta</p>
<p>Chunk workers previously persisted the full set of chunk options to every job's meta, including
defaults like size, sleep, timeout, and leading. Now only options explicitly set on the worker
are stored, and defaults are merged in at processing time. This shrinks the meta written for
each chunk job while preserving identical runtime behavior.</p>
</li>
<li>
<p>[Cron] Schedule jobs inline on workers and decorated functions</p>
<p>Add a <code>:cron</code> option to <code>Oban.Pro.Worker</code> and <code>Oban.Pro.Decorator</code>. The bare form accepts a cron
expression, while the keyword form supports <code>:expression</code>, <code>:timezone</code>, <code>:name</code>, <code>:guaranteed</code>,
and <code>:paused</code>. An <code>:oban</code> option scopes annotations to a particular Oban instance.</p>
<p>A Mix compiler records annotated workers and decorators in a per-app manifest that
<code>Oban.Pro.Cron</code> loads at startup. Discovery no longer depends on which modules happen to be
loaded, fixing missing entries in environments with lazy code loading.</p>
<p>Using <code>cron:</code> without adding <code>:oban_pro</code> to the project's compilers emits a compile-time
warning.</p>
</li>
<li>
<p>[Diagnostics] Include executing pid in diagnostics reply</p>
<p>The diagnostics reply now carries executing job's pid alongside the existing process info.</p>
</li>
<li>
<p>[Engine] Add <code>per_node</code> option to scale global partitioned limits</p>
<p>Global limits are normally a fixed ceiling across all nodes. The new <code>per_node: true</code> modifier
treats <code>allowed</code> as a per-node value that scales with the number of nodes running a queue.</p>
<p>It's intended for partitioned queues, where each partition stays bounded while the cluster's
throughput grows with capacity. Without a partition it's equivalent to a <code>local_limit</code> of the
same value.</p>
</li>
<li>
<p>[Lifeline] Add selective matching to retry_exhausted</p>
<p>The <code>retry_exhausted</code> option now accepts a simple match spec in addition to a boolean, so
exhausted jobs can be retried for specific workers or queues. Pass workers and/or queues and any
exhausted job that doesn't match is still discarded:</p>
<pre class="lumis" style="color: #c0caf5; background-color: #24283b;"><code class="language-plaintext" translate="no" tabindex="0"><div class="l-line" data-line="1">plugins: [
</div><div class="l-line" data-line="2">  {DynamicLifeline, retry_exhausted: [workers: [MyWorker], queues: [:safe]]}
</div><div class="l-line" data-line="3">]
</div></code></pre>
<p>Passing <code>true</code> or <code>false</code> behaves as before, retrying or discarding every exhausted job.</p>
</li>
<li>
<p>[Migration] Optimize chain and chunk indexes for large tables</p>
<p>Replace existing chain and chunk indexes with definitions tailored to their lookup patterns.
Chain lookups avoid scanning completed jobs, while chunk fetching uses index order for priority
and scheduling, keeping both operations fast as job tables grow.</p>
</li>
<li>
<p>[Pro] Promote dynamic plugins to top-level services</p>
<p>Oban v2.24 introduced dedicated <code>cron</code>, <code>lifeline</code>, <code>pruner</code>, and <code>queue</code> configuration keys, so
the corresponding Pro modules no longer belong under <code>:plugins</code> and no longer need the <code>Dynamic</code>
prefix to distinguish them:</p>
<ul>
<li><code>Oban.Pro.Plugins.DynamicCron</code> → <code>Oban.Pro.Cron</code></li>
<li><code>Oban.Pro.Plugins.DynamicLifeline</code> → <code>Oban.Pro.Lifeline</code></li>
<li><code>Oban.Pro.Plugins.DynamicPruner</code> → <code>Oban.Pro.Pruner</code></li>
<li><code>Oban.Pro.Plugins.DynamicQueues</code> → <code>Oban.Pro.Queues</code></li>
</ul>
<p>The old modules remain as shims so existing configuration and function calls keep working, with
<code>@deprecated</code> annotations pointing at the new names. Nothing needs to change immediately, but
the shims will be removed in a future major version.</p>
</li>
<li>
<p>[Migration] Drop indexes renamed by the v1.7 migration</p>
<p>The v1.7 migration renamed the indexes it replaced with an <code>_old</code> suffix to retain the
originals. Those aren't needed now, and v1.8 drops the remaining indexes to reclaim space and
database cycles.</p>
</li>
<li>
<p>[Pruner] Use persisted and runtime-updatable rules</p>
<p>Replace implicitly ordered queue, state, and worker overrides with database-backed pruning
rules. Rules support compound matches, explicit first-match precedence, per-rule retention
limits, pausing, and runtime management without restarting Oban.</p>
<p>Existing mode and override configuration is translated automatically for backward compatibility.</p>
</li>
<li>
<p>[Pruner] Add declarative job archiving</p>
<p>Rules can now set <code>archive: true</code> to preserve matched jobs instead of deleting them. Archived
jobs are copied to a dedicated <code>oban_jobs_archive</code> table before they're removed from
<code>oban_jobs</code>, retaining the job's full record (args, meta, errors, timestamps) for long-term
storage and later inspection.</p>
<p>The copy runs in the same transaction as the delete, so a job is never lost between the two
tables, and rows are moved with a single set-based insert without round-tripping through the
application.</p>
<p>The <code>oban_jobs_archive</code> table mirrors <code>oban_jobs</code> and is created by the standard v1.8.0
migration. Read archived jobs back through the new <code>Oban.Pro.Archive</code> queryable, which loads
rows as ordinary <code>Oban.Job</code> structs:</p>
<pre class="lumis" style="color: #c0caf5; background-color: #24283b;"><code class="language-plaintext" translate="no" tabindex="0"><div class="l-line" data-line="1">Repo.all(Oban.Pro.Archive)
</div><div class="l-line" data-line="2">
</div><div class="l-line" data-line="3">[worker: MyApp.AuditWorker, state: &quot;completed&quot;]
</div><div class="l-line" data-line="4">|&gt; Oban.Pro.Archive.query()
</div><div class="l-line" data-line="5">|&gt; Oban.all_jobs()
</div></code></pre>
<p>Prune telemetry now includes an <code>archived_count</code> measurement alongside <code>pruned_count</code>.</p>
</li>
<li>
<p>[Worker] Add global overrides for pro worker defaults</p>
<p>Oban bakes in system-wide defaults for options like max_attempts (20), which previously could
only be changed by setting the option on every worker. Pro workers can now override the defaults
for <code>max_attempts</code>, <code>priority</code>, and <code>queue</code> across an entire application:</p>
<pre class="lumis" style="color: #c0caf5; background-color: #24283b;"><code class="language-plaintext" translate="no" tabindex="0"><div class="l-line" data-line="1">config :oban_pro, worker_defaults: [max_attempts: 5, priority: 1]
</div></code></pre>
<p>Defaults apply to any Pro worker that doesn't set the option itself, so values from <code>use</code> or
passed to <code>new/2</code> always take precedence. The config is read at compile time and validated
against the allowed subset, so unknown or invalid options raise with a clear error.</p>
</li>
<li>
<p>[Worker] Add <code>fetch_recorded/2</code> to enhance fetching from external storage</p>
<p>Define <code>Worker.fetch_recorded/2</code> that is capable of fetching externally stored recordings
without the worker module loaded, as well as optionally bypoassing <code>decode</code>. Recorded data also
includes a <code>size</code> attribute as an external hint, so Web can alert users about the overal size
before downloading and displaying large recordings.</p>
</li>
<li>
<p>[Worker] Support encrypted key rotation and authentication</p>
<p>Encrypted workers accept a <code>:keyring</code> module in place of a static <code>:key</code>. Each job records which
key encrypted it, so keys can rotate without stranding jobs inserted beforehand. Define a
keyring module, and configure it for a worker:</p>
<pre class="lumis" style="color: #c0caf5; background-color: #24283b;"><code class="language-elixir" translate="no" tabindex="0"><div class="l-line" data-line="1"><span style="color: #9d7cd8;">use</span> <span style="color: #7dcfff;">Oban.Pro.Worker</span><span style="color: #89ddff;">,</span> <span style="color: #2ac3de;">encrypted: </span><span style="color: #a9b1d6;">[</span><span style="color: #2ac3de;">keyring: </span><span style="color: #7dcfff;">MyApp.Keyring</span><span style="color: #a9b1d6;">]</span>
</div></code></pre>
<p>New jobs use <code>aes_256_gcm</code> rather than <code>aes_256_ctr</code>, authenticating args so tampering fails the
job instead of decrypting to garbage. Existing jobs are still read with <code>aes_256_ctr</code>.</p>
</li>
<li>
<p>[Worker] Add <code>on_retried/2</code> worker hook for retried jobs</p>
<p>Retrying a job marks it as available for future execution but doesn't run the worker
immediately, so there was no execution hook that fired at the moment an operator retried a job.</p>
<p>Add <code>on_retried/2</code> to complete the set of external state hooks. It's called with the <code>:manual</code>
reason after a job's state is reset. As with the other external hooks, the callback runs against
the fully loaded job (decryption and structuring applied) and exceptions are caught and logged
without affecting the retry.</p>
</li>
<li>
<p>[Worker] Use a units syntax for recorded size limits</p>
<p>Add units syntax, <code>{32, :mb}</code>, with support for kb, mb, and gb units to simplify defining
storage size limits.</p>
</li>
<li>
<p>[Worker] Add pluggable storage for recorded job output</p>
<p>Recorded output can now be written to and read from external object stores (S3, R2, Tigris,
etc.) through the new <code>Oban.Pro.Storage</code> behaviour, rather than only living inline on a job.</p>
<p>Setting global worker defaults has moved to <code>:oban_pro, Oban.Pro.Worker</code> for consistency with
other compile-time options, and setting a global <code>recorded</code> option is now also possible.</p>
<pre class="lumis" style="color: #c0caf5; background-color: #24283b;"><code class="language-elixir" translate="no" tabindex="0"><div class="l-line" data-line="1"><span style="color: #7aa2f7;">config</span> <span style="color: #2ac3de;">:oban_pro</span><span style="color: #89ddff;">,</span> <span style="color: #7dcfff;">Oban.Pro.Worker</span><span style="color: #89ddff;">,</span> <span style="color: #2ac3de;">recorded: </span><span style="color: #a9b1d6;">[</span><span style="color: #2ac3de;">storage: </span><span style="color: #7dcfff;">MyApp.S3</span><span style="color: #89ddff;">,</span> <span style="color: #2ac3de;">limit: </span><span style="color: #ff9e64;">32_000_000</span><span style="color: #a9b1d6;">]</span>
</div></code></pre>
</li>
<li>
<p>[Workflow] Add saga-style workflow compensations</p>
<p>Steps declare compensation through an automatically detected <code>compensate/1</code> worker callback or
an explicit function capture. Declarations are serialized into job meta when the workflow is
built, keeping behavior stable across deploys.</p>
<p>When a workflow terminates according to its <code>compensate_on</code> policy, Pro materializes a linked
compensation workflow with completed steps arranged in reverse dependency order. Cascade
compensations receive reconstructed context and recorded ancestry.</p>
<p>Workflow rescue repairs drifted counters and re-evaluates unresolved candidates so a crash
can't strand pending compensation.</p>
</li>
<li>
<p>[Workflow] Expose compensations in workflow status</p>
<p>Rework <code>status/1</code> to read from the workflow table rather than raw jobs, and add a new
<code>compensation</code> field with linked workflow details.</p>
<p>Reading from the table is significantly faster, counts include the <code>suspended</code> state, durations
are only reported once a workflow finishes, and unnamed workflows fall back to a single worker
name.</p>
</li>
</ul>
<h3>Changes</h3>
<ul>
<li>
<p>[Engine] Rename the <code>Smart</code> engine to <code>Oban.Pro.Engine</code></p>
<p>The engine is now <code>Oban.Pro.Engine</code> to match the flatter module names introduced for Pro's
services. Update the <code>:engine</code> option in your config:</p>
<pre class="lumis" style="color: #c0caf5; background-color: #24283b;"><code class="language-diff" translate="no" tabindex="0"><div class="l-line" data-line="1">  config :my_app, Oban,
</div><div class="l-line" data-line="2"><span style="color: #89ddff;">-</span><span style="background-color: #52313f;">   engine: Oban.Pro.Engines.Smart</span>
</div><div class="l-line" data-line="3"><span style="color: #89ddff;">+</span><span style="background-color: #2b485a;">   engine: Oban.Pro.Engine</span>
</div></code></pre>
<p><code>Oban.Pro.Engines.Smart</code> still works and delegates all callbacks to the new module, so there's
no rush to change it and no deprecation warnings.</p>
</li>
<li>
<p>[Worker] Drop the obsolete recorded <code>to</code> option</p>
<p>The option was never promoted into recorded meta or used at fetch time for bulk operations. This
silently drops the option to prevent failing validation for any projects that may have set it.</p>
</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>
<p>[Testing] Await hook execution to prevent leaking side-effects</p>
<p>Hooks triggered by cancellation, exhaustion, and workflow operations were executed in
fire-and-forget tasks. During testing this allowed hooks to finish after a test had already
concluded, producing racy results when draining jobs.</p>
<p>When draining, the task is awaited so all side-effects complete before the drain returns.
Outside of draining it remains fire-and-forget and isolated from the caller.</p>
</li>
<li>
<p>[Worker] Warn about risky unique states in Pro workers</p>
<p>Pro workers validated the shape of <code>:unique</code> options but skipped the advisory check that
<code>Oban.Worker</code> performs, so state lists that can't detect duplicates or omit in-flight states
compiled silently.</p>
</li>
<li>
<p>[Worker] Await async hook side-effects during testing</p>
<p>Bulk worker hooks (<code>on_retried</code>, <code>on_cancelled</code>, <code>on_discarded</code>) and other deferred side-effects
ran in a detached task that borrowed the test's sandbox connection. When a test process exited
while that task was still querying, the connection was reclaimed mid-query, leaking a Postgrex
&quot;owner exited&quot; error even though no assertions failed.</p>
<p><code>Utils.async_maybe_await</code> now awaits whenever testing is enabled, not only during draining, so
these side-effects complete before a test concludes.</p>
</li>
<li>
<p>[Worker] Unify shared result type in the stage module</p>
<p>The <code>c:after_process/3</code> callback stated it only returned <code>:ok</code>, which was incorrect. Now there's
a properly defined <code>t:result/0</code> that is shared between *process callbacks.</p>
</li>
<li>
<p>[Workflow] Return <code>nil</code> for missing recordings in workflows</p>
<p>External recorded output outlives its job and may expire or be reaped from the store while the
job row remains. In that case <code>all_recorded/3</code> and <code>get_recorded/2</code> now return <code>nil</code> for the
absent entry, matching their documented contract, rather than leaking an <code>{:error, :missing}</code>
tuple. A genuinely unreachable store still returns <code>{:error, reason}</code> so an outage isn't
silently mistaken for absent output.</p>
<p>Cascades continue to treat missing dependency output as an error and retry, since a
just-completed step's output should still be present.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.7.11</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.7.11" />
      <id>https://oban.pro/releases/pro/1.7.11</id>
      <published>2026-08-25T00:00:00Z</published>
      <updated>2026-08-25T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Enhancements</h3>
<ul>
<li>
<p>[Smart] Check uniqueness before job insertion</p>
<p>Unique jobs are now verified when they're inserted, safely across processes and nodes, rather
than relying exclusively on the unique index to reject conflicts. The index remains in place as
a backstop, and this version also tolerates non-unique index variants that are better suited to
some databases.</p>
<p>Conflicting inserts return the existing job with <code>conflict?</code> set to <code>true</code>, rather than
annotating the conflicted job's meta with <code>uniq_conflict</code>.</p>
</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>
<p>[Pro] Support upcoming Oban v2.24 changes</p>
<p>Oban v2.24 has config changes and internal module adjustments that Pro relied on. This change
makes it possible to use Pro v1.7 with v2.24 rather than forcing an upgrade to Pro v1.8</p>
</li>
<li>
<p>[Chunk] Use latest completion time to trigger chunks</p>
<p>A leading chunk decides whether to run immediately based on how long it's been since the last
job in the chunk finished. It now derives that from the maximum completion time across the
chunk, which uses the chunk index for a substantial speedup.</p>
<p>This always reflects the true last completion even when a retried job finishes out of id order.</p>
</li>
<li>
<p>[DynamicLifeline] Ensure configured queue included for chunk_id</p>
<p>The configured queue wouldn't always be used when computing the <code>chunk_id</code> value and it would
fall back to &quot;default&quot;. Passing a queue through manually and repairing chunks always used the
correct queue in the calculation, which led to differing chunks for jobs that should be related.</p>
</li>
<li>
<p>[DynamicPruner] Optimize workflow preservation during pruning</p>
<p>Split active workflow checks into targeted queries that properly use existing workflow indexes
and avoid pruning timeouts.</p>
</li>
<li>
<p>[Migration] Reapply SQL functions on every migration run</p>
<p>Functions were created in schema migrations, so they only ran for new versions. That meant
function fixes never reached an already recorded version, subtly breaking functionality.</p>
<p>Trigger and helper functions are now recreated on any migration run, regardless of whether that
version ran before. In particular, this restores the workflow trigger lock ordering and UTC
timestamps for anyone who migrated to 1.7.0 before those fixes shipped.</p>
</li>
<li>
<p>[Smart] Guarantee chains release exactly one held job</p>
<p>Under some query plans, a single flush released every held member of a chain at once, silently
breaking serialization for chains used to enforce one active job at a time.</p>
</li>
<li>
<p>[Worker] Safely cast wait_for infinity in signals</p>
<p>A persisted <code>wait_for: :infinity</code> wasn't correctly cast back to an atom across retries.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.7.10</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.7.10" />
      <id>https://oban.pro/releases/pro/1.7.10</id>
      <published>2026-08-17T00:00:00Z</published>
      <updated>2026-08-17T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Enhancements</h3>
<ul>
<li>
<p>[Smart] Prepare acking query to avoid replanning</p>
<p>Acking ran as an unnamed statement, so it was planned on every call. Adding a name lets Postgres
cache the plan per connection, which cuts overall ack time 1.3-2.5x for typical small batches.</p>
</li>
<li>
<p>[Smart] Support advisory unique indexes for insert-only</p>
<p>Detect when the <code>oban_jobs_unique_index</code> exists on a <code>uniq_key</code> column without a <code>UNIQUE</code>
declaration and switch to an advisory mode. In this mode, dupes are detected with indexed
lookups before insertion, rather than relying on the index to enforce uniqueness through
conflicts.</p>
<p>This primarily benefits CockroachDB, where mid-statement unique validation causes transient
constraint errors during bulk transitions.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.7.9</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.7.9" />
      <id>https://oban.pro/releases/pro/1.7.9</id>
      <published>2026-08-12T00:00:00Z</published>
      <updated>2026-08-12T00:00:00Z</updated>
      <content type="html"><![CDATA[<ul>
<li>
<p>[Smart] Guard against double execution from replayed acks</p>
<p>Acks replayed after an ambiguous commit (committed server-side, but the client saw a connection
error) could rewind a refetched job to the <code>scheduled</code> state and trigger a concurrent execution.</p>
<p>Acks now <em>only</em> apply to the execution that produced them, and stale acks are dropped rather
than replaying until they stick. This situation required precise timing around connection
failures that are unlikely, but possible under load.</p>
</li>
<li>
<p>[Chain] Repeat suspended check during chains repair</p>
<p>Prevents potential double execution of repaired chains by re-using the <code>suspended</code> check
internally to avoid stale subquery snapshot.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.7.8</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.7.8" />
      <id>https://oban.pro/releases/pro/1.7.8</id>
      <published>2026-07-14T00:00:00Z</published>
      <updated>2026-07-14T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Bug Fixes</h3>
<ul>
<li>
<p>[RateLimit] Isolate rate windows by algorithm during rollouts</p>
<p>When a queue switched algorithms and the producers were briefly mixed, a producer could read a
window written by a different algorithm and crash.</p>
<p>Producers now only merge windows written with the same algorithm, so a mixed rollout stays
consistent and clears itself once the switch completes.</p>
</li>
<li>
<p>[RateLimit] Refill idle token buckets in the rate limiter</p>
<p>The token_bucket algorithm stopped refilling once a bucket hit its limit and the queue went
idle, leaving the queue wedged with zero demand until restarted. Idle buckets now refill on
their own after a burst.</p>
</li>
<li>
<p>[RateLimit] Respect partitions in rate limit consumption</p>
<p><code>Oban.Pro.RateLimit.consume/3</code> computed each producer's capacity from global demand. For a
partitioned rate limit this made <code>consume/3</code> disagree with <code>available/2</code> Capacity is now scoped
to the requested partition's window, matching <code>available/2</code> across all algorithms.</p>
</li>
<li>
<p>[Workflow] Wait for deeply nested sub grafts</p>
<p>A downstream job depending on a graft stopped waiting once the workflow containing that graft
was itself grafted. At that point, the consumer cascaded as soon as the grafter finished, before
the grafted jobs ran.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.7.7</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.7.7" />
      <id>https://oban.pro/releases/pro/1.7.7</id>
      <published>2026-06-26T00:00:00Z</published>
      <updated>2026-06-26T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Bug Fixes</h3>
<ul>
<li>
<p>[Smart] Repair unique violations out-of-band</p>
<p>Staging now repairs the conflict out-of-band on a fresh connection and skips the current round,
letting the next pass promote the freed jobs. Recovery no longer depends on the aborted
transaction, so the Stager keeps making progress.</p>
</li>
<li>
<p>[Smart] Refresh partition configuration periodically</p>
<p>In a multi-node deployment, jobs could be inserted without a partition key on nodes that hadn't
yet seen a queue's partition configuration. Once a queue became partitioned, those nodes kept
enqueueing unpartitioned jobs until they were restarted.</p>
<p>Partition configuration is now refreshed periodically on every node, so newly partitioned queues
are picked up everywhere within a short window without requiring a restart.</p>
</li>
<li>
<p>[Smart] Retry producer fetches on lock contention</p>
<p>Stop leaking <code>lock_not_available</code> exceptions that may crash the producer. Lock contention in
CRDB is now treated like a deadlock and retried after a jittery sleep, giving the same try-lock
behaviour as Postgres.</p>
</li>
<li>
<p>[Smart] Correct unique index check for CRDB compatibility</p>
<p>The unique index check didn't account for the sort order, which is always included in CRDB
index definitions.</p>
</li>
<li>
<p>[Worker] Restore deprecated <code>after_process/2</code> hook callback</p>
<p>The two-arity <code>after_process/2</code> callback was deprecated in favor of <code>after_process/3</code> long ago,
but a refactor in 1.7 silently dropped support for it.</p>
</li>
<li>
<p>[Worker] Trigger <code>on_cancelled/2</code> for all expired deadlines</p>
<p>Previously <code>on_cancelled/2</code> only fired for deadlines enforced <em>mid-execution</em> when <code>force</code> was
enabled. Now, jobs whose deadline elapses before execution also trigger with a <code>:deadline</code>
reason.</p>
</li>
<li>
<p>[Worker] Avoid notifier subscription for <code>await_signal/1</code> when draining</p>
<p>Immediately park the job or timeout when draining, rather than attempting to subscribe to a
notifier. Otherwise, expect a Notifier to be running and warn loudly.</p>
</li>
<li>
<p>[Workflow] Fix sub-workflow context jobs with parent deps</p>
<p>Prevent workflow context from being suspended when attaching a sub-workflow with dependencies.</p>
</li>
<li>
<p>[Workflow] Wait for sub-workflows grafted via <code>add_workflow/4</code></p>
<p>A downstream job that depends on a graft failed to wait for grafted sub-workflows. The cascade
ran immediately after the grafter, before any of the grafted jobs executed. Grafted jobs that
aren't direct members of the graft are now tied back to the graft.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.7.6</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.7.6" />
      <id>https://oban.pro/releases/pro/1.7.6</id>
      <published>2026-06-05T00:00:00Z</published>
      <updated>2026-06-05T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Bug Fixes</h3>
<ul>
<li>
<p>[Smart] Guard attempt changes against acking replays</p>
<p>Prevent replayed <code>snooze</code> acking from decrementing an <code>attempt</code> beyond the acceptable range. In
rare cases an ack transaction could commit, but have the connection drop before the response,
which would lead to the entire transaction replaying later. That could violate the
<code>attempt_range</code> check constraint on subsequent fetches, causing a producer crash loop.</p>
</li>
<li>
<p>[Smart] Utilize full limit across partitions with burst</p>
<p>Partitioned queues using burst mode wouldn't fill the entire local quota when there are numerous
active partitions, but only of them were deep. In that situation, shallow partitions would waste
the deep partitions slice, leaving available queue capacity idle.</p>
<p>Burst mode uses a new, optimized query that allows idle capacity to flow to partitions that have
a backlog, so a skewed queue is filled to the limit as expected.</p>
</li>
<li>
<p>[Workflow] Fix deadlocks when staging jobs in workflows</p>
<p>Under high throughput, staging scheduled or retryable jobs in workflows could deadlock against
other job state changes, surfacing as intermittent errors. Workflow progress is now tracked in a
way that avoids the conflict, and staging automatically retries in the rare event a deadlock
still occurs.</p>
</li>
<li>
<p>[Workflow] Prevent <code>status/2</code> from hanging on nested grafts</p>
<p>Checking the status of a workflow that contained grafts nested inside other grafts could loop
indefinitely instead of returning. Status checks now handle nested grafts correctly and return
as expected.</p>
</li>
<li>
<p>[Worker] Park <code>await_signal/1</code> immediately while draining</p>
<p>Calling <code>await_signal/1</code> while draining jobs raised an error because there's no notifier to
deliver a live signal. Now the job parks instead, so a signal buffered before the drain resumes
it as expected.</p>
</li>
<li>
<p>[Worker] Use <code>is_valid_period</code> guard for deadline</p>
<p>Validate using the now-standard, and more accurate, Period guard rather than custom logic.</p>
</li>
<li>
<p>[DynamicLifeline] Repair multiple stalled chains per cycle</p>
<p>Chain repair previously continued a single stalled chain per run, so recovering multiple
stranded chains took multiple repairs. Repair now advances up to the DynamicLifeline's
<code>repair_limit</code> chain's head in one pass.</p>
</li>
<li>
<p>[DynamicLifeline] Rescue workflows that never had a job start</p>
<p>The workflow rescue sweep filtered on <code>started_at &lt; threshold</code>, which is never true when a
workflow's upstream jobs were deduplicated by a unique conflict. Those workflows lacked
<code>started_at</code> data and stayed orphaned in the database.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.7.5</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.7.5" />
      <id>https://oban.pro/releases/pro/1.7.5</id>
      <published>2026-05-27T00:00:00Z</published>
      <updated>2026-05-27T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Bug Fixes</h3>
<ul>
<li>
<p>[Batch] Fix incorrect types used in batch validation</p>
<p>Oban v2.23 fixed a longstanding bug in the validator logic that would silently let unknown types
fall through and be ignored. This updates Batch validation to use properly defined types.</p>
</li>
<li>
<p>[Smart] Apply burst quota to untracked partitions</p>
<p>Previously, the per-partition demand for keys not yet present in <code>tracked</code> used the global_limit
rather than the burst quota. On the first fetch for a newly active partition, including cold
start, only <code>allowed</code> jobs would dispatch, even when burst capacity was free.</p>
</li>
<li>
<p>[Workflow] Preserve sub-workflow identity through <code>apply_graft/2</code></p>
<p>Multiple <code>add_many</code> sub-workflows in a single graft could collide because they shared common
names (&quot;0&quot;, &quot;1&quot;, etc.) and a single workflow id. Now the sub-workflow id is retained for grafts,
eliminating the collision without changing auto-generated job names.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.7.4</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.7.4" />
      <id>https://oban.pro/releases/pro/1.7.4</id>
      <published>2026-05-20T00:00:00Z</published>
      <updated>2026-05-20T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Enhancements</h3>
<ul>
<li>
<p>[Smart] Fill all burst slots under uneven partition counts</p>
<p>Burst mode is meant to maximize throughput by letting partitions exceed their per-partition
global limit when capacity is available. With an uneven split, such as <code>local_limit: 5</code> across
two active partitions, each partition was capped at 2 and one slot sat idle.</p>
<p>Burst now fills the remaining capacity, with the extra slot going to whichever partition has the
highest-priority next job.</p>
</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>
<p>[DynamicLifeline] Fix repair cycle on Ecto 3.14+</p>
<p>The <code>:repair_limit</code> option was passed through to <code>Repo.all</code> and <code>Repo.update_all</code> as a query
option, which Ecto 3.14 rejects (earlier versions silently ignored it), breaking workflow,
chain, partition, and chunk repairs.</p>
</li>
<li>
<p>[Smart] Retry fetching after deadlock errors</p>
<p>An unhandled <code>40P01</code> raised within a producer would terminate it. The crashed producer was
unregistered and no longer refreshed, and the leader cleaned it up, making the queue disappear
until it was restarted.</p>
</li>
<li>
<p>[Workflow] Serialize concurrently acking jobs from a shared workflow</p>
<p>Producers acking workflow jobs could deadlock on <code>oban_workflows</code> rows when the update trigger
locked them in non-deterministic order. Take per-workflow locks so concurrent flushing serialize
before the trigger fires, eliminating the deadlock rather than relying on retry.</p>
</li>
<li>
<p>[Diagnostics] Pull oban conf directly from registery</p>
<p>Prevent testing race conditions caused by oban instances shutting down between selecting them
from the registry and pulling configuration.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.7.3</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.7.3" />
      <id>https://oban.pro/releases/pro/1.7.3</id>
      <published>2026-05-08T00:00:00Z</published>
      <updated>2026-05-08T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Enhancements</h3>
<ul>
<li>
<p>[DynamicCron] Add <code>cron_at</code> to scheduled job metadata</p>
<p>Each scheduled job now carries its slot time in UTC under <code>cron_at</code>, alongside the existing cron
fields. The slot is the same value for both on-time and guaranteed catch-up inserts, giving
callbacks a stable time anchor that survives retries and re-runs without re-parsing the cron
expression.</p>
</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>
<p>[Worker] Preserve <code>unsaved_error</code> in job error hooks</p>
<p>The original error was overwritten after resolving structured args, which was a regression from
the previous behavior.</p>
</li>
<li>
<p>[Chain] Restore predicate in chain query for partial index</p>
<p>One of the primary chain subqueries lacked a predicate check, which prevented the query from
using the proper chain index.</p>
</li>
<li>
<p>[Chain] Process hybrid legacy and suspended chains</p>
<p>When any legacy chain existed, the chain flusher and rescuer would only match the legacy
representation and skip suspended followers entirely. This is a parallel fix to the recent
legacy workflow changes.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.7.2</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.7.2" />
      <id>https://oban.pro/releases/pro/1.7.2</id>
      <published>2026-05-06T00:00:00Z</published>
      <updated>2026-05-06T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Bug Fixes</h3>
<ul>
<li>
<p>[Workflow] Correctly process hybrid legacy and suspended workflows</p>
<p>Workflows with mixed <code>on_hold</code> meta and <code>suspended</code> states weren't transitioned properly, which
could leave those workflows stuck and without a way to recover.</p>
<p>Now on-hold 'scheduled' and 'suspended' jobs are checked together for legacy workflows. The
detection cache now expires as well, so a legacy system can transition to non-legacy after older
jobs have processed.</p>
</li>
<li>
<p>[Chain] Detect legacy chains separately from workflows</p>
<p>Split the legacy detection so chain rescue and flushing can be gated on chain data alone. The
previous check required a legacy workflow, so a system without legacy workflows never rescued
legacy chains.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.7.1</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.7.1" />
      <id>https://oban.pro/releases/pro/1.7.1</id>
      <published>2026-05-04T00:00:00Z</published>
      <updated>2026-05-04T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Bug Fixes</h3>
<ul>
<li>
<p>[Worker] Encode signal payloads as native terms</p>
<p>Signal payloads were silently converted to JSON, which would cause an exception for unencodable
terms such as tuples. Signals are now treated the same as recorded job data, so any term
round-trips through signal/await_signal intact.</p>
</li>
<li>
<p>[Migration] Stop dropping any indexes in 1.7 migration</p>
<p>Previously, the v1.7 migration dropped several indexes during the upgrade to make way for
optimized replacements. Now the migration renames the originals with an <code>_old</code> suffix and
creates the new indexes alongside them, leaving the slow drops to a separate cleanup migration
users run on their own schedule.</p>
</li>
<li>
<p>[Smart] Repair more states during unique violations</p>
<p>When the Smart engine hits a unique violation transitioning a job, it previously only cleared
siblings in the executing state, leaving potential duplicates in available or retryable to trip
the same violation on their next transition.</p>
</li>
<li>
<p>[Workflow] Fix timezone shift in workflow timestamp triggers</p>
<p>The workflows trigger used <code>now()</code> to populate timestamps, which returned <code>timestamptz</code> and
could differ from UTC used elsewhere. Now timestamps are cast as UTC for consistency.</p>
</li>
</ul>
<p><code>signal/3</code> and <code>await_signal/1</code>.</p>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.7.0</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.7.0" />
      <id>https://oban.pro/releases/pro/1.7.0</id>
      <published>2026-04-30T00:00:00Z</published>
      <updated>2026-04-30T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Enhancements</h3>
<ul>
<li>
<p>[Worker] Add <code>signal</code> and <code>await_signal</code> for external events</p>
<p>Introduces <code>signal/2,3</code> and <code>await_signal/1</code>, letting jobs pause mid-execution and resume when a
signal arrives. The Smart engine preserves scheduled_at when a signal lands during the snooze
ack window, so signals delivered before, during, or after parking are never lost.</p>
<p><code>Workflow.signal/3,4</code> is a thin wrapper that resolves named jobs and delegates to the worker.</p>
</li>
<li>
<p>[Workflow] Restructure all_recorded query for index optimization</p>
<p>Restructure the fragment so each disjunct includes its own containment predicate.</p>
</li>
<li>
<p>[Workflow] Optimize legacy suspended check to use workflow index</p>
<p>Replace the containment operator <code>(@&gt;)</code> in legacy workflow detection query with key existence
operators that match the partial workflow index, avoiding a sequential scan when the <code>meta</code> GIN
index is removed.</p>
</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>
<p>[Smart] Determine unique mode based on index shape</p>
<p>The mode must be determined from the shape of <code>oban_jobs_unique_index</code> itself rather than column
presence. An upgraded database can retain the legacy <code>uniq_key</code> generated column while the only
remaining unique index is the expression-based one created by the v1.7 migration.</p>
</li>
<li>
<p>[Usage Rules] Include usage-rules in packaged releases</p>
<p>None of the usage-rules were included in the packaged release.</p>
</li>
</ul>
<h3>Changes</h3>
<ul>
<li>
<p>[Chunk] Rename Oban.Pro.Workers.Chunk to Oban.Pro.Chunk</p>
<p>Rename the Chunk worker to <code>Oban.Pro.Chunk</code> to match the naming convention used by <code>Batch</code> and
<code>Workflow</code>. The old module is preserved as a deprecated shim that delegates to the new module.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.7.0-rc.3</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.7.0-rc.3" />
      <id>https://oban.pro/releases/pro/1.7.0-rc.3</id>
      <published>2026-04-04T00:00:00Z</published>
      <updated>2026-04-04T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Enhancements</h3>
<ul>
<li>
<p>[Workflow] Optimize workflow flush query for pathological cases</p>
<p>Overhaul workflow flushing query to avoid lateral sub-queries, minimize buffer hits, avoid
repeated TOAST reads, and use a more efficient hash join for same-workflow dependencies.</p>
<p>For a 10_000-&gt;1 fan-in workflow the result is 31x faster, with 222x fewer buffer hits.</p>
</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>
<p>[Pro] Fix bootstrap file checking on Windows</p>
<p>File checks failed on Windows because Mix copies files to <code>_build</code> instead of symlinking. The
NIF was navigating from <code>priv_dir</code> up to find source files, which only worked when symlinks
resolved to deps.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.7.0-rc.2</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.7.0-rc.2" />
      <id>https://oban.pro/releases/pro/1.7.0-rc.2</id>
      <published>2026-03-31T00:00:00Z</published>
      <updated>2026-03-31T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Enhancements</h3>
<ul>
<li>
<p>[Refresh] Gracefully check for hex availability</p>
<p>Ensure hex is available before attempting to update in the <code>oban_pro.refresh</code> task.</p>
</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>
<p>[Pro] Update app modules list after compiling sources</p>
<p>Encrypted modules handled by the <code>:oban_pro</code> compiler weren't included in the <code>.app</code> file
because it was generated before they were compiled. Releases use the modules list to determine
what to load at boot, causing releases to crash with <code>UndefinedFunctionError</code> for encrypted
modules.</p>
</li>
<li>
<p>[Pro] Fix Windows NIF loading in bootstrap module</p>
<p>On Windows, BEAM passes NIF function pointers via a callback table at load time instead of
resolving them through dynamic linking. The bootstrap module now properly defines, resolves, and
casts callbacks for cross-platform operation.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.7.0-rc.1</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.7.0-rc.1" />
      <id>https://oban.pro/releases/pro/1.7.0-rc.1</id>
      <published>2026-03-26T00:00:00Z</published>
      <updated>2026-03-26T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Bug Fixes</h3>
<ul>
<li>
<p>[Migration] Stop adding <code>suspended</code> state to the <code>oban_jobs_state</code> enum</p>
<p>The <code>suspended</code> state must be added by Oban's v14 migration prior to running the Pro 1.7.0
migration. Postgres prohibits altering a type and referencing it within the same transaction,
and the upgrade could fail.</p>
</li>
<li>
<p>[Workflow] Use <code>t:add_cascade_opts/0</code> for <code>add_graft/4</code></p>
<p>The <code>add_graft/4</code> function accepts workflow opts in addition to job opts, but only specified the
<code>t:add_opts/0</code> type.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.6.14</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.6.14" />
      <id>https://oban.pro/releases/pro/1.6.14</id>
      <published>2026-03-26T00:00:00Z</published>
      <updated>2026-03-26T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Enhancements</h3>
<ul>
<li>[Chunk] Backport <code>chunk_id</code> generation for smoother upgrade to v1.7</li>
</ul>
<p>Generate a <code>chunk_id</code> at job insertion time and store it in meta. This pre-populates the
identifier that Pro v1.7 uses for optimized chunk queries, ensuring jobs inserted on v1.6 will
work seamlessly immediately after upgrading. Pro v1.7 will backfill jobs with a missing <code>chunk_id</code>
after a minute or so, but does so in batches.</p>
<h3>Bug Fixes</h3>
<ul>
<li>
<p>[Worker] Conditionally add @impl to worker <strong>opts</strong>/0</p>
<p>Oban v2.21 marked <code>c:Worker.__opts__/0</code> as a callback and made it public. Now, <code>Oban.Pro.Worker</code>
marks the <code>__opts__/0</code> function as a callback for Oban v2.21, but not for older versions.</p>
</li>
<li>
<p>[Unique] Include suspended state in unique bmp mapping</p>
<p>The <code>suspended</code> state is available for uniqueness as of Oban v2.21, but it was lacking from the
uniq bmp mapping. The state is now supported to prevent errors when inserting unique jobs.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.7.0-rc.0</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.7.0-rc.0" />
      <id>https://oban.pro/releases/pro/1.7.0-rc.0</id>
      <published>2026-03-25T00:00:00Z</published>
      <updated>2026-03-25T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Enhancements</h3>
<ul>
<li>
<p>[Pro] Use suspended state for workflow and chain tracking</p>
<p>Jobs waiting on workflow or chain dependencies now use a proper <code>suspended</code> job state instead of
the previous <code>on_hold</code> psuedo-state.</p>
<p>This provides cleaner state semantics, better query performance through simplified indexes, and
enables the database triggers to track workflow state counts accurately. The <code>scheduled_at</code>
timestamp is preserved directly on suspended jobs, eliminating the need for <code>orig_scheduled_at</code>
in meta.</p>
</li>
<li>
<p>[Pro] Add usage rules for agentic coding assistants</p>
<p>Ship reference documents that help coding agents understand Pro's idioms and best practices.
Rules cover workers, queues, composition primitives (workflows, batches, chains, chunks),
plugins, and testing.</p>
</li>
<li>
<p>[Chunk] Optimize queries with centralized index and better job acking</p>
<p>Use a pre-computed <code>chunk_id</code> for chunk tracking, enabling a partial index for <em>much</em> faster
chunk lookups. This eliminates dynamic query construction based on partitioning fields in favor
of direct <code>chunk_id</code> matching.</p>
<p>Chunks now use a single SQL operation for acking , reducing database round-trips when
completing, cancelling, or retrying jobs within a chunk. The new acking operation has better
compatibility with non-Postgres databases such as CockroachDB.</p>
</li>
<li>
<p>[Chunk] Add <code>:snooze</code>  support to chunk workers</p>
<p>Chunks can now selectively snooze jobs using <code>{:snooze, period, jobs}</code> or selectively snooze
some jobs with <code>snooze: {period, jobs}</code> in the keyword list result. Snoozed jobs are rescheduled after the specified
period, while unlisted jobs complete normally.</p>
</li>
<li>
<p>[DynamicCron] Add <code>get/2</code> function for fetching entries by name</p>
<p>Provides a convenient way to retrieve a single cron entry without fetching all entries. Accepts
either a worker module or custom string name and returns {:ok, entry} or {:error, message}.</p>
</li>
<li>
<p>[DynamicLifeline] Improve workflow rescue accuracy and remove limit</p>
<p>The DynamicLifeline's workflow rescue mechanism now queries the aggregate table for workflows
with suspended jobs, providing more accurate detection than scanning the jobs table for
suspended jobs alone.</p>
<p>This catches edge cases where workflows are stuck but their suspended jobs may have been lost or
deleted. Legacy <code>on_hold</code> workflows that predate the aggregate table are still found via a
direct jobs table query.</p>
<p>Only workflows that have been executing for more than a minute are candidates for rescuing by
default.</p>
</li>
<li>
<p>[DynamicLifeline] Automatically repair chunk jobs missing <code>chunk_id</code></p>
<p>Chunk workers now use a pre-computed <code>chunk_id</code> for grouping. Jobs created before this change
won't have a <code>chunk_id</code> in their metadata, which would prevent them from being grouped correctly.</p>
<p>The DynamicLifeline plugin now automatically computes and sets the <code>chunk_id</code> for any chunk jobs
that are missing it, similar to how it repairs missing <code>partition_key</code> values for partitioned
queues.</p>
</li>
<li>
<p>[DynamicPruner] Add configurable <code>preserve_workflows</code> option</p>
<p>Allow disabling workflow job preservation during pruning via the new <code>preserve_workflows</code>
option, which defaults to true for backwards compatibility. When disabled, jobs are pruned
regardless of whether their workflow is still active. This is useful for large workflows that
naturally run longer than a pruning cycle.</p>
</li>
<li>
<p>[Migration] Replace generated columns with expression indexes</p>
<p>Generated columns for <code>uniq_key</code> and <code>partition_key</code> were originally added for CockroachDB
compatibility but introduced unnecessary complexity and excessive table locking for large
tables. This change replaces them with expression indexes directly on the meta JSONB fields.</p>
<p>A <code>generated_columns</code> migration option is still available for apps that are running CockroachDB
and need the old functionality. The Smart engine detects which mode is being used and handles
conflicts accordingly.</p>
</li>
<li>
<p>[Migration] Add partial indexes for pruning and staging</p>
<p>Partial indexes reduce index size and improve query performance by only indexing rows that match
the filter condition. This adds partial indexes for terminal job states and a staging index for
jobs ready to transition to <code>available</code>.</p>
<p>Also fixes completed job pruning to use <code>completed_at</code> instead of <code>scheduled_at</code>, which is the
semantically correct timestamp for determining job age.</p>
<p>The new staging query may perform 2-10x faster depending on the number of jobs and overall state
distribution.</p>
</li>
<li>
<p>[Rate Limit] Add centralized module for using rate limits outside of job execution</p>
<p>The module's functions allow checking, resetting, and consuming rate limits from running queues.</p>
<p>The <code>consume/3</code> function is fully distributed and spreads consumption across multiple nodes when
a single producer cannot satisfy the request.</p>
</li>
<li>
<p>[Smart] Add <code>auto_space</code> option to spread out bulk inserts</p>
<p>When inserting large batches of jobs, <code>auto_space</code> schedules each batch at
increasing intervals. This prevents overwhelming queues when jobs can't
all execute immediately.</p>
</li>
<li>
<p>[Smart] Add <code>on_conflict: :skip</code> option for bulk inserts</p>
<p>Support skipping unique conflicts without row locking during insert_all. When enabled,
conflicting jobs are silently skipped and only newly inserted jobs are returned. This improves
performance for high-throughput scenarios where tracking conflicts isn't needed.</p>
</li>
<li>
<p>[Smart] Add transaction option for bulk insert atomicity</p>
<p>Support <code>transaction: :per_batch</code> to commit each batch independently during <code>insert_all/2</code>.
Previously inserted batches persist even if a later batch fails. The default transaction: :all
preserves the existing all-or-nothing behavior.</p>
</li>
<li>
<p>[Smart] Add telemetry sub-spans for engine fetch_jobs</p>
<p>Instrument the fetch_jobs transaction with nested telemetry spans for granular observability
into acking, flushing, demand calculation, and job fetching. Each sub-span emits standard span
events under <code>[:oban, :engine, :fetch_jobs, :ack | :flush | :demand | :fetch]</code>.</p>
</li>
<li>
<p>[Smart] Support selecting between multiple rate limiting algorithms</p>
<p>Rate limited queues can select from <code>:sliding_window</code>, <code>:fixed_window</code>, and <code>:token_bucket</code>
algorithms to control how rate quotas are consumed. The <code>:sliding_window</code> algorithm remains the
default.</p>
</li>
<li>
<p>[Smart] Add <code>:fixed_window</code> rate limiting algorithm</p>
<p>Introduce a fixed window algorithm, which resets the count when the period expires rather than
using weighted averaging.</p>
</li>
<li>
<p>[Smart] Add token bucket rate limiting algorithm</p>
<p>Introduce token bucket algorithm that refills tokens continuously at a fixed rate rather than
resetting at period boundaries. Tokens refill at <code>allowed / period</code> per second, providing
smoother rate limiting with natural burst handling.</p>
</li>
<li>
<p>[Worker] Add <code>@impl</code> declaration for worker <code>__opts__/0</code></p>
<p>Mark <code>Oban.Pro.Worker.__opts__/0</code> as implementing the new <code>__opts__/0</code> public callback from
<code>Oban.Worker</code>.</p>
</li>
<li>
<p>[Worker] Add <code>on_cancelled/2</code> and <code>on_discarded/2</code> worker hooks</p>
<p>Introduce two new worker callbacks that fire when jobs are cancelled or discarded, regardless of
how the state transition happens:</p>
<ul>
<li><code>on_cancelled/2</code> receives :dependency or :manual reason</li>
<li><code>on_discarded/2</code> receives :exhausted reason</li>
</ul>
<p>Both callbacks work with global hooks via <code>attach_hook/1</code> and module-level hooks via the
<code>:hooks</code> option.</p>
</li>
<li>
<p>[Worker] Apply structured args <code>timeout/1</code> and <code>backoff/1</code></p>
<p>Automatically apply encryption and structuring before calling user defined <code>timeout/1</code> or
<code>backoff/1</code> implementations. This allows pattern matching on structured args without any code
changes.</p>
</li>
<li>
<p>[Worker] Variable weight rate limit tracking via options and callback</p>
<p>Add support for job weights in rate limiting, allowing jobs to consume variable amounts of
rate limit capacity:</p>
<ul>
<li>Worker option: <code>use Oban.Pro.Worker, rate: [weight: 5]</code></li>
<li>Job option: <code>Worker.new(args, rate: [weight: 3])</code></li>
<li>Callback: <code>weight/1</code> for dynamic weight calculation at dispatch time</li>
</ul>
<p>Jobs with higher weights consume more rate limit capacity, enabling
fine-grained control over resource-intensive operations.</p>
</li>
<li>
<p>[Workflow] Optimize workflow flushing with de-duplication</p>
<p>Restructure workflow flushing to compute dependency states once per unique dependency rather
than once per job-dep combination. This eliminates the M*N scaling problem when M jobs share
common dependencies.</p>
<p>Benchmarks show ~2x faster execution, 7x fewer buffer hits, and 15x fewer index scans for
workflows with shared dependencies.</p>
</li>
<li>
<p>[Workflow] Flushing is optimized to load minimal data up front</p>
<p>Only the exact data needed for workflow flush operations is loaded from the database, rather
than the entire job structure. This saves data over the wire, serialization overhead, and memory
usage for active workflows or jobs with large <code>args</code>, <code>errors</code>, or <code>meta</code>.</p>
<p>The full job structure is loaded asynchronously when cancellation callbacks are needed.</p>
</li>
<li>
<p>[Workflow] Add table for centralized workflow tracking</p>
<p>Introduces a dedicated table to track workflow metadata and job state counts, replacing
expensive aggregation queries with precomputed values. This improves performance for large
workflows and enables efficient filtering/sorting in Oban Web.</p>
</li>
<li>
<p>[Workflow] Add unique workflow support to prevent duplicates</p>
<p>Workflows can now be created with <code>unique: true</code> to prevent multiple workflows with the same
name from running concurrently. When a duplicate unique workflow is inserted, its jobs are
marked with <code>conflict?: true</code> instead of being inserted.</p>
</li>
</ul>
<h3>Changes</h3>
<ul>
<li>
<p>[Pro] Packages are distributed with encrypted source code</p>
<p>Pro packages are encrypted, with licenses that stay fresh for 30 days. Development remains
seamless, so documentation, type signatures, and LSP integration all work normally.</p>
<p>Enterprise license holders receive unencrypted source code.</p>
<p>See the <a href="v1-7.html">Upgrade Guide</a> for details on checking license status and refreshing.</p>
</li>
</ul>
<h3>Deprecations</h3>
<ul>
<li>
<p>[DynamicPartitioner] Deprecate the <code>DynamicPartitioner</code> plugin</p>
<p>The complexity and edge cases introduced by partitioned tables far outweigh the benefits for
most applications.</p>
</li>
<li>
<p>[Workflow] Deprecate <code>after_cancelled/2</code> in favor of universal <code>on_cancelled/2</code></p>
<p>The <code>after_cancelled/2</code> callback is deprecated in favor of the universal <code>on_cancelled/2</code> hook.
Currently, <em>both</em> hooks will be called if defined, and users should switch to <code>on_cancelled/2</code>.</p>
</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>
<p>[Migration] Fix migration check crash with dynamic repo config</p>
<p>The migration telemetry handler crashed on startup when users configured a placeholder repo
module with <code>get_dynamic_repo</code> providing the actual repo at runtime. The handler attempted to
call <code>get_dynamic_repo/0</code> on the static repo before evaluating the dynamic repo callback.</p>
</li>
<li>
<p>[Refresher] Add error handling to producer record refreshing</p>
<p>Previously, if refresh_producers or cleanup_producers raised (e.g., due to a connection checkout
timeout), the GenServer would crash and restart, causing missed heartbeats and timer resets. Now
errors are caught and logged, allowing the refresh cycle to continue uninterrupted.</p>
</li>
<li>
<p>[Testing] Ensure ordered <code>run_workflow/2</code> output</p>
<p>Always order workflow jobs by execution completion order to preserve sequential execution
results.</p>
</li>
<li>
<p>[Worker] Trigger <code>on_cancelled/2</code> when deadline force-cancels</p>
<p>When a job with <code>deadline: [force: true]</code> exceeds its deadline during execution, the
<code>on_cancelled/2</code> hook is now called with <code>:deadline</code> as the reason. This allows workers to
perform cleanup or notifications when jobs are terminated due to deadline expiration.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.6.13</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.6.13" />
      <id>https://oban.pro/releases/pro/1.6.13</id>
      <published>2026-03-02T00:00:00Z</published>
      <updated>2026-03-02T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Enhancements</h3>
<ul>
<li>
<p>[Oban] Add job diagnostics for runtime process inspection</p>
<p>Introduce runtime diagnostics for executing <code>Oban.Pro.Worker</code> jobs, allowing external tools like
Oban Web to request process information (stacktrace, memory, status) for running jobs via
PubSub.</p>
</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>
<p>[DynamicCron] Skip worker validation when deleting DynamicCron</p>
<p>When a worker module is removed from the codebase, it was previously impossible to delete its
persisted cron entry using the delete: true option because validation would fail trying to load
the module.</p>
</li>
<li>
<p>[Smart] Fix unique violation retry loop for similar states</p>
<p>Clearing unique violations could fail to find conflicting jobs when the job's current state
(e.g., <code>retryable</code>) was included in its unique states. This caused the clearing query not to
match, so nothing would be cleared, causing an infinite retry loop.</p>
<p>Retries are now limited and an error logged for excessive retry loops.</p>
</li>
<li>
<p>[Smart] Prevent chain race condition on concurrent insert</p>
<p>When two transactions concurrently insert jobs for the same chain without any prior jobs in the
chain, both could see &quot;no existing job&quot; and insert as <code>available</code>, violating sequential
guarantees.</p>
</li>
<li>
<p>[Smart] Include <code>:meta</code> options in <code>t:partition/0</code></p>
<p>Using <code>meta</code> for partitioning has been supported for a while now, but the type was outdated.</p>
</li>
<li>
<p>[Workflow] Fix context for deps in nested sub-workflows</p>
<p>When using <code>add_cascade</code> with a fan-out tuple (e.g., <code>{items, &amp;fun/2}</code>) inside a nested
sub-workflow that also has dependencies, the fan-out jobs would incorrectly receive the outer
workflow's context instead of their immediate parent sub-workflow's context.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.6.12</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.6.12" />
      <id>https://oban.pro/releases/pro/1.6.12</id>
      <published>2026-01-30T00:00:00Z</published>
      <updated>2026-01-30T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Enhancements</h3>
<ul>
<li>
<p>[DynamicCron] Optimize cron entry insertion history tracking</p>
<p>The history tracking for cron entries retained too much information and was needlessly expensive
to read and write. This simplifies the insertion format and dramatically simplifies tracking
updates.</p>
<p>The optimization is backward compatible and existing arrays with history will continue to work,
they'll just be replaced with single-element arrays on the next insertion.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.6.11</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.6.11" />
      <id>https://oban.pro/releases/pro/1.6.11</id>
      <published>2026-01-19T00:00:00Z</published>
      <updated>2026-01-19T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Enhancements</h3>
<ul>
<li>
<p>[Workflow] Support fan-out tuple form in add_graft/4</p>
<p>The type specification for <code>add_graft/4</code> indicated support for the cascade fan-out form
<code>{Enum.t(), (any(), map() -&gt; any())}</code>, but only the single-function form was implemented.</p>
<p>Now you can fan out graft points the same way you would with <code>add_cascade</code>:</p>
<pre class="lumis" style="color: #c0caf5; background-color: #24283b;"><code class="language-plaintext" translate="no" tabindex="0"><div class="l-line" data-line="1">Workflow.new()
</div><div class="l-line" data-line="2">|&gt; Workflow.add(:setup, SetupWorker.new(%{}))
</div><div class="l-line" data-line="3">|&gt; Workflow.add_graft(:process, {items, &amp;process_item/2}, deps: :setup)
</div><div class="l-line" data-line="4">|&gt; Workflow.add(:finalize, FinalizeWorker.new(%{}), deps: :process)
</div></code></pre>
</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>
<p>[Workflow] Wait for arbitrarily nested grafted sub-workflows</p>
<p>Jobs depending on a graft now properly wait for dynamically appended sub-workflows at <em>any</em>
nesting depth. Previously, when a grafted workflow used <code>append</code> + <code>add_many</code> to create
additional jobs, the dependent job could run before those nested jobs completed.</p>
<p>The flush mechanism now tracks the complete ancestor chain so workflows flush correctly
regardless of how deeply they are nested.</p>
</li>
<li>
<p>[Workflow] Inherit all context for nested sub-workflows</p>
<p>When using <code>add_cascade</code> with fan-out inside nested sub-workflows, context from intermediate
parent workflows was not accessible. Jobs would only see context from the outermost workflow,
skipping any workflows in between.</p>
<p>Now context is correctly inherited through any depth of workflow nesting. Each level's context
is merged in order from outermost to innermost, with closer ancestors overriding values from
farther ones.</p>
</li>
<li>
<p>[Smart] Ensure partition key cache never hits under load</p>
<p>The timed cache for partition keys could fail to return cached results when jobs were being
inserted concurrently, causing repeated database queries for available partition keys. This
removes stale telemetry handlers that were interfering with cache lookups.</p>
</li>
<li>
<p>[DynamicLifeline] Correctly filter empty partitions during rescue queries</p>
<p>The partition rescue query could incorrectly check producers with empty partitions after queue
updates.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.6.10</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.6.10" />
      <id>https://oban.pro/releases/pro/1.6.10</id>
      <published>2026-01-16T00:00:00Z</published>
      <updated>2026-01-16T00:00:00Z</updated>
      <content type="html"><![CDATA[<ul>
<li>
<p>[Smart] Improve partition query by repairing unpartitioned jobs</p>
<p>Partitioning selects fewer keys, more accurately, to prioritize active partitions. This ensures
partitions with higher priority or older jobs are processed first, while still distributing work
across all active partitions.</p>
<p>Jobs in partitioned queues that are missing a partition key (such as jobs scheduled before a
queue was partitioned) are now repaired by <code>DynamicLifeline</code> automatically.</p>
<p>Overall, this change means fetching jobs in partitioned queues requires fewer resources even
after bulk job inserts.</p>
</li>
<li>
<p>[Smart] Add telemetry and logging for unique repairs</p>
<p>Emit <code>[:oban, :engine, :uniq_violation_repaired]</code> telemetry on every unique repair and log an
error once per unique key to surface potential issues that could otherwise silently degrade
staging and fetching performance.</p>
</li>
<li>
<p>[Workflow] Preserve name for deeply nested sub-workflows</p>
<p>Preserve the sub-workflow's name for jobs from nested sub-workflows when composing workflows
with <code>add_workflow/3</code>. Previously, nesting a workflow containing <code>add_many/3</code> inside another
<code>add_workflow/3</code> would incorrectly overwrite the inner jobs' metadata.</p>
</li>
<li>
<p>[Workflow] Prevent context conflicts in deeply nested workflows</p>
<p>Preserve the workflow id for jobs from nested sub-workflows to prevent multiple context jobs
from sharing the same <code>workflow_id</code>. This eliminates an <code>Ecto.MultipleResultsError</code> when using
<code>put_context/2</code> in workflows nested multiple levels deep via <code>add_workflow/2</code>.</p>
</li>
<li>
<p>[Workflow] Wait for dynamically appended jobs for grafted workflows</p>
<p>Jobs depending on a grafted workflow now correctly wait for all dynamically inserted jobs,
including those added via <code>add_many/4</code> within <code>apply_graft/2</code>.</p>
<p>Previously, when a grafter job used <code>apply_graft/2</code> with a workflow containing nested
sub-workflows (from <code>add_many/4</code>), dependent jobs would execute before the appended jobs
completed.</p>
</li>
<li>
<p>[Workflow] Track recursive graft dependencies from parent workflows</p>
<p>When a grafting job creates sub-grafts recursively, jobs depending on the original graft now
correctly wait for all recursive grafts to complete.</p>
<p>The fix ensures nested graft jobs share the root graft's <code>workflow_id</code> so the parent's wildcard
dependency can find them.</p>
</li>
<li>
<p>[Worker] Validate structured args during <code>update_job/3</code></p>
<p>Structured args weren't validated during <code>update_job/3</code> calls, which could lead to errors when
the job eventually processed. Now, same validation is applied on update as when the job is built
with <code>new/2</code></p>
</li>
<li>
<p>[Decorator] Correct typespec for the subset of unique opts for decorated jobs</p>
<p>The typespec for <code>period</code>, <code>states</code>, and <code>timestamp</code> were incomplete or too loose. The new
typespec references the types from <code>Oban.Job</code>.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.6.9</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.6.9" />
      <id>https://oban.pro/releases/pro/1.6.9</id>
      <published>2025-11-25T00:00:00Z</published>
      <updated>2025-11-25T00:00:00Z</updated>
      <content type="html"><![CDATA[<ul>
<li>
<p>[Smart] Fix inaccurate acking for externally modified jobs</p>
<p>When another node or process updates a job while it's executing, or inserts a conflicting unique
job, the ack query could fail to lock or update. This could cause mismatches between tracked and
actual job states.</p>
</li>
<li>
<p>[Worker] Ensure <code>after_process/3</code> hooks trigger on cancellation</p>
<p>Executing jobs that were manually cancelled via <code>Oban.cancel_job</code> or similar mechanisms, didn't
have <code>after_process/3</code> hooks triggered due to a caching issue. That situation is handled now and
hooks are called as expected.</p>
</li>
<li>
<p>[Refresher] Cleanup producers regardless of running queues</p>
<p>The refresher will now cleanup stale producers from any leader node, not just nodes that are
running queues. This aims to ensure producers are cleaned up even with subtle misconfigurations,
e.g. disabling queues without disabling plugins.</p>
</li>
<li>
<p>[Docs] Extract svg diagrams into separate files</p>
<p>Inline svg images involve a lot of text and can overwhelm an LLM's context window. This extracts
the svgs out as separate assets and loads them dynamically instead.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.6.8</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.6.8" />
      <id>https://oban.pro/releases/pro/1.6.8</id>
      <published>2025-11-13T00:00:00Z</published>
      <updated>2025-11-13T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Enhancements</h3>
<ul>
<li>
<p>[Workflow] Add <code>atom_keys</code> option to configure cascade context</p>
<p>The <code>atom_keys</code> option controls whether the keys in a cascade function's <code>context</code> map are
atomized or converted to strings. This helps with consistency for nested workflows, or when
atoms may not exist between nodes.</p>
</li>
<li>
<p>[Testing] Add missing functions to make a drop-in replacement for <code>Oban.Testing</code></p>
<p>Add missing functions (<code>build_job/3</code>, <code>perform_job/1,2</code>, and <code>with_testing_mode/2</code>) to ensure
<code>Oban.Pro.Testing</code> is fully compatible with <code>Oban.Testing</code>. This allows seamless migration from
Pro without modifying existing test helpers or test code.</p>
</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>
<p>[Workflow] Flatten contexts within nested grafts</p>
<p>When building complex workflows with nested grafts (a graft within another graft), the recorded
results from upstream jobs were being incorrectly nested in the context passed to downstream
jobs. Now, nesting is applied correctly at any level of nesting.</p>
</li>
<li>
<p>[Workflow] Prevent deadlocks for queues processing workflows</p>
<p>Adds locking to prevent deadlocks when jobs from the same workflow or chain complete
simultaneously on different nodes.</p>
</li>
<li>
<p>[Workflow] Correct condition guard in status query</p>
<p>The query used to expand the status for sub-workflows didn't make use of the existing workflow
indexes, which could lead to poor performance in busy systems.</p>
</li>
<li>
<p>[DynamicCron] Compare inserted times in UTC for guaranteed cron</p>
<p>Prevent double triggering jobs for guaranteed cron during DST changes. Time is always compared
in UTC, rather than shifting to the current timezone.</p>
</li>
<li>
<p>[Refresher] Prevent active producers from erroneous cleanup</p>
<p>When a queue's producer is stuck in a transaction retry loop, it isn't able to handle new
messages, including periodic refresh requests. This would allow the producer record to become
outdated in the database, at which point it is subject to erroneous cleanup.</p>
<p>Refreshing now proactively discovers and refreshes all active producers through the Registry,
rather than relying on individual producers messages.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.6.7</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.6.7" />
      <id>https://oban.pro/releases/pro/1.6.7</id>
      <published>2025-10-23T00:00:00Z</published>
      <updated>2025-10-23T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Bug Fixes</h3>
<ul>
<li>
<p>[Workflow] Explicitly group workflow conditions for flushing query</p>
<p>The boolean grouping of statements in a workflow query was incorrectly interpreted and resulted
in a highly inefficient workflow query.</p>
</li>
<li>
<p>[Smart] Fetch and execute jobs with missing partition keys</p>
<p>Previously, partitioned queues wouldn't run jobs with null partition keys. This was an edge
case, usually caused by adding partition config to a queue with older, scheduled jobs in it.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.6.6</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.6.6" />
      <id>https://oban.pro/releases/pro/1.6.6</id>
      <published>2025-10-20T00:00:00Z</published>
      <updated>2025-10-20T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Enhancements</h3>
<ul>
<li>
<p>[Relay] Add with_retries option to <code>await/2</code></p>
<p>It's now possible to keep waiting for a job to complete across retries. The awaiting process
will keep waiting until the job has exhausted all attempts before or the timeout is reached.
This makes async/await more reliable for jobs that have flickering failures.</p>
</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>
<p>[Refresher] Fix shutdown order to prevent rescue mistakes</p>
<p>Queue producer refreshing, which is essential to identifying live queues, could terminate too
early during node shutdown. When the <code>shutdown_grace_period</code> exceeded the refresh interval,
producers would lose the ability to update their timestamps while still running, leading to
false stale producer detection and unnecessary job rescues.</p>
<p>Refreshing now survives instance shutdowns and continues updating producer timestamps throughout
the grace period.</p>
</li>
<li>
<p>[Smart] Prevent deadlock during concurrent unique insert and fetch operations</p>
<p>Inserting unique jobs that conflicted with currently executing jobs could cause a deadlock. The
ack query now takes an explicit lock to ensure updates complete, while also preventing the
deadlock.</p>
</li>
<li>
<p>[Workflow] Update workflow flushing syntax for CRDB</p>
<p>The flushing query used a syntax that is valid in Postgres, but incompatible with how CRDB
handles JSONB function output.</p>
</li>
<li>
<p>[Workflow] Include cascade options in <code>apply_graft/2</code></p>
<p>The spec only included <code>t:new_opts/0</code>, but the function also creates cascade jobs and should
include <code>t:add_cascade_opts/0</code>.</p>
</li>
<li>
<p>[DynamicQueues] Correct typespec for <code>update/3</code></p>
<p>The typespect listed <code>t:queue_opts/0</code>, which is a union of tuples, but should be a keyword list.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.6.5</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.6.5" />
      <id>https://oban.pro/releases/pro/1.6.5</id>
      <published>2025-09-22T00:00:00Z</published>
      <updated>2025-09-22T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Bug Fixes</h3>
<ul>
<li>
<p>[DynamicPruner] Retain jobs in active workflows when pruning</p>
<p>The <code>DynamicPruner</code> no longer deletes jobs that are part of an active workflow or related
sub-workflow. If any job in a workflow is in an incomplete state (e.g. executing), no jobs in
the workflow will be pruned.</p>
</li>
<li>
<p>[DynamicQueues] Enabling clearing limits on queue config update</p>
<p>Clearing a DynamicQueue's <code>rate_limit</code> or <code>global_limit</code> by setting <code>nil</code>, or swapping between
options, now works with a config update.</p>
</li>
<li>
<p>[Workflow] Handle empty enumerables passed to <code>add_many/4</code></p>
<p>Empty sub-workflows no longer prevent downstream jobs in a workflow from executing. Now, step
elimination ensures the workflow executes in the expected order when a sub-workflow created from
an empty enumerable.</p>
</li>
<li>
<p>[Worker] Move option validation to <code>after_compile/2</code> hook</p>
<p>Validating options with an exception in an <code>after_verify/1</code> hook breaks progressive compilation
and causes ongoing warnings during subsequent compilations. Options are now validated with
<code>after_compile/2</code> instead.</p>
</li>
</ul>]]></content>
    </entry>
  
    <entry>
      <title>Oban Pro v1.6.4</title>
      <link rel="alternate" href="https://oban.pro/releases/pro/1.6.4" />
      <id>https://oban.pro/releases/pro/1.6.4</id>
      <published>2025-08-21T00:00:00Z</published>
      <updated>2025-08-21T00:00:00Z</updated>
      <content type="html"><![CDATA[<h3>Bug Fixes</h3>
<ul>
<li>
<p>[Smart] Prevent leaking tracked partitions between job fetches.</p>
<p>A race condition between acking and fetching could cause tracked global partitions to be
retained when jobs weren't still processing. Now that logic is simplified and corrected to
ensure only actively running job partitions are tracked.</p>
</li>
</ul>]]></content>
    </entry>
  
</feed>
