Skip to content

1.2.63

Choose a tag to compare

@github-actions github-actions released this 11 Oct 18:36
· 74 commits to main since this release
Immutable release. Only release title and notes can be modified.
  • cronstable pair prints the iOS app's pairing QR code in the terminal, for
    a server that runs the HTTP API without the dashboard page
    (web.ui: false) or a shell with no browser. It is the code that the web
    dashboard's Pair a device panel shows, and it contains the token that the
    command presents. When --url is a loopback address, the command puts
    another address of the host in the code: the LAN address, or the address of
    a listener on another interface, such as a VPN's. The daemon must report a
    listener there, and the address must answer with the daemon's instance ID.
    The command sends the token only to --url, and --public-url sets the
    address that the phone dials. --format link and --format json print the
    pairing link and the pairing JSON as text, and the JSON escapes a character
    that isn't printable, such as a control character in the server name. The
    terminal dashboard's command palette opens the same code with
    "Pair a device (QR)".
  • GET /whoami reports instance, an ID that the daemon draws at random
    when it starts, and listeners, the addresses of its bound TCP sockets.
    The reply to an anonymous request under web.anonymousScopes has no
    listeners. Every response that the web API serves, including a 401 and
    a 404, carries the ID in the Cronstable-Instance header, so a client
    can tell one daemon from another without presenting a token. The 400 for
    a request that the server can't parse, such as one with a malformed request
    line, doesn't carry the header.
  • cronstable mcp reports an HTTP redirect as a transport error that names
    the redirect's target, and the --url value to pass when the redirect
    leads to the /mcp endpoint at another address. The bridge does not follow
    redirects, because each request carries the bearer token. A reply that is
    not HTTP and a --url that is not a valid URL get a transport error too.
  • cronstable mcp, cronstable pair, and cronstable tui refuse a bearer
    token that contains a line break or another character that an HTTP header
    cannot carry, such as the line ending of a file that the variable was read
    from. The message names the flag or the environment variable and leaves
    the token out.
  • cronstable mcp and cronstable pair send the bearer token as UTF-8, the
    form that the daemon compares, so a token with characters outside ASCII
    authenticates.
  • The job state commands (cronstable state, cursor, lock, artifact,
    idempotent, secret, and xcom) report an HTTP redirect from the state
    endpoint as an error that names the redirect's target. They do not follow
    redirects, because each request carries the run's token. A reply that is
    not HTTP gets a one-line error. Only a 2xx reply counts as success, so a
    redirect status that names no target, such as a 302 with no Location
    header, ends the command with status 1.
  • cronstable mcp --timeout takes a finite number greater than 0, and any
    other value is a usage error. cronstable lock acquire and lock run read
    a negative --timeout as 0, and a value of nan or inf is a usage
    error. Each command waits at most 2,147,483 seconds, about 24.9 days, for
    a reply.
  • Every subcommand ends with one line on stderr and status 1 when the reader
    of its output closes the pipe, as head does after its last line.
  • In the terminal dashboard, Esc closes the panel or drawer that is on
    top. When a 401 opens the access token prompt while the command palette
    is open, the palette closes and the prompt takes the keys. The wallboard
    draws the prompt and any other open panel or drawer, and the one on top
    takes the keys, so cronstable tui --tv works against a daemon that
    requires a token. With --job NAME and no token, the prompt comes first,
    and the job's drawer appears when the prompt closes. With
    --tv --job NAME, the drawer opens beside the board. Saving a token
    restarts a log stream that the daemon refused with a 401.
  • The Bonjour advert skips a listener bound to [::]. That socket accepts
    IPv6 connections only, and the advert's address record is IPv4. On a host
    with no default route and a hostname that isn't a valid DNS name, such as
    one with a 64-character label, the daemon logs a warning and skips the
    advert.
  • Configuration checks read a loopback or wildcard address in every form that
    the host's socket layer reads as an address. On a host that reads 127.1
    as 127.0.0.1, state.jobApi.listen: http://127.1:9000 counts as a
    loopback bind. A form that the host looks up as a name counts as a
    hostname, so the checks treat it as a non-loopback address. So does a form
    with a leading zero that the host's C library reads as octal in one call
    and as decimal in another, such as 0177.0.0.1 on macOS.
  • The web dashboard's header keeps its buttons in view at any window width.
    As the window narrows, the header hides readouts, least essential first:
    the job-set ID chip, the node meter's bars, the clock, the version and the
    node meter, and then the summary pills. On a phone-width screen, the token
    button shows only its lock icon. In a window about 375px wide or narrower,
    the connection indicator shows only its dot while it reads live, and it
    always shows no signal in words. A larger UI scale narrows the header in
    the same way. Content that still doesn't fit, such as a long version
    string, wraps to a second row.
  • The web dashboard's command palette has a "Toggle node resources" entry.
    It opens and closes the node resources card at any window width, including
    when the header hides the node meter.
  • In the web dashboard, when a 401 opens the access token prompt while the
    command palette is open, the palette closes and the prompt takes the keys.
    The palette shortcut does nothing while a panel that covers the palette is
    open, such as the token prompt or the settings panel. The prompt opens on
    top of every other panel, and Enter in its field saves the token without
    activating the control that held the focus before.
  • In the web dashboard's log panes, line numbers and timestamps have a
    contrast ratio of at least 4.5:1 in all ten themes. Placeholder text in
    every field uses the theme's faint ink.
  • In the web dashboard, the workflows, resource pools, and saved state cards
    align their content to the same 12px gutter as the jobs table. Workflow run
    states and the saved state card's tabs have the square corners of the other
    chips and buttons. The workflow drawer's backfill fields match the token
    dialog's input, and the row wraps in a narrow drawer.
  • Web dashboard buttons tint in their own text color while you press them.
    The job drawer's Run button shows a play icon, as Cancel and Pause show
    theirs. The connection indicator's dot uses the ok status color, so the
    color vision setting remaps it. With Reduce motion on, a drawer opens in
    place without sliding. The demo dashboard includes all of these changes.
  • cronstable supports Python 3.15 for pip and pipx installs, and the
    test matrix runs it on Linux, Windows, and macOS. On Python 3.15, the
    speedups extra skips isal, which has no wheel for that version, so the
    daemon compresses responses with the standard library's zlib. The
    orjson wheels for Python 3.15 on glibc Linux require glibc 2.39 or
    later. The release binaries and the Docker images bundle Python 3.14 or an
    earlier version.

Performance compared with 1.2.62

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 (100 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. 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.62 1.2.63 Change Noise (±) Regression limit
schedule.duplicates_20k 18.59 ms 17.16 ms -7.7% 5.6% 53.8% (declared 15%)
loop.stall_metrics_2000 26.72 ms 28.55 ms +6.8% 1.8% Not enforced
webui.append_line_5k 15.0 us 14.0 us -6.7% 3.0% 25%
schedule.due_pass_100k 16.26 ms 15.62 ms -4.0% 5.2% 61.49% (declared 25%)
mem.gc_pause_100k 134.99 ms 129.70 ms -3.9% 7.1% 25%
cronexpr.test_match_200k 25.64 ms 24.65 ms -3.9% 3.4% 38.99% (declared 15%)
startup.import_cronexpr 26.04 ms 25.63 ms -3.7% 0.3% 25%
cluster.parse_summaries_6k 86.23 ms 89.25 ms +3.5% 1.8% 15%
schedule.pressure_20k_48h 16.46 ms 15.93 ms -3.2% 4.5% 60.74% (declared 15%)
webui.render_term_5k 107.50 ms 104.10 ms -3.2% 1.7% 25%
cronexpr.next_dst_2k 40.17 ms 41.28 ms +2.8% 1.4% 24.89% (declared 15%)
job.report_noop_100k 51.93 ms 53.31 ms +2.7% 8.4% 15%
cronexpr.next_complex 15.77 ms 15.36 ms -2.6% 2.0% 63.42% (declared 15%)
config.reload_gc_100k 2.723 s 2.663 s -2.2% 1.2% 25%
cronexpr.next_simple 39.00 ms 38.26 ms -1.9% 1.1% 25.64% (declared 15%)
ical.render_500x7d 50.80 ms 51.73 ms +1.8% 1.6% 19.68% (declared 15%)
dag.plan_claim_10k 13.42 ms 13.67 ms +1.8% 1.3% 74.49% (declared 15%)
schedule.suggest_slot_5k 4.34 ms 4.26 ms -1.8% 2.1% 230.6% (declared 15%)
webui.week_walk_500 2.90 ms 2.95 ms +1.7% 2.5% 68.97% (declared 25%)
state.list_records_2k 22.65 ms 23.04 ms +1.7% 1.9% 44.14% (declared 15%)
cluster.job_owner_2k 57.77 ms 58.75 ms +1.7% 1.6% 17.31% (declared 15%)
startup.import_daemon 178.16 ms 180.70 ms +1.6% 2.9% 25%
config.jobconfig_3k 68.72 ms 67.66 ms -1.5% 0.8% 15%
cronexpr.parse_complex 24.08 ms 23.74 ms -1.4% 1.3% 41.53% (declared 15%)
redact.clean_20k 20.41 ms 20.68 ms +1.3% 2.6% 49% (declared 15%)
redact.secrets_5k 20.49 ms 20.76 ms +1.3% 1.6% 48.8% (declared 15%)
state.artifact_list_churn 1.49 ms 1.47 ms -1.3% 2.0% 335.3% (declared 25%)
startup.version 53.36 ms 53.72 ms +1.3% 1.8% 25%
webui.render_fleet_15x400 23.84 ms 23.54 ms -1.3% 1.8% 25%
loop.stall_completions_500 10.12 ms 9.99 ms -1.3% 1.7% 49.43% (declared 25%)
state.list_records_warm 107.21 ms 108.46 ms +1.2% 0.8% 15%
startup.validate_config_100 221.74 ms 219.36 ms -1.1% 1.6% 25%
state.append_1k 125.16 ms 126.56 ms +1.1% 0.7% 39.95% (declared 15%)
webui.render_rows_500 17.19 ms 17.00 ms -1.1% 1.2% 25%
startup.job_set_id_100 227.65 ms 225.35 ms -1.1% 1.1% 25%
prometheus.render_500 17.24 ms 17.42 ms +1.1% 1.7% 58.01% (declared 15%)
state.artifact_get_newest 21.44 ms 21.66 ms +1.0% 1.3% 25%
schedule.reseed_local_20k 95.42 ms 94.45 ms -1.0% 2.0% 15%
redact.adversarial_10k 36.17 ms 36.51 ms +1.0% 1.0% 27.65% (declared 15%)
config.parse_yaml_3k 1.996 s 1.979 s -0.8% 1.8% 15%
tui.log_restyle_5k 31.44 ms 31.69 ms +0.8% 1.4% 31.8% (declared 15%)
schedule.lint_250_zoned 7.90 ms 7.83 ms -0.8% 0.7% 126.6% (declared 15%)
job.stream_capture_120k 36.28 ms 36.00 ms -0.7% 2.7% 27.57% (declared 15%)
state.boot_rehydrate_populated 39.54 ms 39.25 ms -0.7% 1.8% 25.29% (declared 15%)
startup.import_config 95.37 ms 95.86 ms +0.7% 2.6% 25%
mcp.handle_200 155.76 ms 156.92 ms +0.7% 1.0% 15%
push.seal_500 90.86 ms 90.19 ms -0.7% 0.5% 15%
state.kv_roundtrip_200 55.82 ms 56.20 ms +0.7% 0.9% 17.91% (declared 15%)
webapi.sse_burst_20k 45.92 ms 46.23 ms +0.7% 1.8% 15%
schedule.cold_build_100k 491.40 ms 488.20 ms -0.7% 0.9% 15%
schedule.next_fires_2k 25.20 ms 25.36 ms +0.6% 1.2% 39.69% (declared 15%)
fingerprint.job_set_id_10k 304.07 ms 305.97 ms +0.6% 1.4% 15%
webapi.jobs_payload_500 50.66 ms 50.97 ms +0.6% 1.3% 19.74% (declared 15%)
state.derive_max_cold 19.96 ms 19.85 ms -0.6% 0.8% 50.09% (declared 15%)
cronexpr.occurrences_1k 14.11 ms 14.03 ms -0.5% 0.6% 70.88% (declared 15%)
dag.adopt_scan_500 100.53 ms 101.08 ms +0.5% 0.9% 25%
config.parse_yaml_300 198.94 ms 199.99 ms +0.5% 1.4% 15%
startup.python_baseline 17.47 ms 17.38 ms -0.5% 2.3% Not enforced
dag.build_chain_10k 28.95 ms 28.81 ms -0.5% 2.1% 34.54% (declared 15%)
statsd.emit_2k 13.07 ms 13.13 ms +0.5% 0.6% 76.51% (declared 25%)
webapi.gzip_body_500 63.85 ms 64.13 ms +0.4% 0.1% 15%
loop.stall_jobs_500 20.80 ms 20.88 ms +0.4% 0.6% Not enforced
config.parse_crontab_1k 12.51 ms 12.47 ms -0.4% 1.2% 79.92% (declared 15%)
state.mutate_document_1k 144.87 ms 145.38 ms +0.4% 0.7% 34.51% (declared 25%)
config.interp_2k 65.87 ms 65.65 ms -0.3% 0.7% 15%
json.roundtrip_orjson_3k 209.84 ms 210.43 ms +0.3% 0.5% 15%
json.roundtrip_3k 577.15 ms 578.76 ms +0.3% 0.7% 15%
state.gc_sweep_2k_streams 32.68 ms 32.76 ms +0.3% 1.3% 45.91% (declared 25%)
schedule.reseed_100k 203.33 ms 202.80 ms -0.3% 1.0% 15%
tui.drawer_paint_5k 104.76 ms 105.00 ms +0.2% 0.5% 15%
config.reload_warm_50 46.55 ms 46.45 ms -0.2% 1.9% 25%
dag.finish_fanin_1k 3.41 ms 3.40 ms -0.2% 1.5% 146.8% (declared 25%)
mem.rss_version 14.47 MB 14.44 MB -0.2% 0.3% 25%
state.depends_on_past_gate 3.08 ms 3.07 ms -0.2% 1.9% 65.02% (declared 25%)
dag.list_runs_warm 12.87 ms 12.89 ms +0.2% 0.9% 25%
dag.build_layered_10k 49.48 ms 49.55 ms +0.1% 1.0% 20.21% (declared 15%)
mem.rss_daemon_import 37.49 MB 37.54 MB +0.1% 0.1% 25%
cronexpr.parse_simple 44.07 ms 44.02 ms -0.1% 0.8% 22.69% (declared 15%)
config.reload_warm_include_50 100.01 ms 100.13 ms +0.1% 0.7% 25%
loop.idle_wake_rate 22.14 ms 22.12 ms -0.1% 1.2% 25%
state.derive_max_warm 146.34 ms 146.51 ms +0.1% 0.3% 15%
dag.advance_quiescent_1k 72.14 ms 72.07 ms -0.1% 0.3% 25%
state.fanout_gather_100 10.01 ms 10.00 ms -0.1% 2.6% 99.91% (declared 25%)
dag.advance_quiescent_chain 70.95 ms 71.02 ms +0.1% 1.0% 25%
cluster.fleet_view_15x400 84.63 ms 84.55 ms -0.1% 1.5% 25%
state.lease_renew_200 50.08 ms 50.03 ms -0.1% 1.2% 99.85% (declared 25%)
webapi.auth_scope_20k 87.26 ms 87.34 ms +0.1% 1.7% 15%
tui.dag_graph_paint_2k 228.45 ms 228.30 ms -0.1% 1.4% 15%
tui.log_search_20k 15.41 ms 15.42 ms +0.1% 1.0% 64.89% (declared 15%)
dag.list_dags_warm 1.09 ms 1.09 ms -0.1% 1.8% 183.2% (declared 25%)
resources.monitor_stop_100 290.78 ms 290.63 ms -0.1% 0.3% 25%
state.list_documents_600 55.68 ms 55.70 ms +0.0% 0.8% 21.55% (declared 15%)
dag.mapped_drain_256 19.98 ms 19.97 ms -0.0% 1.1% 50.06% (declared 25%)
webui.log_count_5k 100.0 us 100.0 us +0.0% 0.0% 2000% (declared 25%)
webui.radar_walk_500 400.0 us 400.0 us +0.0% 1.1% 500% (declared 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%
mem.crontab_10k 2.80 MB 2.80 MB +0.0% 0.0% 17.85% (declared 15%)
mem.jobconfig_2k 1.62 MB 1.62 MB +0.0% 0.0% 30.9% (declared 15%)
mem.retired_output_1k 0.12 MB 0.12 MB +0.0% 0.0% 433.7% (declared 15%)

What's Changed

  • Bump the python group with 4 updates by @dependabot[bot] in #35
  • Bump the github-actions group across 1 directory with 2 updates by @dependabot[bot] in #34
  • Bump the nix group with 2 updates by @dependabot[bot] in #33

Full Changelog: 1.2.62...1.2.63