Skip to content

Run checks at NSClient++ startup - #1407

Merged
mickem merged 1 commit into
mainfrom
scheduler-run-on-startup
Aug 14, 2026
Merged

Run checks at NSClient++ startup#1407
mickem merged 1 commit into
mainfrom
scheduler-run-on-startup

Conversation

@mickem

@mickem mickem commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Closes #392.

A schedule only reports for the first time once its interval (or the next matching cron time) has elapsed, so after a reboot or a configuration change the monitoring server keeps the old result for as long as the interval — hours for exactly the checks whose status is most likely to have changed (uptime, pending updates, …).

What this adds

[/settings/scheduler/schedules/uptime]
interval = 1h
channel = NSCA
command = check_uptime
run on startup = true

The command runs once as soon as the agent is up, then continues on its normal schedule from that run.

  • run on startup on the default schedule turns it on for every schedule that does not override it (the global switch the issue asked for); an explicit run on startup = false on a named schedule wins.
  • [/settings/scheduler] startup window = 30s spreads the startup runs evenly for installs with many schedules. The default (0s) runs them all immediately.
  • Startup runs also happen after a configuration reload, so a schedule you just changed reports its new status right away.

How it works

Startup runs fire from the plugin start hook ("on_start": trueScheduler::startModule), which the core calls from post_start_plugins() once every plugin is loaded. Firing them during module load would query commands that later-loading modules have not registered yet and submit bogus Command was not found UNKNOWNs. A reload never gets a second start hook and is not exposed to that ordering problem, so loadModuleEx fires them directly once the module has started.

The scheduler learned to register a task without queueing its first run (add_task(..., schedule_first_run = false) + run_now). This matters: every execution queues the following one, so leaving the normal first-run instance in place next to the startup instance would make the task fire twice per interval for the lifetime of the process. Startup instances are also exempt from the "ran N seconds too late" error — they are all queued for the same instant by design — while the watchdog still sees the lag and scales the thread pool.

Bugs found along the way (fixed here)

  • Reloading the Scheduler left the tasks and their queued instances from before the reload in place alongside the newly added ones, so every schedule ran twice after a reload.
  • A plugin loaded into an already running agent (load_single_plugin, i.e. the REST module-load path) never got its start hook called. This also affected LUAScript's on-start scripts.
  • schedule_object's copy constructor did not copy id, so the "Adding scheduled item" log line printed an indeterminate value (intervalcheck[1634497895]).

Tests

  • schedules_handler_test.cpp — default value, template copy, to_string, and the queue-count invariants (deferred until fired, one instance per startup task, normal tasks untouched).
  • simple_scheduler_test.cpp — deferred first run for both interval and cron tasks, run_now including delay handling and unknown ids, and queue clearing.
  • tests/scheduler-run-on-startup.test.ts — new Docker-free integration suite driving a local carbon listener through GraphiteClient. Every schedule uses a 1h interval, so any result can only have come from a startup run: explicit flag, inheritance from default, run on startup = false staying silent, exactly-once, and a re-run after a REST-triggered reload.

ctest -R "sched|plugin|settings" passes 12/12; activate-module and rest-modules-v1/v2 pass against the load_single_plugin change.

Note: the reload case needs a build with a web backend (the mongoose-less build tree has no WEBServer module).

🤖 Generated with Claude Code

A schedule only reports for the first time once its interval (or the next
matching cron time) has elapsed, so after a reboot or a configuration change
the monitoring server keeps the old result for as long as the interval - hours
for the checks where the status is most likely to have changed. Fixes #392.

Schedules can now set `run on startup` to run the command once as soon as the
agent is up, after which the normal schedule continues from that run. Setting
it on the `default` schedule turns it on for every schedule which does not
override it, and `startup window` spreads the startup runs out for installs
with many schedules.

The startup runs fire from the plugin start hook, which the core calls once
every plugin is loaded - running them during module load would query commands
that later modules have not registered yet. A reload gets no second start hook
and is not exposed to that ordering problem, so it fires them directly. To keep
a startup schedule from ending up with two independent chains of queued
instances (which would make it run twice per interval forever), the scheduler
learned to register a task without queueing its first run.

Along the way:

- Reloading the Scheduler left the tasks and queued instances from before the
  reload in place alongside the newly added ones, so every schedule ran twice
  after a reload.
- A plugin loaded into an already running agent never got its start hook
  called, which also affected LUAScript's on-start scripts.
- The schedule object's copy constructor did not copy its id, so the "Adding
  scheduled item" log line printed a random number.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Michael Medin <michael@medin.name>
@mickem
mickem merged commit f611392 into main Aug 14, 2026
29 of 30 checks passed
@mickem
mickem deleted the scheduler-run-on-startup branch August 14, 2026 09:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Run checks at NSClient++ startup [Enhancement request]

1 participant