Repository navigation
Migrate to Quartz 4.2.1 - #467
Merged
Merged
Conversation
Quartz 4 made CronExpression immutable and dropped GetTimeAfter, GetExpressionSummary and IsValidExpression; IJob.Execute and IScheduler.ScheduleJob gained parameters, and misfire handling moved to an enum. Behaviour changes, measured against Quartz 3.22 over two years of fire times in four time zones: - A fire time that does not exist on the spring-forward night now fires at the end of the gap (03:00) instead of an hour later (03:30). - Hourly and sub-hourly schedules keep firing through the repeated hour on the fall-back night; Quartz 3 skipped that hour. - Cron expressions with both day fields '*' are accepted; a zero step and years after 2199 are rejected. The cron summary is gone from the next-fires API, the CLI, the MCP tool and the AI chat forecast: Quartz 4 has no replacement and it was Quartz's internal field dump rather than a readable description. Closes #451. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sev7eNup
added a commit
that referenced
this pull request
Oct 3, 2026
SlowReader_AfterProcessExit_DrainsBufferedOutputWithinTheTotalBudget set a 1 s idle grace for both cases. In the case that must capture all output, a single read that stalled for a second on a loaded runner ended the drain early (seen on #467). That case now uses the 5 s production default; the bounded case keeps 1 s so it still reaches the total limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 3, 2026
Sev7eNup
added a commit
that referenced
this pull request
Oct 3, 2026
#469) * Stop the redactor from letting secrets through when a pass times out Every built-in redaction pattern carried a 500 ms match timeout, and a pass that timed out was skipped, dropping the redactions it had already made. The timeout also fires on a thread that merely got no CPU: on a busy host the first password match took 1.6-2.4 s and passed through unredacted, which is what made AuditStagerTests.Build_AppliesRedactor_ ToDetails fail in parallel runs. Independently of load, a closed private key followed by many unclosed BEGIN markers made the private-key pattern quadratic and leaked the key. The fourteen built-in regexes are linear (no nested quantifiers) and now run without a timeout on the same engine. The two PEM shapes, whose lazy body is retried from every BEGIN marker, are paired by hand with the same semantics in linear time. Operator patterns keep the timeout. Checked against the previous implementation: identical output on 300,000 generated inputs (208,287 redacted) and on the named-value path; 1 MB adversarial inputs finish in milliseconds instead of timing out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Give the cluster recovery tests a realistic batch budget ClusterFailoverRecoveryHostTests.DeferredSingleExecution_* configured a 1 s lease timeout, which bounds every recovery batch to 0.67 s, including the retry that has to commit. On a loaded, coverage-instrumented CI runner the retry missed that budget and the test timed out (seen on #466). The first attempt now fails with a command timeout, which takes the same deferral branch as an expired budget, and the retry runs under the default 2 s budget. The test also asserts that the deferral path was taken, since the host retries after any error. StartupRecoveryBatchTests.BudgetExpiry_* had the same shape with 200 ms for the batches after the deliberate stall; it now uses 2 s. The budget path itself stays covered by SingleExecutionExceedsBudget_*. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Use the default drain grace where the StartProgram drain must complete SlowReader_AfterProcessExit_DrainsBufferedOutputWithinTheTotalBudget set a 1 s idle grace for both cases. In the case that must capture all output, a single read that stalled for a second on a loaded runner ended the drain early (seen on #467). That case now uses the 5 s production default; the bounded case keeps 1 s so it still reaches the total limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Matches the order of the earlier releases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 4, 2026
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.
Summary
Ports the code to Quartz 4.2.1 so the Dependabot bump (#445) can land. Closes #451.
API changes handled:
CronExpressionis immutable: the zone goes into the constructor (MaintenanceWindowEvaluator,OperationalKnowledgeReader,ScheduleTriggerSource,ScheduleMissedSource).GetTimeAfter→GetNextValidTimeAfter;IsValidExpression→TryParse.IJob.Executetakes aCancellationTokenand returnsValueTask;ScheduleJobtakesScheduleJobOptions(defaultReplace = false, as before);WithMisfireHandlingInstructionDoNothing()→WithMisfireInstruction(CronTriggerMisfireInstruction.DoNothing).GetExpressionSummaryis gone without replacement. Thesummaryfield is removed fromGET /api/triggers/schedule/next-fires, the CLI and MCP mirrors,np cron next(prints the expression instead) and the AI chat forecast. It was Quartz's internal field dump, not a readable description.Behaviour changes
Measured by running Quartz 3.22 and 4.2.1 side by side over two years of fire times in UTC, Berlin, Los Angeles and Sydney. Only these differ:
0 0 2 * * *(both day fields*) is now accepted and means daily at 02:00. A zero step (0/0) and years after 2199 are now rejected at publish.Unchanged and pinned by a test: a cron maintenance window evaluated during the repeated hour opens a second time for a daily expression. Both Quartz versions do this.
Test plan
dotnet test NodePilot.slnx) on Quartz 4; the only failures were a CLI assertion fixed here andAuditStagerTests, which passes in isolation and is unrelatedMaintenanceWindowEvaluatorTests: 3 new DST cases (spring gap, fall-back sub-hourly, fall-back daily)ScheduleCronValidationTestsupdated for the new acceptance rulesApiDtoParityTests,EndpointClientCoverageTests🤖 Generated with Claude Code