Configuration
Everything you can set, where it lives, and what it defaults to.
- Where the jobs file comes from
- jobs.yaml field reference
- Writing the command
- No secrets in jobs.yaml
- Command reference
- State, history, and logs on disk
- Email alerting
- Hosting jobs.yaml remotely
- Environment variables
Where the jobs file comes from
Every subcommand except history resolves its configuration the same way, in
this order:
--jobs-file PATH— an explicit local file.--config-url URL— an HTTPS URL serving the file
(details).$REGSCALE_JOBS_FILE— a local path.$REGSCALE_JOBS_URL— an HTTPS URL.~/.regscale/jobs.yaml— the default.
Two rules about combining them:
- Passing both
--jobs-fileand--config-urlon the same command line is
a usage error; nothing runs. - An explicit
--jobs-filewins over$REGSCALE_JOBS_URL. The environment
variable is a fallback, not an override, so setting it globally in a container
image does not break a one-off local run.
A missing jobs file is not an error. It loads as an empty configuration, so
validate, list and run-due all succeed and do nothing until you create it.
jobs.yaml field reference
version: 1
jobs:
- name: wiz-nightly
description: Nightly Wiz vulnerability sync for the production SSP
command: ["wiz", "inventory", "--regscale_id", "42", "--regscale_module", "securityplans"]
schedule: "0 3 * * *"
timezone: America/New_York
enabled: true
timeout_minutes: 240
notify:
on_failure: [[email protected], [email protected]]
on_success: []
| Field | Type | Default | Notes |
|---|---|---|---|
version | int | required | Must be 1. |
jobs | list | [] | The job definitions. |
jobs[].name | string | required | Must match ^[a-z0-9][a-z0-9-]{0,63}$ — lower-case letters, digits, hyphens; starts with a letter or digit; 64 characters max. Must be unique in the file. |
jobs[].description | string | "" | Free text. Shown in failure emails; not interpreted. |
jobs[].command | list of strings | required, at least 1 item | The CLI arguments, one list item each. See Writing the command. |
jobs[].schedule | string | required | A five-field cron expression. See Schedules and cron. |
jobs[].timezone | string | "UTC" | An IANA zone name, e.g. America/New_York. The schedule is evaluated in this zone. |
jobs[].enabled | bool | true | A disabled job is skipped by run-due and daemon and shows - for its next run, but still runs on demand via jobs run. |
jobs[].timeout_minutes | int | 240 | Between 1 and 10080 (7 days). The job's subprocess is killed past this. |
jobs[].notify.on_failure | list of emails | [] | Emailed when a run ends FAILED or TIMEOUT. Requires SMTP configuration. |
jobs[].notify.on_success | list of emails | [] | Emailed when a run ends SUCCESS. |
Unknown fields are rejected rather than ignored, at both the document and the
job level — a typo like timeout_mins or notify_on_failure fails
validate instead of silently doing nothing. So does a duplicate job name,
a duplicate YAML key, a YAML anchor or alias (&a / *a), and a root that is
not a mapping.
Two validation details worth knowing because they catch real mistakes:
- A cron expression with no real occurrences is rejected, not just a
malformed one."0 0 30 2 *"(February 30th) parses fine as cron and would
never fire, so it fails validation. - Recipient addresses are checked for shape, not deliverability: an entry
needs something before and after an@, and may not contain spaces or
commas. Put each address in its own list item —[[email protected], [email protected]], never
["[email protected], [email protected]"].
Writing the command
command is an argv list: the arguments you would type after regscale,
each as its own item.
# Correct
command: ["axonius", "sync_assets", "--plan_id", "12"]
# Rejected — a single string, not a list
command: "axonius sync_assets --plan_id 12"
The job runs as an isolated subprocess of the CLI itself. There is no shell.
That means:
- No pipes,
&&,;, redirection, globbing, or$VARIABLEexpansion. A|
is just a literal character in an argument. - Quoting is not needed for values containing spaces — the list already
separates them.["--title", "My Report Name"]is one flag and one value. - To chain two commands, make them two jobs, scheduled a safe distance apart.
The job inherits the environment of whatever started it, so init.yaml and the
usual REGSCALE_* variables work exactly as they do interactively. The working
directory is inherited too — relevant for any command taking a relative file
path, which is a good reason to use absolute paths in a scheduled job.
Both stdout and stderr are captured to the run's log file. Standard input is
closed, so a command that would prompt for input fails rather than hangs.
No secrets in jobs.yaml
jobs.yaml is designed to be checked into version control or hosted in Blob
Storage, so it must never carry credentials. Authentication stays exactly where
it is today: init.yaml or environment variables.
This is enforced at load time, and the CLI refuses the whole file rather
than the offending job:
- Any mapping key anywhere in the document containing
password,secret,
token, orapikey(case-insensitive) is rejected. - A credential-bearing flag in
commandthat is followed by a value is
rejected —--password,--client-secret,--api-key,--aws-session-token
and friends. Flags whose name merely contains one of those words but whose
value is not a secret are fine:--token-urland--project_keyare
accepted,--api-keyand--secret-keyare not. - A credential-shaped value anywhere in
command— a bearer token, a JWT,
a token JSON blob — is rejected regardless of the flag name.
If a command genuinely needs a credential, configure it in init.yaml and let
the command read it from there. That is how the same command already works when
you run it by hand.
Separately from this load-time gate, the argv recorded in run history and the
log excerpt included in alert emails are both passed through the CLI's
credential scrubber. So a command that passes validation can still show
<redacted> in jobs history output — that is the recording layer, not this
one.
Command reference
Every command below accepts --jobs-file PATH or --config-url URL except
history, which reads recorded runs from the state directory rather than the
jobs file.
regscale jobs validate
regscale jobs validateParses and validates the configuration, then prints the job count.
regscale jobs validate --jobs-file ./jobs.yaml
# OK: 3 job(s)
Exit code 1 with a message naming the problem if the file is invalid. Good in
CI, and worth running before rolling out any config change.
regscale jobs list
regscale jobs listregscale jobs list
NAME SCHEDULE TZ ENABLED NEXT RUN LAST RESULT
axonius-weekly 0 2 * * 1 America/New_York True 2026-09-21 06:00:00Z SUCCESS exit=0
wiz-nightly 0 3 * * * America/New_York True 2026-09-17 07:00:00Z FAILED exit=1
jcam-monthly 0 4 1 * * UTC False - -
NEXT RUN is always UTC. - means the job is disabled. invalid means the
stored cron expression has no computable next occurrence.
regscale jobs run <name>
regscale jobs run <name>Runs one job immediately, ignoring both its schedule and its enabled flag.
regscale jobs run axonius-weekly
# axonius-weekly: SUCCESS (exit=0) log=/home/you/.regscale/job_logs/axonius-weekly-3f9a1c2b0d4e.log
Exits 0 only on success; 1 for FAILED, TIMEOUT, or an unknown job name.
The run is recorded in history with trigger=manual and does send notification
email.
It deliberately does not take the scheduler's interlock, so it can run
alongside a scheduled pass. If a job is not safe to run twice at once, do not
trigger it manually while it is also due.
regscale jobs run-due
regscale jobs run-dueRuns every enabled job whose most recent scheduled occurrence has not run yet,
then exits. This is the mode for an external scheduler.
regscale jobs run-due
# Ran 2 job(s)
Safe to call more often than any job's schedule — it decides what is actually
due. See Deployment.
regscale jobs daemon
regscale jobs daemonRuns the scheduler loop in the foreground: reload config, run what is due,
sleep, repeat.
| Option | Default | Notes |
|---|---|---|
--interval SECONDS | 60 | Seconds between passes. Minimum 5. |
--grace-period SECONDS | 10 | How long a timed-out job gets between the polite stop signal and being force-killed. Minimum 0. |
regscale jobs daemon --interval 60
On startup it loads the configuration, marks any run left RUNNING by a
previous process as FAILED, and logs each enabled job's next fire time in
that job's own timezone:
Job axonius-weekly scheduled (0 2 * * 1, tz=America/New_York); next fire ~2026-09-21 02:00:00 EDT
Then every pass re-reads the configuration, so schedule edits take effect on the
next tick with no restart. If a reload fails, it logs a warning and keeps
running on the last known-good configuration rather than exiting.
SIGTERM and SIGINT (Ctrl+C) request a clean stop: it finishes the job in
flight, saves state, and exits without sleeping again — which makes it safe to
stop with a normal container shutdown.
regscale jobs history <name>
regscale jobs history <name>regscale jobs history axonius-weekly --limit 10
2026-09-21 06:00:03Z SUCCESS exit=0 1243.7s trigger=daemon
2026-09-14 06:00:01Z FAILED exit=1 8.2s trigger=daemon
--limit defaults to 20. trigger is daemon, run-due, manual
(jobs run) or tui (Run Now in the terminal UI). History survives deleting
the job, so this still works for a job that is no longer scheduled.
State, history, and logs on disk
All of it lives under ~/.regscale by default. Point the whole tree somewhere
else with REGSCALE_JOBS_STATE_DIR — do that for any container or service
account, where a home directory may not be what you expect.
| Path | What it holds |
|---|---|
jobs_state.json | The occurrence each job last ran for, plus the last scheduler tick. This is what makes run-due and daemon idempotent. |
job_runs/<job-name>.jsonl | Append-only run history, one JSON object per line. Kept to the most recent 200 runs per job. |
job_logs/<job-name>-<run-id>.log | One log file per run: the job's merged stdout and stderr. |
jobs_remote_cache.yaml / .etag | Last known-good copy of a remotely hosted config. |
run-due.lock, job_runs/*.lock | Lock files. Leave them alone. |
Two things follow from that table:
- Logs are pruned with history. When a job's history passes 200 runs, the
oldest records are dropped and their log files are deleted with them. If you
need run output kept longer than that, ship it off the box. - A single run's log is capped at 5 MB. Past that, the file holds the first
5 MB, a line saying how many bytes were dropped, and the last 64 KB — so both
ends of a long, noisy job survive. Directories and files are created
owner-only (0700/0600), and log files are scrubbed of credential-shaped
text when the run closes.
The state directory must be writable and persistent. On a container, that
means a volume: lose it and the scheduler forgets what already ran.
Email alerting
regscale jobs sends one plain-text email per finished run to that job's
recipient list, through an SMTP relay you configure. There is no per-job relay,
no HTML mail, and no digest.
Relay settings
Six init.yaml keys, each with an environment-variable fallback:
init.yaml key | Env fallback | Default | Notes |
|---|---|---|---|
smtpServer | SMTP_SERVER | "" | Relay hostname. Required. |
smtpPort | SMTP_PORT | 587 | Relay port. |
smtpUser | SMTP_USER | "" | Optional; only used together with smtpPassword. |
smtpPassword | SMTP_PASSWORD | "" | Optional; sensitive, never logged. |
smtpFrom | SMTP_FROM | "" | From address. Required. |
smtpUseTls | SMTP_USE_TLS | true | Issues STARTTLS after connecting. |
Alerting is off until both smtpServer and smtpFrom are set. Username and
password are optional — an anonymous relay works — but they are only used when
both are present.
TLS is on by default and the relay's certificate is verified, against the
system trust store or against the bundle named by customCaCert if you have one
configured for internal CAs. Setting smtpUseTls: false turns off encryption
and verification both; if credentials are configured, authentication is then
skipped rather than sent in cleartext, and the relay will most likely reject the
message. Only use false for a plaintext relay on a trusted network.
Relay settings are read once per process, so restart the daemon after
changing them.
Who gets mail
| Run ended | Recipients |
|---|---|
FAILED | notify.on_failure |
TIMEOUT | notify.on_failure — a timeout counts as a failure |
SUCCESS | notify.on_success |
CANCELLED | Nobody — a human stopped it and already knows |
An empty list means no mail and no connection to the relay, so an unconfigured
relay is harmless for jobs that only populate the other list. This applies
identically however the job was started — daemon, run-due, jobs run, or
Run Now in the TUI.
What the mail looks like
Subject: [RegScale Jobs] FAILED: axonius-weekly on ip-10-0-1-23
Body: the job name and description, terminal state, trigger, start and finish
times in UTC, duration, exit code, the log file path, and the last 50 lines of
the run's log with credential-shaped text redacted. That last part is usually
enough to triage without logging into the host.
A delivery failure is logged and never fails the job: an unreachable relay costs
you the notification, not the run. If recipients are configured but the relay is
not, you get one warning per process naming the job and the two missing keys —
once, not once per run.
Hosting jobs.yaml remotely
Instead of a file on the host, the CLI can fetch jobs.yaml over HTTPS —
typically an Azure Blob SAS URL with read-only permission. The point is to
change schedules without rebuilding or restarting anything.
regscale jobs validate --config-url "https://acct.blob.core.windows.net/cfg/jobs.yaml?sv=...&sig=..."
# OK: 3 job(s)
export REGSCALE_JOBS_URL="https://acct.blob.core.windows.net/cfg/jobs.yaml?sv=...&sig=..."
regscale jobs daemon --interval 60
A read-only SAS token is enough, because the file carries no credentials of its
own. What you need to know:
https://only. Any other scheme — including a bare hostname — is
rejected before any network call, because a SAS token in a query string must
never cross the wire in cleartext.- Live reload. The daemon re-fetches at the start of every pass, so an edit
to the blob takes effect on the next tick. - Cheap polling. The fetch sends the previous
ETag; a304 Not Modified
reuses the already-validated configuration without re-parsing. - Fail-safe. If a fetch fails — network error, non-200, or a document that
fails validation — the CLI falls back to the last known-good copy it holds in
memory, then to the on-disk cache in the state directory (which survives a
restart), and only raises an error if neither exists. Whenever it is serving a
fallback copy,jobs listprints a warning to stderr andjobs validate
exits 1, so a stale config is visible rather than silent. - Bounded. The response is capped at 5 MB and the fetch times out after 30
seconds, so a misconfigured URL cannot exhaust the daemon's memory. - TLS follows the same
sslVerify/customCaCertsettings as every other
outbound call the CLI makes. There is no separate knob. - The SAS token stays out of the logs. Only scheme, host and path are ever
logged, including inhttpxrequest logging.
To build the file without hand-editing YAML, use the TUI's Export YAML
button and paste the result into the blob — see the TUI guide.
Environment variables
| Variable | Effect |
|---|---|
REGSCALE_JOBS_FILE | Path to jobs.yaml. Overridden by an explicit --jobs-file. |
REGSCALE_JOBS_URL | HTTPS URL serving jobs.yaml. Equivalent to --config-url; loses to an explicit --jobs-file. |
REGSCALE_JOBS_STATE_DIR | Root for jobs_state.json, job_runs/, job_logs/ and the remote cache. Defaults to ~/.regscale. |
SMTP_SERVER, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_FROM, SMTP_USE_TLS | Fallbacks for the init.yaml SMTP keys above. |
Jobs also inherit the ordinary CLI variables — REGSCALE_DOMAIN,
REGSCALE_TOKEN, REGSCALE_USERNAME / REGSCALE_PASSWORD,
REGSCALE_CONFIG_FILE and the per-integration ones — because a job is just the
CLI running with the environment it was given.
Updated about 8 hours ago
