FAQ and Troubleshooting
- My job did not run
- My job ran and failed
- Error messages, explained
- Logs, history, and disk
- Questions about behavior
- Things it does not do
- What to gather before asking for help
My job did not run
Work down this list; it is ordered by how often each one turns out to be the
cause.
1. Is anything actually running the schedule?
This is the answer most of the time. jobs.yaml is inert on its own — a daemon
or a run-due trigger has to exist.
regscale jobs list # does the job exist, and is NEXT RUN what you expect?
ps aux | grep "jobs daemon" # Linux/macOS, daemon mode
systemctl status regscale-jobs # systemd
docker ps | grep regscale # Docker
kubectl get pods,cronjobs # Kubernetes
The TUI's Jobs tab says this directly: Scheduler: no recent activity means no
pass has been recorded, and nothing will fire until you start one. See
Deployment.
2. Is the job enabled?
ENABLED: False in jobs list, or a - in the NEXT RUN column, means it is
skipped by both daemon and run-due. Flip enabled: true, or use the TUI's
Enable/Disable button.
3. Does the config even load?
regscale jobs validate
One invalid job rejects the whole file — so a typo in a job you added last
week can silently stop every other job. If the scheduler was already running
when you broke the file, it keeps going on the last good copy and logs
Config reload failed (...); continuing on last known-good config. That is a
warning worth alerting on.
4. Is the next run when you think it is?
NEXT RUN is UTC. 0 2 * * 1 with timezone: America/New_York shows as
06:00Z — that is correct, not a bug. And check the expression itself: a
schedule with both a day-of-month and a day-of-week is an OR, not an AND. See
Schedules and cron.
5. Is the poll frequent enough?
A job scheduled */5 * * * * under a run-due triggered every 15 minutes runs
once every 15 minutes. Missed occurrences collapse into one run.
6. Did state get lost?
If the state directory is not persistent — a container with no volume, an
emptyDir, a REGSCALE_JOBS_STATE_DIR pointing into /tmp — every job looks
due on the next pass and you get a burst of runs after each restart, then
nothing predictable. Fix the volume.
7. Is another pass holding the lock?
another run-due/daemon pass is in progress; skipping this pass in the log means
a previous pass is still going — usually a long job. That is the interlock doing
its job, not an error. If it never clears, find the stuck run:
regscale jobs history <name> --limit 5 # a RUNNING record with no terminal record
8. Check the scheduler's own log.
journalctl -u regscale-jobs, docker logs, kubectl logs. The daemon logs
every start (Starting job <name> (run <id>, trigger=daemon)) and every finish.
If the job's name is absent entirely, it was never considered due.
My job ran and failed
The run's own log has the reason. Find it:
regscale jobs history axonius-weekly
# 2026-09-21 06:00:03Z FAILED exit=1 8.2s trigger=daemon
ls -lt ~/.regscale/job_logs/ | head # or $REGSCALE_JOBS_STATE_DIR/job_logs
tail -100 ~/.regscale/job_logs/axonius-weekly-<run-id>.log
That file is the command's own stdout and stderr, exactly as you would have seen
it interactively.
The first thing to rule out is that the command itself is broken, not the
scheduling:
regscale axonius sync_assets --plan_id 12 # run it by hand
regscale jobs run axonius-weekly # run it through the scheduler
If the first works and the second does not, the difference is almost always
environment — see the next two answers.
FAILED with an authentication error. The job inherits the environment of
the process that started it. A REGSCALE_TOKEN exported in your interactive
shell is not present in cron, systemd, or a container. Put credentials in
init.yaml (and set REGSCALE_CONFIG_FILE if it is not in the default
location) or set the variables in the unit/crontab/pod spec itself.
FAILED with a file-not-found error. The job inherits the working directory
too, and cron's is not yours. Use absolute paths for every file argument.
TIMEOUT. The job exceeded timeout_minutes and was terminated — first
politely, then hard after the grace period. Whatever the command had already
written to RegScale stays written; there is no rollback. Either raise
timeout_minutes (up to 10080) or narrow the job's scope, e.g. by plan.
FAILED right after a restart, with no output in the log. A run left
RUNNING by a killed process is reconciled to FAILED at the next daemon
startup (Reconciled N stale RUNNING record(s)). The job did not fail on its
own merits — it was interrupted. Give the scheduler a longer stop timeout
(TimeoutStopSec, stop_grace_period, terminationGracePeriodSeconds) so this
stops happening during restarts.
Error messages, explained
| Message | What it means |
|---|---|
command items must be non-empty strings | An empty string in the argv list, usually from stray YAML quoting like ["axonius", "", "sync_assets"]. |
Input should be a valid list on command | You wrote command: "axonius sync_assets". It must be a list: ["axonius", "sync_assets"]. |
invalid cron expression for schedule: '...' | Not five whitespace-separated fields, or not parseable. @daily and friends are not accepted. |
cron schedule has no valid occurrences: '...' | It parses but can never fire — 0 0 30 2 * (February 30th). |
unknown IANA timezone: 'EST' | Use a region/city name: America/New_York. Windows zone names are not IANA names. |
String should match pattern '^[a-z0-9][a-z0-9-]{0,63}$' | A job name with capitals, underscores, dots, or spaces. Lower-case, digits and hyphens only. |
duplicate job name: 'x' | Two jobs share a name. Names are the key for state and history, so they must be unique. |
duplicate key 'jobs' at line N in jobs config | The same YAML key twice. One of them would have been silently ignored, so the file is refused. |
YAML anchors/aliases are not allowed in jobs config | &anchor / *alias. Write the value out in full. |
jobs config root must be a mapping with 'version' and 'jobs' keys | The file is a bare list, or empty-but-not-absent. |
Extra inputs are not permitted | An unknown field — timeout_mins, notify_on_failure, cron. Check it against the field reference. |
jobs config must not contain secret-like keys (...): jobs[0].apiToken | A key containing password/secret/token/apikey anywhere in the document. Move it to init.yaml. |
command must not carry an inline credential for '--api-key' | A credential-bearing flag with a value in command. See below. |
command must not contain an inline credential | A value in command looks like a token or JWT regardless of the flag it follows. |
invalid email address: '[email protected], [email protected]' | Two addresses in one list item. Give each its own item. |
--jobs-file and --config-url are mutually exclusive | Both passed explicitly on one command line. Pick one. (An env-set REGSCALE_JOBS_URL plus an explicit --jobs-file is fine — the flag wins.) |
Remote config fetch failed (...); the served copy is the last known-good cache | The HTTPS config could not be fetched or did not validate. The scheduler is running on a cached copy — which may be stale. |
could not acquire the run-due lock: ... | The state directory is not writable (read-only mount, wrong UID, missing directory). |
Job <name> persisted last_run (...) is after the current clock (...) | The host clock moved backwards, or state was copied from a host in the future. The job will not fire until the clock passes the recorded occurrence. |
jobs config is too deeply nested | A pathologically nested document. Not something a hand-written file hits. |
"My command legitimately needs --api-key and now it cannot be scheduled."
That is the rule working as designed: jobs.yaml is meant to be safe to commit
or host in Blob Storage. Configure the credential in init.yaml (or its
REGSCALE_* environment variable) and drop the flag from the command — that is
how the same command already picks it up when you run it interactively. Flags
that merely look credential-ish are allowed: --token-url and --project_key
pass, --api-key and --client-secret do not.
Email
No mail is arriving. Alerting is off until both smtpServer and smtpFrom
are set. The process logs, once, Job <name> has notification recipients but SMTP is not configured (set smtpServer and smtpFrom in init.yaml). Also check:
- The job's
notify.on_failurelist is not empty (on_successis separate, and
empty by default — successes are silent unless you ask). - SMTP settings are read once per process: restart the daemon after
changing them. - A delivery failure never fails the run, so the only trace is
Failed to send job notification for <name>in the scheduler log.
TLS errors to an internal relay. The relay's certificate is verified. Point
customCaCert at your internal CA bundle rather than turning TLS off — with
smtpUseTls: false the CLI also skips authentication rather than sending
credentials in cleartext, which most relays then reject.
Which runs send mail. FAILED and TIMEOUT → on_failure; SUCCESS →
on_success. This is identical for daemon, run-due, jobs run, and the TUI's
Run Now.
Logs, history, and disk
Where is everything? Under ~/.regscale, or REGSCALE_JOBS_STATE_DIR when
set: jobs_state.json (what ran), job_runs/<job>.jsonl (history),
job_logs/<job>-<run-id>.log (per-run output).
Why does jobs history show <redacted>? Recorded argv is scrubbed before
it is written, so a value following a credential-looking flag is masked in
history and in the TUI. The real command received the real value; only the
recording is masked.
Why is my log truncated in the middle? A single run's log is capped at 5 MB.
Past the cap you get the first 5 MB, a ... N bytes truncated ... marker, and
the last 64 KB — both ends of a noisy run survive. If you need everything, make
the job quieter or ship its output elsewhere.
How long is history kept? The most recent 200 runs per job. Older records
are pruned and their log files are deleted with them. Archive off-box if you
have a retention requirement.
Can I delete a log file? Yes — nothing depends on it after the run. The
history record keeps the path and will say the log is unavailable. Do not delete
jobs_state.json unless you want every job to look due.
Why is the state directory 0700? It holds run output that may contain
sensitive data, so the directory and its files are created owner-only. Run the
scheduler as a dedicated account and keep it that way.
Questions about behavior
Which Python/CLI version does a job use? The same interpreter as the
scheduler — jobs run as <scheduler's python> -m regscale.regscale <args>. So
the CLI does not need to be on PATH, and you cannot have one job on a
different version. Upgrade the scheduler to upgrade the jobs.
Do jobs run in parallel? No. Within a pass they run one at a time, in file
order. Ten jobs all scheduled at 0 2 * * * run back to back, so stagger
anything long.
Do I need to restart after editing jobs.yaml? No. The config is re-read at
the start of every pass, so a change lands within one interval. Restart only
after changing SMTP settings.
Does jobs run affect the schedule? No. It records a run with
trigger=manual and sends email, but does not touch the occurrence state, so
the next scheduled run still happens. It also ignores enabled, which makes it
the way to test a disabled job.
Is it safe to run run-due from cron every minute? Yes. It computes what is
due, and overlapping invocations are blocked by the lock. The cost is process
startup, not duplicate work.
What happens if the scheduler is down for a week? Each due job runs once
when it comes back, for its most recent occurrence, with
Job <name> missed N occurrence(s); running once for the latest in the log. No
backfill of every missed occurrence.
Can I schedule against two RegScale instances? Not from one scheduler — the
environment is process-wide. Run a second scheduler with its own
REGSCALE_CONFIG_FILE, its own jobs.yaml, and its own
REGSCALE_JOBS_STATE_DIR. Sharing a state directory between two schedulers
with different configs is the one arrangement to avoid.
Can I run two schedulers for redundancy? They will not double-fire — the
lock and occurrence state prevent that if they share a state directory — but
there is no failover benefit and twice the log noise. Prefer one scheduler your
platform restarts.
Is it safe to edit jobs.yaml while the daemon is running? Yes. It is read,
never written, by the scheduler, and a half-written file simply fails to parse
and is skipped with a warning. Write it atomically (write-then-rename) if you
generate it from automation.
Deleting a job deletes its history? No. History is keyed by name and
survives, which is also why reusing a deleted job's name mixes old and new runs
in one history.
Things it does not do
Save yourself the investigation:
- No shell. No pipes,
&&, redirection, globs, or$VARexpansion in
command. Two steps means two jobs. - No dependencies between jobs. No "run B after A succeeds". Order them by
clock time, or drive the sequence from an external orchestrator that calls
regscale jobs run <name>and checks the exit code. - No retries. A failed run is failed; the next occurrence is the next
attempt. - No backfill of missed occurrences beyond one catch-up run.
- No per-job credentials or per-job environment. Everything inherits the
scheduler's environment. - No sub-minute schedules, and no seconds field.
- No health endpoint to probe. Alert on run outcomes and failure email
instead. - No central UI.
jobs.yamlis per-scheduler; the TUI reads the local file.
What to gather before asking for help
Have these ready and most questions answer themselves:
regscale --version
regscale jobs validate # exit code and message
regscale jobs list # full table
regscale jobs history <job> --limit 10
tail -100 "<state dir>/job_logs/<job>-<run-id>.log"
Plus how the scheduler is started (daemon or run-due, and by what), the
value of REGSCALE_JOBS_STATE_DIR, and the scheduler's own log around the time
in question. Redact tokens before sharing anything — the CLI scrubs recognizable
credential shapes from logs, but it cannot recognize every one.
Still stuck: the engineering reference for this feature is
docs/scheduled-jobs.md in the regscale-cli repository, and RegScale Support
can take it from there.
Updated about 8 hours ago
