Repository navigation
Update dependency Quartz.AspNetCore to v4 - #1869
Open
renovate[bot] wants to merge 1 commit into
Open
renovate[bot] wants to merge 1 commit into
renovate[bot] wants to merge 1 commit into
Conversation
renovate
Bot
force-pushed
the
renovate/major-quartznet-monorepo
branch
2 times, most recently
from
September 13, 2026 13:22
a40a671 to
db92354
Compare
renovate
Bot
force-pushed
the
renovate/major-quartznet-monorepo
branch
2 times, most recently
from
September 25, 2026 16:49
a094daa to
f5ecc47
Compare
renovate
Bot
force-pushed
the
renovate/major-quartznet-monorepo
branch
4 times, most recently
from
September 29, 2026 15:53
e5676e1 to
768cbf4
Compare
renovate
Bot
force-pushed
the
renovate/major-quartznet-monorepo
branch
from
October 7, 2026 22:35
768cbf4 to
6458b83
Compare
renovate
Bot
force-pushed
the
renovate/major-quartznet-monorepo
branch
from
October 13, 2026 21:49
6458b83 to
d3e99b2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
This PR body was truncated due to platform limits.
This PR contains the following updates:
3.15.1→4.5.0Release Notes
quartznet/quartznet (Quartz.AspNetCore)
v4.4.0Quartz.NET 4.4 is history you can run a business on. Each run says what it achieved: succeeded, failed,
cancelled or skipped, with a summary and metrics. The history keeps that per job, sweeps it by result and charts
it over time, and a health check degrades when a required job stops succeeding. Recovery respects rolling
deploys, and triggers already due fire in the transaction that acquires them: at 20 triggers a second on
PostgreSQL, 95–99 % of firings land within ±50 ms, up from 54 % on 4.3.0. Quartz.Weasel now manages the job store's schema on six databases, adding MySQL, Oracle and Firebird.
Highlights
context.Result, a status per job, retentionby result, and a chart of runs over time. Execution history and outcomes
RequireSuccessWithin(jobKey, TimeSpan.FromHours(26))degrades when a nightly jobstops succeeding. Execution history and outcomes
RunAtStartup, scheduling a triggeralready paused, and pausing a set of keys with a reason in one call. Scheduling
420–429 firings a second. Reliability and clustering
or the restarted node. Reliability and clustering
Quartz.Weasel.MySQL,Quartz.Weasel.OracleandQuartz.Weasel.FirebirdjoinPostgreSQL, SQL Server and SQLite. Databases and Weasel
HTTP API and dashboard
Execution history and outcomes
context.Result = JobRunReport.Skipped("…").With("released", 3).JobRunResult {Succeeded, Failed, Cancelled, Skipped}.1059/1060).
MisfireReason.Vetoed). Manual runs (TriggerJob) are marked.JobRunStatus: last run, last success and failure, consecutive failures, counts.RetentionByResult,MisfireRetention) andMaxEntriesPerJob.sweeps per cluster.
with history on went from 113–248 to 77–189 firings a second. Tracked in #3979.
database/migrations/database/migrations/…. (#3972)and the HTTP API's
JobExecutedevent carry it, not the wrapper's "Job threw an unhandled exception". Logtemplates are unchanged.
o.RequireSuccessWithin(new JobKey("nightly-report"), TimeSpan.FromHours(26)), or aRequiredJobslistbound from configuration.
IExecutionHistoryStore.QueryExecutionStatistics(a default member), with an HTTP route and a clientmember.
native
PERCENTILE_CONTon PostgreSQL and Oracle, a ranked read elsewhere.cards count the window rather than the page.
ExecutionHistoryOptions.RecordInputrecords a run'sJobInput, up toMaxInputBytes(default16 KB). Over the cap the input is not kept, and the row is flagged.
TriggerJob(key, data)and says whether it did.JOB_INPUTandJOB_INPUT_TOO_LARGE, in the optional 4.4 migration.quartz.job.execution.durationgains aquartz.job.resulttag:succeeded,failed,cancelledorskipped, classified by the same rule as the history row.error.typeis unchanged: it is set on a failedrun that threw. (#4004, closes #3967)
Quartz.Job.Executespan getsquartz.job.resulttoo. A failed store span now carrieserror.type.(#4020, closes #4016)
QuartzSchedulerOptions.RecordExceptionSpanEvents, since OpenTelemetry deprecated span events in 2026.Unset, Quartz follows
OTEL_SEMCONV_EXCEPTION_SIGNAL_OPT_IN, sologsturns the events off. The default isunchanged. (#4020)
Scheduling
[RetryPolicy(3, "00:00:30")](Fixed),[RetryPolicy(5, "00:00:10", 2.0)](Exponential), Explicit delays,or
[RetryPolicy(0)](never), plusq.UseDefaultRetryPolicy(…).RetryPolicy.Nonerefuses at any level.event 1062).
SendMailJobandNativeJobcarry[RetryPolicy(0)].q.RunAtStartup(jobKey).twice gives one run.
@rebootis still rejected, and its message namesRunAtStartup.ScheduleJobOptions/AddTriggerOptionsgainPaused,PauseReasonandPauseRequestedBy. The shippedstores write the trigger paused in the same write, so it never fires first.
IJobStore.SupportsStoringPauseddefault member. Without it, thescheduler pauses the trigger straight after storing it; the gap between the two is documented.
PauseTriggersWith/PauseJobsWithonIScheduler,IJobStoreandIQuartzApiClient./keys/pauseroutes take the reason. A reasonless 4.3 client still gets the exact reasonlesspause.
IJobListener.JobProgressChanged(context, progress), a default member.per firing, and only on a change.
JobWasExecuted. It is called off the job's thread and is in-processonly.
Reliability and clustering
The database store makes one transaction per round instead of two, and sends each round's claims and fire
writes as one batch each.
The in-memory store takes one lock per round instead of two.
PostgreSQL, against the released 4.3.0 (three alternating sittings; the full table is under Measured):
A custom
IJobStorekeeps the old two-step path through the newAcquireNextTriggersAndFireDuedefaultmember.
A custom
StdAdoDelegatekeeps every override it has. The newIDriverDelegate.SupportsFireOnAcquireanswers true only for the shipped delegates, and a subclass gets the one-transaction round through the
per-trigger members it may override.
closes #3988).
worst firing went from 12.9 s late to 0.37 s.
unchanged.
AdoJobStoreOptions.RecoverFiringsCancelledByShutdown(keyquartz.jobStore.recoverFiringsCancelledByShutdown).RequestsRecoveryjob cancelled by a graceful shutdown gets a recovery trigger instead of completing asCancelled. A peer, or the restarted node, runs it on its next acquisition.
Interruptor[JobTimeout]stays a cancellation. The in-memory store is not affected.in a row it is stored ERROR. Before, it was released and acquired again forever, which hot-looped and starved
its
[DisallowConcurrentExecution]job-mates.AdoJobStoreOptions.MaxConsecutiveFireFailures(default 5, 0 = never), flat keyquartz.jobStore.maxConsecutiveFireFailures.ResetTriggerFromErrorState.InMemoryJobStoreOptions.MaxConsecutiveFireFailuresfailures in a row (log events 2008/2009).
unexpectedly.
closes #3985 and #4006).
BLOCKED.being parked within milliseconds. They are counted only after the store's own commit, and a successful
misfire resets the count.
event 3053). New in-memory event 2010.
AddCalendar(…, updateTriggers: true)works out every trigger's new fire time on a clone first, and fails asthe database store does, with
JobPersistenceException.Databases and Weasel
UseWeaselForMySql()andUseWeaselForOracle().GET_LOCKuser lock,which needs no privilege.
CURRENT_SCHEMA. There is no lock, becauseDBMS_LOCKneeds a grant. Aconflicting apply re-reads and retries, as
ProvisionSchema()does.unchanged; 3.20, 4.2 and 4.3 databases upgrade add-only; an application's own objects survive.
UseWeaselForFirebird(), for Firebird 3, 4 and 5.9.39.0.
MaxIdentifierLengthallows up to 63 on Firebird 4 andlater.
[9.39.0, 10.0.0).IDX_QRTZ_T_NFT_STon every apply. JasperFx/weasel#660fixed it upstream, so Quartz.Weasel.Oracle needs no workaround.
code. Every failure surfaces as a
SchedulerExceptionwith the original inside.their rows. 4.3 refused. Thanks to the 11 upstream fixes in Weasel 9.36.0. (#3978, closes #3956)
columns, types, keys, foreign keys and teardown.
ProvisionSchema()leaves it out too, as the fresh-install script always has.Thanks to the JasperFx maintainers for reviewing and merging Weasel.Firebird (JasperFx/weasel#666).
Weasel schema management
Database schema
4.4/add_execution_outcome_<dialect>.sqlRESULT,SUMMARY,METRICS,MANUAL,FIRE_INSTANCE_ID,JOB_INPUT,JOB_INPUT_TOO_LARGEonQRTZ_EXECUTION_HISTORY;IDX_QRTZ_EH_JOB_TIME; tableQRTZ_JOB_STATUS(#3972, closes #3958; #4021)The new columns are nullable, and the script is safe to run beside 4.3 nodes. The Weasel models carry it.
HTTP API and dashboard
/history/job-status, with a version check so a client never sends a 4.4 filter to anolder host.
reasonsnames it, so a 4.3 reader never meets a name it can'tparse.
Last run / Last success / failing ×N on Jobs, and a status panel on Job Detail.
null, or leaves only that item out of a listing. Eachenum's rule is documented. Log events 9200–9203.
used to be an empty 500. A second native canary runs the wire path on every pull request.
AspNetCore example's dashboard works from source on .NET 10 (
MapStaticAssets), and ExamplesSmoke assertsblazor.web.js.Execution history and outcomes.
Behaviour changes
Cancelled(Succeededfalse)context.OutcomereadsVetoedinsideJobExecutionVetoedSucceededMisfireReason.Vetoed)TriggerJobfirings carryQRTZ_MANUAL_TRIGGERin the trigger'sJobDataMapJobExecutedevent carry the job's own exception messageMaxConsecutiveFireFailures, 0 = off)WithRetryPolicy(null)means "inherit"; manual and recovery runs inherit tooSendMailJobandNativeJobcarry[RetryPolicy(0)]: the scheduler default never retries them; a trigger's own policy still appliesSchedulerExceptionwith the original insideInvalidOperationExceptionreasonsnames itShutdown()was called mid-roundTriggersFiredspan; the acquisition span carries the countTriggersFiredspanOTEL_SEMCONV_EXCEPTION_SIGNAL_OPT_IN=logsturns exception span events off (RecordExceptionSpanEventsoverrides)Mixed 4.3 and 4.4 nodes
QRTZ_JOB_STATUS, so theper-job status counts only runs 4.4 nodes record until every node is 4.4.
Vetoedin the HTTP misfire listing. A 4.2 misfire row with no reason reads as Missed.than 4.4 is refused.
Upgrading
An application on 4.3 compiles on 4.4 unchanged. On a persistent store with the execution history on, run
4.4/add_execution_outcome_<dialect>.sqlbefore the first 4.4 node starts. Every change, and the full list ofmixed-version notes, is in the migration guide's
Upgrading from 4.3 to 4.4.
Public API
Additions only: +278 lines, none removed, and every member added to an existing interface has a default implementation.
The whole delta, member by member:
git diff v4.3.0 v4.4.0 -- src/Quartz.Tests.Unit/Verify src/Quartz.Tests.AspNetCore/Verify.Measured
PostgreSQL 15.1 in Docker (fsync and synchronous commit on), pool 10, three alternating sittings of 4.3.0 and 4.4.0 on one quiet machine.
Dependencies
[9.39.0, 10.0.0), which needs JasperFx 2.76.0 or later. (#4007)Quartz.Weasel.MySQL,Quartz.Weasel.OracleandQuartz.Weasel.Firebird. The packagecount goes from 18 to 21. (#3987, #4005)
(#3969, closes #3966)
Also
Contributors run
dotnet test --project ….Microsoft.NET.Test.Sdk(RIDER-131530), and a test guardsglobal.json's runner(RIDER-140778).
--report-github.v4.3.0Quartz.NET 4.3 is an additive minor. Every public change is a new type, a new member on a type we own, or a default
interface member, and its schema change is one generated folder,
database/migrations/4.3/, that only addsnullable columns. A released 4.2 node and a 4.3 node share one PostgreSQL database in CI to prove a mixed cluster
runs safely. A 4.2 application upgrades by changing the version and, on a persistent store, running the 4.3 scripts
before the first 4.3 node starts. The headlines: a scheduler on a database fires one-off jobs 2.8× faster than 4.2
at its shipped defaults; a job can be a lambda; and a schedule can be steered while it runs, with an overlap policy,
a pause that says why, backfill, progress and captured logs, and a dashboard that edits. Four new packages put the job store's tables under Weasel, for applications on Marten or Wolverine.
Highlights
Faster where it runs
A job that takes nothing from the container — no constructor parameters, not registered, no
ConfigureScope—is built without a DI scope: 1.84 → 1.68 KB a firing on
RAMJobStore, a little faster too. Every other job keepsits scope exactly as before. (#3866)
RAMJobStorecomputes a cron trigger's next fire time once per firing, not twice, inside its lock. (#3866)A scheduler on a database batches what is already due, out of the box.
MaxBatchSizenow defaults toautomatic: the thread pool's size on a persistent store, clustered or not, and 1 in memory.
Nothing fires early (the fire-ahead window is still zero). On PostgreSQL a backlog of one-off jobs goes from
about 80–91 to 125 firings a second, and from 6.0 to 2.8 commits a firing.
MaxBatchSize = 1restores 4.2.On a cluster, the flip was gated on a 2- and 4-node drain against a durable PostgreSQL: 1.7–2.1× faster on
two nodes and 1.6–2.0× on four, at a lock-wait p99 of about 60 ms. One of four sittings read 0.78× on four nodes
while every lock wait on that shared VM was slower. (#3862, #3900)
Scheduling a job costs less.
ScheduleJobwith a simple trigger allocates 2.76 KB, down from 3.83 KB,and a cron one 3.3 KB, down from 4.6 KB. Scheduling a trigger later than the one the scheduler is already
waiting for no longer wakes it, and listener lists are built when they change, not on every call. (#3865)
A firing that may run beside itself completes without the store's lock. Completions of
concurrent-allowed jobs no longer queue behind each other on
TRIGGER_ACCESS. With the batching default above,one-off jobs on PostgreSQL go from 4.2's 91 to 237 firings a second on the same database: 2.6×. Repeating
triggers fire 1.5–1.9× faster. A 2- and 4-node cluster drains 1.5–1.8× faster at a fifth of the lock wait. A
[DisallowConcurrentExecution]job, a retry, a trigger with continuations, a buffered overlap policy and anon-durable job's last trigger keep the lock. The misfire pass also settles a continuation whose parent no
longer exists. (#3863)
Measured on the release candidate against 4.2.0, alternating sittings on one box and one durable PostgreSQL:
In memory every firing still lands within ±50 ms.
A job can be a lambda
q.ScheduleJob("cleanup", static async (IRepo repo, ILogger<Cleanup> log, CancellationToken ct) => …, t => t.WithCronSchedule("0 0 * * * ?"))— the parameters are what the code needs: the firing (
IJobExecutionContext), its token, its scope(
IServiceProvider), or any service from that scope.AddJob(name, handler)adds a durable one. It is stored asQuartz.Impl.DelegateJobkeyed by the job key, so it persists and clusters like any job; every node registers thehandler. (#3867)
The compiler binds a lambda handler: Quartz's source generator intercepts the call, so a firing resolves its
parameters with generated code, in 14 ns and allocating nothing, instead of by reflection (51.6 ns, 72 B). Any
other handler is bound by reflection once at registration, native AOT included. Delegate jobs (#3882)
See what a running job is doing
context.ReportProgress(40, "copied 400 of 1,000")shows as a progress bar on the dashboard's Currently Executingpage, cluster-wide (written at most once a second, only on change).
q.UseExecutionLogCapture()keeps the log linesa firing writes — bounded by lines and bytes — with its history entry, and a new execution page shows one run with
its outcome, timings, exception and log. Hangfire.Console's job, in the box. Progress and execution logs (#3874)
Say what happens when the last firing is still running
t.WithOverlapPolicy(OverlapPolicy.Skip)drops an occurrence that comes due while the trigger's previous firingruns (recorded in misfire history with reason
Overlap);BufferOneholds one and fires it when the previousends;
CancelPreviousinterrupts the running firing and starts the new one (on another node it holds instead);AllowAlloverlaps freely.Defaultis 4.2's behaviour and costs nothing.[DisallowConcurrentExecution]stillwins. Temporal's and Kubernetes CronJob's knob, per trigger, in every store. New
ITriggerListener.TriggerSkipped. Roll every node to 4.3 before relying on it: a 4.2 node ignores thepolicy. Overlap policy (#3875)
A pause says why
scheduler.PauseTriggerWith(key, new PauseDetails { Reason = "vendor API down", RequestedBy = "ops" })(andPauseJobWith,PauseTriggerGroupsWith,PauseJobGroupsWith,PauseAllWith) records why, who and when;GetTriggerPause(key)reads it back, and the dashboard shows it beside the pause. The HTTP pause routes take anoptional
{ reason, requestedBy }body, with the requester defaulting to the signed-in user.q.PauseTriggerWhenRetriesExhausted()pauses a trigger whose retry policy gave up, with the failure as thereason. A pause without a reason is exactly 4.2's pause. Pausing with a reason (#3879)
The dashboard is a control panel
Edit a trigger in place: description, priority, calendar, misfire instruction, execution group, retry policy,
preferred node, overlap policy and its job data, with each save written to the action log (now with a User column).
Filter the Triggers list by group, name, job, calendar, state and "next fire before", and Jobs by group and name.
Every filter lives in the URL. Select rows to pause, resume or unschedule them together. It works the same over an
HTTP target and a store-attached window. (#3878)
Steer who runs, and how often the same thing is enqueued
limits.ForGroupsWithPrefix("tenant:", 2, ExecutionLimitScope.Cluster)gives every groupunder the prefix its own allowance, cluster-wide;
WithExecutionGroup("tenant:{TenantId}")and[ExecutionGroup("tenant:{TenantId}")]fill the group from the trigger's job data; a one-off saysExecutionGroup = $"tenant:{input.TenantId}". Concurrency keys, which Oban, Sidekiq, JobRunr and BullMQsell in their paid tiers. (#3876)
OneOffJobOptions.OnConflictisThrow(as before),Replace,Keep(enqueue unless it is already pending) or
KeepEarlier(whichever fires first stays). NewIScheduler.ScheduleTrigger(trigger, onConflict)/IJobStore.StoreTrigger(trigger, onConflict)reportwhether the trigger was created, replaced or kept; the HTTP API takes
onConflict. (#3877)Backfill a range you missed
scheduler.Backfill(triggerKey, from, to)schedules one run of the trigger's job for every slot its schedule had in[from, to): calendar applied, the job data, priority, retry policy and execution group carried, the slot readablein the job through
context.GetBackfillSlot(). Re-running the range skips slots still pending,MaxSlots(1,000)and
Spacingbound it, and a future range is refused. Misfire handling is for when the scheduler was down; backfillis for a range you choose. Over the HTTP API it is one audited request (
POST …/triggers/{group}/{name}/backfill),and the dashboard's trigger page has a Backfill dialog. Backfill (#3880)
Quartz's tables under Weasel
Quartz.Weasel.PostgreSQL,Quartz.Weasel.SqlServerand
Quartz.Weasel.SQLiteput the ADO.NET job store's schema under Weasel.db-apply,db-assert,db-patch,resources setupand AutoCreate at startup then create and migrate Quartz's tables beside theapplication's own, instead of
ProvisionSchema()and hand-run scripts. One call on the store:store.UseWeaselForPostgres(),UseWeaselForSqlServer()orUseWeaselForSqlite(). (#3941, #3948)database/tables/,ProvisionSchema()or the migrations reads as unchanged, and a 3.x or 4.2 schema is brought up to 4.3 in place. Tablesare add-only: columns, indexes and keys you added are kept. Every apply takes a lock, so nodes starting together take
turns. On PostgreSQL the tables can instead join a Marten store as a feature schema. (#3941, #3948)
provider. Weasel schema management
Quartz.NET.
Test with a clock you control
Advancing a
FakeTimeProvidernow wakes the scheduler:clock.Advance(TimeSpan.FromHours(1))fires what was duein that hour, with no real waiting.
JobExecutionContextBuilder.For(job).WithTrigger(trigger).FiredAt(when).Build()builds a context for a job's unit test in one line. Testing (#3869)
Declared where it is used
t.WithCronSchedule(cron => cron.AtTime(new TimeOnly(3, 0)).OnWeekdays())configures the expression inline,and
CronExpressionBuilder.Every(TimeSpan.FromMinutes(10))is a clock-anchored interval(
0 0/10 * ? * *). (#3868)q.AddJobLogScope()puts the job, trigger and fire instance on every log line a firing writes. Opt-in:272 bytes a firing when on, nothing when off. (#3870)
[CronTrigger("0 0 2 * * ?", ConfigurationKey = "Jobs:Cleanup:Cron")]reads the schedule from configuration,falling back to the literal the compiler checked; a bad configured value fails at startup. New
QZ1005;QZ1004is now Info, becauseAddDeclaredJobsFrom<Assembly>()is always emitted besideAddDeclaredJobs(). (#3871)[SimpleTrigger("00:10:00")]declares an interval job the way[CronTrigger]declares a cron one:RepeatCount(forever by default), the misfire instruction, priority, execution group and
ConfigurationKey. NewQZ0005refuses an interval that is not a positive
TimeSpanat compile time. (#3924)Database schema
database/migrations/4.3/is the second schema move since 4.0, and like 4.2's it only adds nullable columns, so a4.2 node keeps running beside a 4.3 node while you roll:
add_fire_progress_<dialect>.sqlQRTZ_FIRED_TRIGGERS.PROGRESS,PROGRESS_MESSAGE(#3874)add_execution_log_<dialect>.sqlQRTZ_EXECUTION_HISTORY.EXECUTION_LOG(#3874)add_overlap_policy_<dialect>.sqlQRTZ_TRIGGERS.OVERLAP_POLICY(#3875)add_misfire_reason_<dialect>.sqlQRTZ_MISFIRE_HISTORY.REASON(#3875)add_pause_reason_<dialect>.sqlPAUSE_REASON,PAUSED_BY,PAUSED_ATonQRTZ_TRIGGERS,QRTZ_PAUSED_TRIGGER_GRPS,QRTZ_PAUSED_JOB_GRPS(#3879)Every table script carries the new columns, the memory-optimized and pre-2016 SQL Server scripts included. Each
script is guarded, so running it twice is safe; SQLite's
ADD COLUMNis the exception, as before.A released 4.2.2 node and a 4.3 node share one PostgreSQL database upgraded from the 4.2.0 schema with these scripts,
in every PostgreSQL CI run: 2,000 one-off jobs run exactly once between them, a
[DisallowConcurrentExecution]jobnever overlaps, continuations settle whichever node completes the parent, and the 4.3 columns keep their values
when the 4.2 node fires, updates or resumes the rows around them. A 4.2 node ignores what the new columns mean
(an overlap policy, a pause reason, progress), so roll every node before relying on those. (#3925)
Behaviour changes
QuartzSchedulerOptions.MaxBatchSizedefaults to0, meaning automatic (see above); an explicit value wins asbefore, and
quartz.scheduler.batchTriggerAcquisitionMaxCountstill maps to it. (#3862)Public API
Additions only: 271 lines across
Quartz(+238),Quartz.Dashboard(+23) andQuartz.HttpClient(+10), noneremoved. Every one of the 45 members added to an existing interface (
IScheduler,IJobStore,ITrigger,IJobExecutionContext,ITriggerConfigurator<T>,IQuartzApiClientand others) has a default body, so animplementation or forwarder written against 4.2 compiles and runs unchanged.
Configuration
📅 Schedule: (in timezone Asia/Shanghai)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.