Skip to content

fix: correlate execute() by id, isolate event hooks, and stop counting manual runs toward maxExecutions#607

Merged
merencia merged 2 commits into
mainfrom
fix/execution-path-correctness
Jul 2, 2026
Merged

fix: correlate execute() by id, isolate event hooks, and stop counting manual runs toward maxExecutions#607
merencia merged 2 commits into
mainfrom
fix/execution-path-correctness

Conversation

@merencia

@merencia merencia commented Jul 2, 2026

Copy link
Copy Markdown
Member

Summary

Three related bugs in the runner/task execution path:

  • execute() could resolve with the wrong run's result. It matched on the generic execution:finished/execution:failed events without checking which execution actually settled, so a scheduled fire completing while a manual execute() call was still in flight could resolve the manual call with the scheduled run's result. Both InlineScheduledTask.execute() and BackgroundScheduledTask.execute() now correlate by the execution id they invoked. For background tasks, the id is generated in the parent, sent through the task:execute IPC command, threaded into the daemon's InlineScheduledTask.execute(id) call, and echoed back on the forwarded events.

  • A throwing/rejecting user hook could reclassify a successful run as failed. onFinished was called inside the task's own try block, so a synchronously-throwing execution:finished listener made the runner treat a successful execution as failed (emitting execution:failed after execution:finished, and overwriting lastRun()). An async listener that rejected became an unhandled rejection. The runner now calls onFinished outside the task's try block, in its own try/catch routed to the internal error fallback logger, and the inline task's event emission catches both a throwing and a rejecting listener without affecting the task's own outcome.

  • Manual execute() silently drifted toward maxExecutions. It incremented runCount on every call but never checked maxExecutions, so repeated manual calls could push the count past the limit. Per decision: maxExecutions (and runsLeft()) now only counts scheduled fires. Manual execute() never counts toward it and is never blocked by it, even after the task has auto-stopped from maxExecutions.

Test plan

  • npm run build
  • npx tsc --noEmit
  • npm run lint
  • npm test (741 tests, 100% statements/branches/functions/lines)
  • TDD: each new test observed failing for the right reason before the fix (race resolves with the wrong result, hook throw reclassifies success as failure, unhandled rejection from async hook, runCount incrementing/gating on manual execute()), then passing after

merencia added 2 commits July 2, 2026 16:32
…g manual runs toward maxExecutions

Three related bugs in the execution path:

- execute() could resolve with a concurrent scheduled run's result instead
  of its own, because it matched on the generic execution:finished/failed
  events without checking which execution settled. Both the inline and
  background tasks now correlate by the execution id they invoked; for
  background tasks the id is echoed through the task:execute IPC command
  and the resulting events.

- A user execution:finished listener that threw synchronously could
  reclassify a successful run as failed (emitting execution:failed after
  execution:finished and overwriting lastRun()), and a rejecting async
  listener became an unhandled rejection. The runner now isolates the
  onFinished hook from the task's own try block, and event emission on the
  inline task catches both throwing and rejecting listeners.

- Manual execute() incremented runCount without ever checking
  maxExecutions, so repeated manual calls could silently stop the task.
  maxExecutions now counts and is enforced only for scheduled fires;
  execute() never counts toward it and always runs, even after the task
  has auto-stopped.
…rrectness

# Conflicts:
#	src/tasks/background-scheduled-task/daemon.ts
@merencia
merencia merged commit 0f039a9 into main Jul 2, 2026
8 checks passed
@merencia
merencia deleted the fix/execution-path-correctness branch July 2, 2026 19:47
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.

1 participant