Option Precedence
Queue name, max attempts, and timeout are configured at two levels. When a job is enqueued, the SDK resolves each setting by checking the levels top-to-bottom — the first non-null value wins, otherwise hardcoded defaults apply.
The two levels
| Priority | Source | Scope | When to use |
|---|---|---|---|
| 1 (highest) | JobOptions | Per-call | One-off overrides for specific enqueue calls |
| 2 (lowest) | [JobConfig] | Per-class | Defaults that apply to every run of a job type |
ZeridionFlareOptions has DefaultQueue, DefaultMaxAttempts, and DefaultTimeout properties, but the SDK's enqueue path does not currently read them. The effective cascade is JobOptions > [JobConfig] > hardcoded defaults. The global options properties are reserved for a future release.
Resolution table
| Setting | JobOptions | [JobConfig] | Hardcoded default |
|---|---|---|---|
| Queue | .Queue | .Queue | "default" |
| Max Attempts | .MaxAttempts | .MaxAttempts | 3 |
| Timeout | .Timeout | .TimeoutSeconds | 30 minutes |
Properties left as null (for JobOptions) or at their attribute defaults (for [JobConfig]) fall through to the hardcoded defaults.
Example scenario
Consider a payment processing application with these configurations:
Class-level attribute (on the job):
[JobConfig(Queue = "payments", MaxAttempts = 10, TimeoutSeconds = 300)]
public class ProcessPayment : IJob<PaymentPayload>
{
public async Task ExecuteAsync(PaymentPayload payload, JobContext ctx)
{
// ...
}
}
Per-call override (at the enqueue site):
// Scenario A: No JobOptions — uses [JobConfig] values
await jobs.EnqueueAsync<ProcessPayment>(payload);
// Queue = "payments", MaxAttempts = 10, Timeout = 5 min
// Scenario B: Override max attempts only
await jobs.EnqueueAsync<ProcessPayment>(payload, new JobOptions
{
MaxAttempts = 1
});
// Queue = "payments" (from [JobConfig]), MaxAttempts = 1 (from JobOptions), Timeout = 5 min (from [JobConfig])
// Scenario C: Override everything
await jobs.EnqueueAsync<ProcessPayment>(payload, new JobOptions
{
Queue = "critical",
MaxAttempts = 20,
Timeout = TimeSpan.FromMinutes(60)
});
// Queue = "critical", MaxAttempts = 20, Timeout = 60 min (all from JobOptions)
What each scenario resolves to
| Setting | Scenario A | Scenario B | Scenario C |
|---|---|---|---|
| Queue | "payments" (attr) | "payments" (attr) | "critical" (opts) |
| MaxAttempts | 10 (attr) | 1 (opts) | 20 (opts) |
| Timeout | 5 min (attr) | 5 min (attr) | 60 min (opts) |
Jobs without [JobConfig]
If a job class has no [JobConfig] attribute, the hardcoded defaults apply directly ("default", 3, 1800). The JobTypeRegistry uses the same fallback values as the attribute's property defaults, so the resolution falls through to the hardcoded defaults.
// No [JobConfig] attribute
public class SendWelcomeEmail : IJob<NewUserEvent>
{
public async Task ExecuteAsync(NewUserEvent payload, JobContext ctx) { /* ... */ }
}
await jobs.EnqueueAsync<SendWelcomeEmail>(payload);
// Queue = "default", MaxAttempts = 3, Timeout = 30 min (all hardcoded defaults)
await jobs.EnqueueAsync<SendWelcomeEmail>(payload, new JobOptions { Queue = "email" });
// Queue = "email" (opts), MaxAttempts = 3 (default), Timeout = 30 min (default)
Tags and IdempotencyKey
Tags and IdempotencyKey exist only on JobOptions — they do not participate in the cascade. They are either set per-call or not set at all.
See also
- JobOptions — per-call overrides (highest priority)
- [JobConfig] Attribute — class-level defaults (lowest priority)
- ZeridionFlareOptions — global options (not currently used in the cascade)