fix: reject re-enabling an automation whose one-time run has passed by rudycelekli · Pull Request #70 · rociiu/talorys · GitHub
Skip to content

fix: reject re-enabling an automation whose one-time run has passed - #70

Closed
rudycelekli wants to merge 1 commit into
rociiu:mainfrom
rudycelekli:fix/reenable-finished-once-automation-20261018
Closed

rudycelekli wants to merge 1 commit into
rociiu:mainfrom
rudycelekli:fix/reenable-finished-once-automation-20261018

Conversation

@rudycelekli

@rudycelekli rudycelekli commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Problem

AutomationService.update only checks that the next run is in the future when schedule is part of the patch. Re-enabling an automation with { enabled: true } skips the check. For a parked one-time automation whose time has passed (which the existing "allowed while being disabled" test creates), the request succeeds with HTTP 200 but the automation is silently marked completed and enabled: false, so the toggle bounces back with no explanation. Creating or rescheduling the same schedule is rejected with "That time is in the past."

Evidence (base)

New test in apps/agent/test/automation-lifecycle.test.ts:

× is rejected instead of silently completing it, and leaves it paused
AssertionError: promise resolved "{ …(16) }" instead of rejecting
Tests  1 failed | 13 passed (14)

After: that file and test/api.test.ts pass (28 tests).

Change

In update, the past-time check now also runs when the patch turns a disabled automation on, validating the existing schedule when none is supplied. Same error message and ValidationError as create/reschedule.

Verified / not verified

Verified: automation-lifecycle and api tests, tsc --noEmit for apps/agent. Not verified: biome lint (not installed in my minimal workspace install) and the web UI toast for the new error. In a full apps/agent run, one unrelated semantic-search test (search to fetch ...) timed out at 34s on my loaded machine; I did not check it on base.

Updating only enabled:true skipped the past-time check that creating or
rescheduling applies, so a parked one-time automation was silently marked
completed and left disabled while the request succeeded.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@rociiu rociiu left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix looks right for the toggle path. Two smaller points inline. One more outside the diff:

The root cause is still reachable through other paths. The silent conversion to completed happens in arm() when computeNextRun returns null. rearmAll() (run after an import or a timezone change) can still enable a past one-time automation, and it then quietly flips to completed/disabled, the same symptom this PR describes. It may be worth surfacing this where arm() returns null, or validating on import, so every path is covered. A follow-up would be fine.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When re-enabling, this parses the stored schedule again. If a stored row no longer matches the current schema (legacy or imported data, or a schema tightened later), the update throws a raw ZodError instead of a ValidationError, and turning the automation back on fails with an unclear error. arm() uses existing.schedule without parsing, so passing existing.schedule straight to computeNextRun (and parsing only patch.schedule) avoids this.

if (patch.schedule !== undefined && (patch.enabled ?? existing.enabled)) {
const schedule = scheduleSpecSchema.parse(patch.schedule);
const enabling = patch.enabled === true && !existing.enabled;
if ((patch.schedule !== undefined || enabling) && (patch.enabled ?? existing.enabled)) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: when enabling is true, (patch.enabled ?? existing.enabled) is always true, and patch.schedule is parsed again in the repo.update call below. Computing the effective schedule once (patch.schedule ? scheduleSpecSchema.parse(patch.schedule) : existing.schedule) and reusing it for both the check and the update would make this easier to read.

@rociiu

rociiu commented Oct 22, 2026

Copy link
Copy Markdown
Owner

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.

2 participants