Repository navigation
1.2.63
cronstable pairprints 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--urlis 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-urlsets the
address that the phone dials.--format linkand--format jsonprint 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 /whoamireportsinstance, an ID that the daemon draws at random
when it starts, andlisteners, the addresses of its bound TCP sockets.
The reply to an anonymous request underweb.anonymousScopeshas no
listeners. Every response that the web API serves, including a401and
a404, carries the ID in theCronstable-Instanceheader, so a client
can tell one daemon from another without presenting a token. The400for
a request that the server can't parse, such as one with a malformed request
line, doesn't carry the header.cronstable mcpreports an HTTP redirect as a transport error that names
the redirect's target, and the--urlvalue to pass when the redirect
leads to the/mcpendpoint at another address. The bridge does not follow
redirects, because each request carries the bearer token. A reply that is
not HTTP and a--urlthat is not a valid URL get a transport error too.cronstable mcp,cronstable pair, andcronstable tuirefuse 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 mcpandcronstable pairsend 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, andxcom) 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 a2xxreply counts as success, so a
redirect status that names no target, such as a302with noLocation
header, ends the command with status 1. cronstable mcp --timeouttakes a finite number greater than 0, and any
other value is a usage error.cronstable lock acquireandlock runread
a negative--timeoutas 0, and a value ofnanorinfis 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, asheaddoes after its last line. - In the terminal dashboard,
Esccloses the panel or drawer that is on
top. When a401opens 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, socronstable tui --tvworks against a daemon that
requires a token. With--job NAMEand 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 a401. - 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 reads127.1
as127.0.0.1,state.jobApi.listen: http://127.1:9000counts 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 as0177.0.0.1on 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 readslive, and it
always showsno signalin 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
401opens 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, andEnterin 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
pipandpipxinstalls, and the
test matrix runs it on Linux, Windows, and macOS. On Python 3.15, the
speedupsextra skipsisal, which has no wheel for that version, so the
daemon compresses responses with the standard library'szlib. The
orjsonwheels 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