openagi-cron-spine-hardening · git:20260805.a4c6544 · 2026-08-05 · sha256 79c15f6e7ae54596
openagi-cron-spine-hardening git:20260805.a4c6544A
Immutable. This exact content is served forever at /api/v1/blob/79c15f6e7ae54596.
--- name: openagi-cron-spine-hardening description: Use when changing openAGI cron scheduling, ABI runtime ticks, cron job timeout behavior, file-backed cron markers, or hosted-interface cron timeout events. --- # openAGI Cron Spine Hardening ## When to use Use this skill when work touches `src/cron-scheduler.js`, `src/file-backed-cron-scheduler.js`, `src/abi-runtime.js` tick behavior, `src/hosted-interface.js` cron events, or tests named `test/cron-overlap-guard.test.js`, `test/cron-job-timeout.test.js`, or `test/cron-interruption.test.js`. Use it for failures containing `TIMEOUT_MS`, `cron-job-timeout`, `cron tick still in flight`, `Promise resolution is still pending but the event loop has already resolved`, `marker on disk while the handler runs`, or `consumeInterruption`. ## Procedure 1. Preserve the three cron-spine properties as one contract. - `AbiRuntime.tick()` skips overlapping ticks instead of stacking them. - `CronScheduler.runDue()` races each job handler against a per-job timeout so one hung handler cannot stall later jobs. - `FileBackedCronScheduler` persists a running marker while a handler is executing and exposes a stale marker once through `consumeInterruption()` after a daemon restart. 2. Keep timeout constants and tests aligned. - `src/cron-scheduler.js` exports `TIMEOUT_MS` and `resolveJobTimeoutMs`. - `TIMEOUT_MS` is `10 * 60 * 1000`. - `resolveJobTimeoutMs` reads `OPENAGI_CRON_JOB_TIMEOUT_MS` and accepts only finite values greater than zero. - Tests import these symbols directly from `../src/cron-scheduler.js`. 3. Do not unref the timeout timer that resolves the handler race. - The timer inside the `Promise.race` timeout path stays refed. - If `timer.unref?.()` is added there, Node can end the subtest before the timeout fires and cancel pending tests. - Clear the timer in `finally` so the timer does not outlive the job fire. 4. Thread timeout options through file-backed scheduling. - `FileBackedCronScheduler.runDue(handler, now, options)` forwards `options` to `super.runDue(handler, now, options)`. - Saving remains tied to actual results so job state and running markers persist after fires. 5. Wire timeout visibility through the runtime and hosted interface. - `AbiRuntime` passes `onTimeout` to `cron.runDue` and emits `cron-job-timeout` on `this.events` with `at`, `jobId`, `jobName`, and `timeoutMs`. - `hosted-interface.js` broadcasts `cron-job-timeout` alongside existing cron events. 6. Verify focused cron tests before the broad suite. - Run `node --test test/cron-overlap-guard.test.js` after tick overlap changes. - Run `node --test test/cron-job-timeout.test.js` after timeout or event-bus changes. - Run `node --test test/cron-interruption.test.js` after file-backed running marker changes. - Run `npm test` when `src/abi-runtime.js`, `src/index.js`, or hosted runtime wiring changed. ## Failure signatures - `# SyntaxError: The requested module '../src/cron-scheduler.js' does not provide an export named 'TIMEOUT_MS'`: export `TIMEOUT_MS` from `src/cron-scheduler.js`; do not change the test to hide the public contract. - `error: 'Promise resolution is still pending but the event loop has already resolved'` in `test/cron-job-timeout.test.js` or `test/cron-overlap-guard.test.js`: the timeout timer that resolves the race was probably unrefed or the test left a never-resolving promise without a refed timeout path. - `not ok ... runDue persists the running marker during the handler and clears it after` with `error: 'marker on disk while the handler runs'`: inspect `FileBackedCronScheduler.runDue`; it must persist `running` before the handler awaits and clear/save it after completion. - `not ok ... consumeInterruption returns the stale marker once and clears it`: `load()` must stash the persisted `running` marker into `_interrupted`, reset `running`, and `consumeInterruption()` must clear the stale marker after returning it once. - Full `npm test` fails later with `error: 'database is locked'` at `SessionIndex.init` after cron timeout tests: isolate the cron timeout test first, then apply the SQLite isolation skill only if the focused cron tests pass. ## Verification - `node --test test/cron-overlap-guard.test.js` passes with the overlapping tick returning `[]` and the skipped-tick warning logged once per streak. - `node --test test/cron-job-timeout.test.js` passes with timeout constants exported, hung jobs recorded as failed, `nextRunAt` advanced, and `cron-job-timeout` emitted. - `node --test test/cron-interruption.test.js` passes with running markers persisted during a handler and consumed once after reload. - `npm test` has no cron-related cancelled subtests and no cron-induced `SessionIndex.init` SQLite lock cascade.