Skip to content

1.2.70

Choose a tag to compare

@github-actions github-actions released this 20 Oct 03:41
· 4 commits to main since this release
Immutable release. Only release title and notes can be modified.
  • cronstable requires aiohttp 3.14.4, sentry-sdk 2.8.0, tzdata 2024.1,
    and psutil 5.9.0 or newer, so it installs next to packages that hold
    those dependencies at older releases. A comment in pyproject.toml gives
    the reason for each minimum, and a new release of a dependency doesn't
    raise it.
  • The extras require kubernetes 27.2.0, uvloop 0.19.0, orjson 3.11.6,
    isal 1.7.0, and zeroconf 0.149.16 or newer. Building from source
    requires setuptools 83.0.0 and setuptools_scm 8.0.0 or newer.
  • The Nix flake builds with the sentry-sdk and tzdata that nixpkgs ships.
  • On the gossip backend, a PreferLeader job whose owner stops moves to a
    surviving node as soon as that node's own poll of the owner fails, even
    while its peers' last reports still list the owner. For a few poll rounds
    after a node loses its link to a peer that other nodes still reach, both
    ends of the link can run that peer's PreferLeader jobs.
  • On a page opened at a loopback address, the web dashboard's Pair a device
    panel draws no code, since a phone can't dial that address. It names any
    listener with the page's scheme that is bound to a specific address a phone
    can dial, and points to cronstable pair --public-url.
  • The web dashboard's open History tab adds each new run in place, and a
    workflow's recovery card follows its runs until they finish. The History
    tab of both dashboards gives paused as the reason for a slot that a pause
    skipped.
  • When the daemon has recorded nothing for a job, the Logs tab of both
    dashboards says so. The web dashboard's open Logs tab also shows a run that
    starts and finishes between two refreshes. When failures don't correlate,
    the verdict bar reads no two share an exit code and reason. A pool's
    waiting reason reads Needs 1 slot for a one-slot entry, and the job
    filter's placeholder fits beside a full toolbar.
  • An overlap that concurrencyPolicy: Allow permits logs at INFO, and an
    overlap under Forbid or Replace logs a warning.
  • web.tls.clientCa, cluster.tls, the job CLI's CRONSTABLE_STATE_CACERT,
    and the CLI clients' --cacert verify certificates in OpenSSL's strict
    X.509 mode on every Python version, as Python 3.13 and later do by default.
    With OpenSSL 3.0 or later, a CA certificate needs a critical
    basicConstraints, a key usage, and a Subject Key Identifier, and a
    certificate it issues needs an Authority Key Identifier. A trust file that
    holds an intermediate CA or a single certificate trusts it directly.
    Listener TLS lists the requirements.
  • The web, cluster, and job state API listeners log a warning with the reason
    when a TLS handshake fails, such as a client certificate without an
    Authority Key Identifier or a browser that rejects the listener's own
    certificate. Each logs at most one such line a minute.
  • When concurrencyPolicy: Forbid starts nothing, because an instance is
    still running or another node holds the job's cluster slot,
    POST /jobs/{name}/start answers 409 with the reason, and the
    cron_run_job MCP tool returns the reason as a tool error. Under
    onStoreUnavailable: fail-closed, a slot store that can't be used, such as
    one that doesn't answer within 10 seconds, gives 503. Both dashboards show
    the reason.
  • The end event of GET /jobs/{name}/logs carries the started_at of the
    run that the stream showed.
  • The release performance check fails a timing benchmark measured in three
    or more rounds only when the median of its rounds also exceeds the
    benchmark's limits, so one round that runs unusually fast on the previous
    release can't block a release.

Performance compared with 1.2.69

Regression check: passed. No compared metric exceeded its regression thresholds.

Both versions were benchmarked in alternating rounds on the same machine. Results use the lowest runtime and median memory usage across rounds. Negative changes mean less time or memory; positive changes mean more.

Benchmark comparisons (199 metrics)

Noise (±) estimates run-to-run variation across both versions. A result fails the regression check only if the increase exceeds its percentage limit, its minimum absolute change, and 2 times the noise estimate. When a result uses the lowest runtime and each version has three or more rounds, the median of the rounds must also increase by more than the percentage limit and the minimum absolute change. n/a means too few rounds to estimate noise; the other limits still apply.

The Regression limit column accounts for the minimum absolute change, expressed as a percentage of the baseline. Not enforced marks informational metrics.

Startup times include Python startup overhead. Their percentage changes exclude that overhead when it can be measured separately and leaves a positive runtime in both versions.

Benchmark 1.2.69 1.2.70 Change Noise (±) Regression limit
mem.gc_pause_100k 136.07 ms 144.14 ms +5.9% 6.7% 25%
cronexpr.parse_simple 31.68 ms 33.33 ms +5.2% 1.9% 31.57% (declared 15%)
state.gc_sweep_2k_streams 29.66 ms 28.20 ms -4.9% 2.6% 50.58% (declared 25%)
startup.import_cronexpr 21.66 ms 21.85 ms +4.7% 1.1% 28.61% (declared 25%)
state.artifact_get_newest 11.39 ms 11.06 ms -2.9% 2.1% 43.9% (declared 25%)
statsd.emit_2k 10.61 ms 10.30 ms -2.9% 1.1% 94.29% (declared 25%)
loop.stall_metrics_2000 22.61 ms 23.24 ms +2.8% 4.4% Not enforced
state.fanout_gather_100 6.68 ms 6.51 ms -2.5% 5.6% 149.8% (declared 25%)
tui.palette_type_5k 138.52 ms 141.76 ms +2.3% 4.2% 15%
webui.timeline_500 43.10 ms 42.10 ms -2.3% 1.2% 25%
redact.secrets_5k 15.45 ms 15.78 ms +2.2% 1.6% 64.74% (declared 15%)
webui.dirty_frame_rows_500 789.3 us 772.4 us -2.1% 21.3% Not enforced
webui.boot_rows_500 319.30 ms 312.50 ms -2.1% 2.9% Not enforced
webui.rows_layout_200 47.10 ms 48.10 ms +2.1% 1.7% 25%
webapi.sse_burst_20k 33.06 ms 33.70 ms +1.9% 2.6% 15%
state.append_pruned_1k 49.67 ms 50.61 ms +1.9% 0.7% 25%
schedule.pressure_20k_48h 13.53 ms 13.29 ms -1.8% 3.6% 73.89% (declared 15%)
loop.stall_mcp_jobs_500 6.42 ms 6.53 ms +1.7% 3.5% Not enforced
pools.admit_cycle_1k 109.23 ms 107.39 ms -1.7% 0.2% 25%
startup.job_set_id_100 138.11 ms 140.05 ms +1.7% 0.8% 25%
cronexpr.test_match_200k 19.02 ms 18.70 ms -1.7% 6.1% 52.57% (declared 15%)
config.interp_2k 47.81 ms 48.58 ms +1.6% 0.9% 20.91% (declared 15%)
state.lease_renew_200 31.71 ms 32.21 ms +1.6% 1.3% 157.7% (declared 25%)
cronexpr.parse_complex 16.80 ms 17.06 ms +1.6% 2.9% 59.54% (declared 15%)
tui.wallboard_paint_5k 136.13 ms 138.24 ms +1.6% 1.1% 15%
startup.version 44.27 ms 43.68 ms -1.5% 1.1% 25%
schedule.reseed_100k 155.41 ms 153.17 ms -1.4% 2.5% 15%
webui.sort_500 42.20 ms 42.80 ms +1.4% 2.6% 25%
config.parse_crontab_1k 9.26 ms 9.39 ms +1.4% 1.7% 107.9% (declared 15%)
dag.plan_claim_10k 10.91 ms 10.76 ms -1.4% 2.8% 91.68% (declared 15%)
cluster.peer_serve_400 118.41 ms 120.00 ms +1.4% 0.7% 15%
state.depends_on_past_gate 2.00 ms 2.03 ms +1.3% 2.2% 99.78% (declared 25%)
state.derive_max_cold 18.54 ms 18.30 ms -1.3% 2.2% 53.93% (declared 15%)
redact.adversarial_10k 27.51 ms 27.85 ms +1.2% 1.0% 36.36% (declared 15%)
webui.tick_500 64.90 ms 65.70 ms +1.2% 5.0% 25%
tui.log_restyle_5k 22.51 ms 22.23 ms -1.2% 2.6% 44.43% (declared 15%)
jobapi.sem_denied_64x10 49.13 ms 48.55 ms -1.2% 0.8% 25%
webapi.resources_poll_20x240 108.71 ms 109.96 ms +1.1% 2.3% 15%
webapi.sse_tail_pump_4x20k 81.98 ms 81.04 ms -1.1% 0.8% 15%
mcp.list_jobs_500 107.33 ms 106.12 ms -1.1% 1.7% 15%
state.artifact_list_churn_x500 36.79 ms 36.39 ms -1.1% 0.7% 27.18% (declared 25%)
tui.poll_absorb_5k 69.14 ms 68.43 ms -1.0% 2.5% 15%
webui.dag_tasks_2k 40.70 ms 40.30 ms -1.0% 1.8% 25%
schedule.lint_2500_zoned 45.03 ms 44.59 ms -1.0% 0.9% 15%
schedule.reseed_local_20k 71.58 ms 70.89 ms -1.0% 2.4% 15%
tui.dag_graph_paint_2k 177.00 ms 178.72 ms +1.0% 1.2% 15%
startup.python_baseline 14.67 ms 14.53 ms -1.0% 2.8% Not enforced
state.list_documents_600 46.86 ms 47.29 ms +0.9% 1.8% 25.61% (declared 15%)
fingerprint.job_set_id_rich_2k 181.19 ms 182.88 ms +0.9% 1.2% 15%
config.reload_warm_dir_1k 176.14 ms 177.77 ms +0.9% 3.1% 25%
webapi.summary_status_500 119.48 ms 118.38 ms -0.9% 1.3% 15%
webapi.index_gzip_bytes 186.56 KB 188.24 KB +0.9% 0.0% 15%
tui.table_paint_5k 129.43 ms 130.59 ms +0.9% 1.3% 15%
cronexpr.next_dst_2k 29.63 ms 29.36 ms -0.9% 4.4% 33.75% (declared 15%)
webui.tail_term_2k 45.50 ms 45.10 ms -0.9% 2.1% 25%
startup.import_daemon 150.32 ms 151.36 ms +0.9% 0.8% 25%
webui.week_walk_500x20 46.00 ms 45.60 ms -0.9% 0.7% 25%
schedule.duplicates_20k 13.80 ms 13.92 ms +0.9% 7.3% 72.44% (declared 15%)
state.mutate_document_1k 109.59 ms 110.53 ms +0.9% 1.1% 45.63% (declared 25%)
dag.list_runs_warm 8.43 ms 8.50 ms +0.9% 0.2% 25%
json.loads_wide_int_2k 163.81 ms 165.21 ms +0.9% 1.0% 15%
webapi.index_bytes 651.90 KB 657.38 KB +0.8% 0.0% 15%
loop.idle_wake_rate 12.09 ms 11.99 ms -0.8% 1.0% 41.35% (declared 25%)
startup.mcp_bridge 82.78 ms 83.18 ms +0.8% 2.5% 25%
startup.jobcli_state_get 85.69 ms 85.00 ms -0.8% 0.9% 25%
webapi.trends_build_5k 127.25 ms 128.21 ms +0.8% 2.0% 15%
loop.stall_launch_herd_8k 61.00 ms 61.46 ms +0.8% 1.7% Not enforced
webui.palette_keys_500 40.00 ms 39.70 ms -0.7% 0.5% 25%
cluster.job_owner_2k 41.67 ms 41.96 ms +0.7% 1.5% 24% (declared 15%)
dag.gc_excess_60 65.05 ms 64.61 ms -0.7% 1.9% 25%
startup.import_config 79.07 ms 78.50 ms -0.7% 0.9% 25%
json.roundtrip_3k 431.85 ms 429.02 ms -0.7% 1.5% 15%
mcp.fleet_15x400 131.22 ms 132.07 ms +0.6% 3.4% 15%
tui.drawer_paint_5k 74.33 ms 73.85 ms -0.6% 6.1% 15%
webapi.activity_build_500 135.43 ms 134.56 ms -0.6% 1.8% 15%
webui.filter_keys_500 47.50 ms 47.80 ms +0.6% 1.5% 25%
mcp.list_jobs_page_500 106.96 ms 107.63 ms +0.6% 1.5% 15%
webui.radar_walk_500x150 49.10 ms 49.40 ms +0.6% 3.5% 25%
cronexpr.next_simple 28.56 ms 28.38 ms -0.6% 2.4% 35.02% (declared 15%)
json.roundtrip_orjson_3k 160.05 ms 161.02 ms +0.6% 0.9% 15%
webapi.dags_poll_1k 98.05 ms 98.63 ms +0.6% 1.3% 15%
config.jobconfig_3k 36.30 ms 36.08 ms -0.6% 0.9% 27.55% (declared 15%)
cluster.parse_summaries_6k 63.59 ms 63.96 ms +0.6% 1.9% 15%
schedule.suggest_slot_5k_x20 57.61 ms 57.95 ms +0.6% 1.2% 15%
state.boot_rehydrate_populated 31.41 ms 31.23 ms -0.6% 0.5% 31.84% (declared 15%)
startup.import_tui 83.24 ms 82.71 ms -0.6% 0.8% 25%
webui.render_rows_500 12.52 ms 12.59 ms +0.6% 1.9% 25%
cronexpr.next_complex 12.00 ms 11.94 ms -0.6% 2.2% 83.31% (declared 15%)
job.report_noop_100k 39.07 ms 38.86 ms -0.5% 3.5% 15%
push.seal_xwing_500 100.92 ms 101.44 ms +0.5% 0.2% 15%
cluster.poll_round_15x400 122.53 ms 123.16 ms +0.5% 1.2% 15%
tui.tail_ingest_30k 128.69 ms 129.32 ms +0.5% 1.2% 15%
schedule.due_pass_100k 13.23 ms 13.16 ms -0.5% 1.1% 75.61% (declared 25%)
job.launch_fail_retry_2k 123.02 ms 123.60 ms +0.5% 1.3% 15%
schedule.cold_build_100k 365.01 ms 366.72 ms +0.5% 0.5% 15%
webui.rows_diff_500 43.20 ms 43.40 ms +0.5% 0.8% 25%
dag.fanin_barrier_1k 141.57 ms 142.22 ms +0.5% 0.5% 25%
redact.clean_20k 15.91 ms 15.84 ms -0.4% 1.0% 62.84% (declared 15%)
webui.render_term_5k 83.75 ms 83.40 ms -0.4% 2.0% 25%
state.append_1k 76.41 ms 76.73 ms +0.4% 0.9% 65.43% (declared 15%)
cluster.derive_cold_50 120.85 ms 121.35 ms +0.4% 0.6% 15%
startup.validate_config_100 135.16 ms 135.49 ms +0.4% 0.3% 25%
webui.ledger_analyze_500x600 53.60 ms 53.40 ms -0.4% 0.8% 25%
config.parse_dir_cold_200 200.97 ms 201.71 ms +0.4% 1.0% 25%
resources.monitor_stop_100 231.83 ms 232.67 ms +0.4% 1.8% 25%
prometheus.render_500 12.96 ms 12.92 ms -0.3% 3.5% 77.15% (declared 15%)
tui.fleet_paint_15x400 113.78 ms 114.16 ms +0.3% 2.0% 15%
state.list_records_2k 19.84 ms 19.91 ms +0.3% 3.0% 50.39% (declared 15%)
push.fanout_8x100 118.39 ms 118.78 ms +0.3% 0.4% 15%
mcp.handle_200 116.20 ms 115.82 ms -0.3% 1.0% 15%
webui.swim_400x49 62.10 ms 62.30 ms +0.3% 0.9% 25%
dag.run_wide_500 83.94 ms 84.21 ms +0.3% 0.4% 25%
job.stream_capture_120k 28.41 ms 28.50 ms +0.3% 3.4% 35.19% (declared 15%)
tui.log_search_20k 10.49 ms 10.52 ms +0.3% 2.2% 95.34% (declared 15%)
tui.view_sort_5k 89.28 ms 89.01 ms -0.3% 0.9% 15%
loop.stall_jobs_500 14.98 ms 14.93 ms -0.3% 2.1% Not enforced
dag.recover_plan_from_1k 82.48 ms 82.72 ms +0.3% 1.8% 15%
pools.enqueue_burst_200 106.13 ms 106.44 ms +0.3% 1.0% 25%
mem.rss_daemon_web_100 53.20 MB 53.35 MB +0.3% 0.1% 25%
config.reload_warm_include_50 61.68 ms 61.50 ms -0.3% 2.1% 25%
state.derive_max_warm 116.33 ms 116.04 ms -0.2% 0.6% 15%
tui.mark_idle_300 208.10 ms 208.58 ms +0.2% 0.8% 15%
mem.rss_daemon_import 38.50 MB 38.59 MB +0.2% 0.2% 25%
loop.stall_completions_500 6.58 ms 6.56 ms -0.2% 1.0% 76.03% (declared 25%)
resources.usage_from_dict_500 69.42 ms 69.27 ms -0.2% 1.8% 15%
cluster.fleet_view_15x400 69.78 ms 69.62 ms -0.2% 1.5% 25%
webapi.gzip_body_500 43.62 ms 43.52 ms -0.2% 0.8% 22.93% (declared 15%)
webui.render_fleet_15x400 18.05 ms 18.01 ms -0.2% 1.4% 25%
tui.week_rows_500 106.32 ms 106.55 ms +0.2% 1.1% 15%
dag.advance_quiescent_chain 55.38 ms 55.26 ms -0.2% 0.5% 25%
webui.select_move_500 48.70 ms 48.80 ms +0.2% 2.4% 25%
dag.finish_fanin_20x1k 54.19 ms 54.08 ms -0.2% 0.6% 25%
push.seal_500 71.39 ms 71.25 ms -0.2% 0.3% 15%
ical.render_500x7d 37.84 ms 37.77 ms -0.2% 0.5% 26.42% (declared 15%)
config.parse_yaml_300 115.38 ms 115.16 ms -0.2% 1.5% 15%
config.reload_gc_100k 1.423 s 1.420 s -0.2% 0.9% 25%
config.parse_yaml_rich_20 171.82 ms 172.14 ms +0.2% 2.0% 15%
state.inventory_2k_streams 72.60 ms 72.73 ms +0.2% 2.1% Not enforced
dag.run_chain_100 136.20 ms 135.95 ms -0.2% 0.8% 25%
job.stream_passthrough_240k 110.84 ms 111.03 ms +0.2% 1.4% 15%
state.artifact_put_distinct_2k 35.14 ms 35.08 ms -0.2% 1.6% 28.46% (declared 25%)
prometheus.first_scrape_after_reload_500 139.71 ms 139.47 ms -0.2% 1.7% 15%
job.launch_reap_2k 108.05 ms 107.87 ms -0.2% 1.0% 15%
dag.build_chain_10k 21.62 ms 21.65 ms +0.2% 6.4% 46.26% (declared 15%)
dag.full_sweep_50x200 92.64 ms 92.50 ms -0.2% 1.8% 25%
schedule.next_fires_2k 17.68 ms 17.71 ms +0.2% 1.3% 56.56% (declared 15%)
dag.mapped_drain_256 15.15 ms 15.13 ms -0.2% 1.6% 66% (declared 25%)
jobapi.read_path_10k 97.65 ms 97.50 ms -0.2% 0.5% 15%
dag.advance_waiting_mapped_1k 95.36 ms 95.22 ms -0.2% 0.8% 25%
dag.advance_quiescent_1k 56.14 ms 56.05 ms -0.2% 0.5% 25%
state.artifact_get_oldest_2k 63.02 ms 63.11 ms +0.1% 1.2% 25%
config.parse_yaml_3k 1.145 s 1.143 s -0.1% 1.0% 15%
webui.logo_recovery 85.70 ms 85.60 ms -0.1% 0.4% 25%
fingerprint.job_set_id_10k 224.55 ms 224.80 ms +0.1% 1.0% 15%
mcp.query_metrics_500 100.96 ms 101.06 ms +0.1% 2.3% 25%
webapi.jobs_payload_500 38.44 ms 38.40 ms -0.1% 0.7% 26.01% (declared 15%)
webapi.auth_scope_20k 66.09 ms 66.03 ms -0.1% 0.9% 15%
dag.build_layered_10k 38.11 ms 38.07 ms -0.1% 2.4% 26.24% (declared 15%)
cronexpr.occurrences_1k 10.48 ms 10.47 ms -0.1% 2.2% 95.46% (declared 15%)
mem.jobconfig_2k 1.59 MB 1.59 MB +0.1% 0.1% 31.46% (declared 15%)
startup.daemon_first_pass_2k 1.065 s 1.064 s -0.1% 0.6% 25%
config.reload_warm_50 28.23 ms 28.25 ms +0.1% 2.5% 35.42% (declared 25%)
pair.qr_encode_14 200.55 ms 200.48 ms -0.0% 0.7% 15%
state.kv_roundtrip_200 36.08 ms 36.09 ms +0.0% 1.2% 27.72% (declared 15%)
state.list_records_warm 87.23 ms 87.20 ms -0.0% 0.8% 15%
webapi.pools_poll_4x130 83.24 ms 83.21 ms -0.0% 0.7% 25%
dag.adopt_scan_500 59.60 ms 59.59 ms -0.0% 1.3% 33.55% (declared 25%)
mem.launch_reap_steady_20x55 0.38 MB 0.38 MB +0.0% 0.0% 15%
webapi.dag_run_poll_1k 69.41 ms 69.42 ms +0.0% 0.3% 25%
dag.list_dags_warm_x300 39.90 ms 39.90 ms -0.0% 1.0% 25%
mem.rss_version 14.41 MB 14.41 MB -0.0% 0.0% 25%
prometheus.counter_snapshot_2k 95.30 ms 95.30 ms -0.0% 1.7% 15%
mem.jobconfig_envfile_2k 16.34 MB 16.35 MB +0.0% 0.0% 15%
mem.rss_daemon_10k 143.82 MB 143.83 MB +0.0% 0.0% 25%
loop.sla_pass_5k_x10 114.96 ms 114.96 ms +0.0% 3.1% 15%
mem.gc_manifest_scan_128x2k 14.32 MB 14.32 MB -0.0% 0.0% 15%
mem.parse_peak_rich_20 4.21 MB 4.21 MB -0.0% 0.0% 15%
mem.cluster_view_50x400 11.64 MB 11.64 MB -0.0% 0.0% 15%
webui.wallboard_poll_500 42.90 ms 42.90 ms +0.0% 2.6% 25%
dag.run_doc_bytes_1k 228.53 KB 228.53 KB +0.0% 0.0% 5%
dag.mapped_doc_bytes_1k 725.81 KB 725.81 KB +0.0% 0.0% 5%
tui.frame_bytes_5k 60.22 KB 60.22 KB +0.0% 0.0% 15%
webui.log_count_5k 70.0 us 70.0 us +0.0% 0.0% 2857% (declared 25%)
webui.append_line_5k 10.0 us 10.0 us +0.0% 0.0% 25%
webapi.jobs_bytes_500 181.42 KB 181.42 KB +0.0% 0.0% 15%
webapi.jobs_gzip_500 5.19 KB 5.19 KB +0.0% 0.0% 15%
webapi.activity_bytes_500 2569.83 KB 2569.83 KB +0.0% 0.0% 15%
webapi.activity_capped_bytes_500 411.18 KB 411.18 KB +0.0% 0.0% 15%
webapi.fleet_bytes_15x400 963.62 KB 963.62 KB +0.0% 0.0% 15%
cluster.peer_bytes_512 83.55 KB 83.55 KB +0.0% 0.0% 15%
prometheus.counter_snapshot_bytes_2k 620.59 KB 620.59 KB +0.0% 0.0% 15%
prometheus.exposition_bytes_500 821.17 KB 821.17 KB +0.0% 0.0% 15%
mcp.list_jobs_bytes_500 155.54 KB 155.54 KB +0.0% 0.0% 15%
mcp.fleet_bytes_15x400 2052.76 KB 2052.76 KB +0.0% 0.0% 15%
mcp.tools_list_bytes 20.09 KB 20.09 KB +0.0% 0.0% 15%
mem.crontab_10k 2.80 MB 2.80 MB +0.0% 0.0% 17.85% (declared 15%)
mem.retired_output_1k 0.12 MB 0.12 MB +0.0% 0.0% 15%
mem.run_history_series_5 9.58 MB 9.58 MB +0.0% 0.0% 15%
mem.tui_state_5k 2.24 MB 2.24 MB +0.0% 0.0% 15%

Full Changelog: 1.2.69...1.2.70