There was no way to tell "running, nothing to pull" from "not running". The
no-change path logs nothing by design, and PM2 reports a cron-restart process
as `stopped` with ↺ 0 even while firing on schedule — I verified that against
PM2 7.0.4: a one-minute cron fired four times without the counter moving once.
scripts/.last-check is now rewritten on every run with the outcome. It is a
single overwritten line, so it needs no trimming.
The outcome is set by each exit path and written once in `finally`, rather than
calling record at each return — the first version of this did the latter and
already missed the build-failure path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Capped at 256 KB, trimmed back to the newest 500 lines. Done once per run
before anything is written: the no-change path logs nothing, so that is the
only point at which the file can have grown since the last run. Failures are
swallowed - housekeeping must never be the reason an update does not land.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Instances were started by hand and updated by hand, so they drifted behind
master silently. Now: PM2 supervises the dashboard and the clicker, a logon
task brings them up, and a 5-minute task pulls, rebuilds and restarts when
master moves.
Restarting on every push is only safe because the scheduler now survives it.
It was pure in-memory state (_global.__autoTrader), so any restart silently
stopped automated trading with the dashboard simply showing it as off. It now
mirrors running/action/symbol/stopAfterAll to the settings table, and
resumeSchedulerIfPersisted() picks it back up from the getClients() bootstrap.
No sync-wait was needed there: tick() already skips while a client reports
!syncComplete and while any account holds a position.
A failed build is never deployed — the build runs before anything restarts, so
a broken push leaves the previous build serving.
start-all and update-check both warm the app with a request afterwards. That is
load-bearing: getClients() is lazily bootstrapped, so until something makes an
HTTP request the Tradovate clients, the reporter and the resumed schedule never
start. That was already true of manual restarts.
Logic lives in Node so a macOS or Linux port only needs an equivalent of
install-autostart.ps1. Python deps are hash-guarded, so the common path is one
hash and one import with no network, and failure is non-fatal since only the
clicker needs them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>