fix: surface real background task start errors and stop orphaned daemons by merencia · Pull Request #535 · node-cron/node-cron · GitHub
Skip to content

fix: surface real background task start errors and stop orphaned daemons#535

Merged
merencia merged 1 commit into
mainfrom
fix/background-task-start-errors
Jun 16, 2026
Merged

fix: surface real background task start errors and stop orphaned daemons#535
merencia merged 1 commit into
mainfrom
fix/background-task-start-errors

Conversation

@merencia

Copy link
Copy Markdown
Member

Closes #484

Problem

Starting a background task forks a daemon that imports the task file. Three problems made failures opaque and unsafe, reproduced against the real build on Node 24:

  1. Real load error hidden. A failed import (missing file; a runtime that cannot run the file; or unsupported TypeScript syntax in Node's strip-only mode, e.g. enum/namespace/decorators) crashed the daemon. The parent only saw Start operation timed out or node-cron daemon exited with code 1; the actual cause (TypeScript enum is not supported in strip-only mode, Cannot find module ...) only reached the child's stderr. This matches @rudewalt (Node 24) and @merencia's loader hypothesis.
  2. Hard-coded 5s start timeout. A task file that loads slowly (transpile, large dependency graph) could not be accommodated. This matches the OP and @jsa4000.
  3. Orphaned daemons. On a failed/timed-out start the daemon was never killed, so a daemon that finished loading after the timeout kept running the schedule on its own. With retries/nodemon this produced "so many task been executed" (@jsa4000).

Fix

  • The daemon catches load/start failures and forwards the real error to the parent (daemon:error); start() rejects with the actual cause.
  • New startTimeout option (ms, default 5000) and an actionable timeout message.
  • Any failed start now kills the daemon and clears the fork (no orphans/duplicates).
  • schedule() routes a background task's auto-start failure to the configured logger instead of leaving an unhandled rejection (the OP's UnhandledRejection).

Tests

Deterministic, written first (red), then made green:

  • daemon forwards the real load error instead of crashing;
  • parent rejects start() with the real cause on daemon:error;
  • parent kills the daemon and clears the fork on a failed start, and on a timeout (no orphan);
  • startTimeout is honoured and the timeout message is actionable;
  • schedule() logs a background start failure instead of throwing unhandled.

End-to-end verified against the built dist (enum .ts -> real error surfaced; slow load + short startTimeout -> daemon killed, no orphan executions).

Honest scope note

node-cron cannot transpile TypeScript for you: on a strip-only runtime an enum still will not run. This change makes the failure diagnosable (you see the exact reason and how to fix it: --experimental-transform-types, tsx/ts-node, or a compiled .js) and stops the orphaned executions. It does not magically run unsupported syntax.

Starting a background task forks a daemon that imports the task file. Three
problems made failures opaque and unsafe (#484):

- A failed import (missing file, a runtime that cannot run the file, or
  unsupported TypeScript syntax in Node's strip-only mode, e.g. enums) crashed
  the daemon and the parent only saw "Start operation timed out" or an opaque
  exit code; the real cause went to the child's stderr.
- The start timeout was a hard-coded 5s. A task file that loads slowly
  (transpile, large graph) could not be accommodated.
- On a failed/timed-out start the daemon was never killed, so a daemon that
  finished loading late kept running the schedule on its own, causing orphaned
  and duplicate executions.

Fixes:
- The daemon catches load/start failures and forwards the real error to the
  parent (daemon:error), which rejects start() with the actual cause.
- New `startTimeout` option (ms, default 5000) plus an actionable timeout
  message.
- Any failed start now kills the daemon and clears the fork (no orphans).
- schedule() routes a background task's auto-start failure to the logger
  instead of leaving an unhandled rejection.
@merencia merencia merged commit da270d2 into main Jun 16, 2026
3 checks passed
@merencia merencia deleted the fix/background-task-start-errors branch June 16, 2026 20:37
@merencia merencia mentioned this pull request Jun 17, 2026
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.

Problem executing a background task using typescript ?

1 participant