Configuration

Everything you can set, where it lives, and what it defaults to.

Where the jobs file comes from

Every subcommand except history resolves its configuration the same way, in
this order:

  1. --jobs-file PATH — an explicit local file.
  2. --config-url URL — an HTTPS URL serving the file
    (details).
  3. $REGSCALE_JOBS_FILE — a local path.
  4. $REGSCALE_JOBS_URL — an HTTPS URL.
  5. ~/.regscale/jobs.yaml — the default.

Two rules about combining them:

  • Passing both --jobs-file and --config-url on the same command line is
    a usage error; nothing runs.
  • An explicit --jobs-file wins 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: []
FieldTypeDefaultNotes
versionintrequiredMust be 1.
jobslist[]The job definitions.
jobs[].namestringrequiredMust 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[].descriptionstring""Free text. Shown in failure emails; not interpreted.
jobs[].commandlist of stringsrequired, at least 1 itemThe CLI arguments, one list item each. See Writing the command.
jobs[].schedulestringrequiredA five-field cron expression. See Schedules and cron.
jobs[].timezonestring"UTC"An IANA zone name, e.g. America/New_York. The schedule is evaluated in this zone.
jobs[].enabledbooltrueA 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_minutesint240Between 1 and 10080 (7 days). The job's subprocess is killed past this.
jobs[].notify.on_failurelist of emails[]Emailed when a run ends FAILED or TIMEOUT. Requires SMTP configuration.
jobs[].notify.on_successlist 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 $VARIABLE expansion. 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, or apikey (case-insensitive) is rejected.
  • A credential-bearing flag in command that 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-url and --project_key are
    accepted, --api-key and --secret-key are 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

Parses 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 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>

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

Runs 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

Runs the scheduler loop in the foreground: reload config, run what is due,
sleep, repeat.

OptionDefaultNotes
--interval SECONDS60Seconds between passes. Minimum 5.
--grace-period SECONDS10How 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 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.

PathWhat it holds
jobs_state.jsonThe occurrence each job last ran for, plus the last scheduler tick. This is what makes run-due and daemon idempotent.
job_runs/<job-name>.jsonlAppend-only run history, one JSON object per line. Kept to the most recent 200 runs per job.
job_logs/<job-name>-<run-id>.logOne log file per run: the job's merged stdout and stderr.
jobs_remote_cache.yaml / .etagLast known-good copy of a remotely hosted config.
run-due.lock, job_runs/*.lockLock 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 keyEnv fallbackDefaultNotes
smtpServerSMTP_SERVER""Relay hostname. Required.
smtpPortSMTP_PORT587Relay port.
smtpUserSMTP_USER""Optional; only used together with smtpPassword.
smtpPasswordSMTP_PASSWORD""Optional; sensitive, never logged.
smtpFromSMTP_FROM""From address. Required.
smtpUseTlsSMTP_USE_TLStrueIssues 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 endedRecipients
FAILEDnotify.on_failure
TIMEOUTnotify.on_failure — a timeout counts as a failure
SUCCESSnotify.on_success
CANCELLEDNobody — 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; a 304 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 list prints a warning to stderr and jobs 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 / customCaCert settings 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 in httpx request 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

VariableEffect
REGSCALE_JOBS_FILEPath to jobs.yaml. Overridden by an explicit --jobs-file.
REGSCALE_JOBS_URLHTTPS URL serving jobs.yaml. Equivalent to --config-url; loses to an explicit --jobs-file.
REGSCALE_JOBS_STATE_DIRRoot 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_TLSFallbacks 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.


Did this page help you?