Skip to content

bug(flow schedule): CLI writes keboola.scheduler config but never activates it via Scheduler Service (cron never fires) #479

Description

@devin-ai-integration

Problem

kbagent flow schedule (and flow schedule-remove) only writes/deletes the keboola.scheduler Storage configuration — it never calls the Scheduler Service API to actually register (activate) or unregister the schedule on the backend.

As a result, a schedule created via the CLI shows up as enabled in the config body, but the Scheduler Service does not know about it, so the cron trigger is never actually fired. The schedule only becomes "live" once something else calls the Scheduler API for that config (e.g. opening/saving the schedule in the UI).

Evidence

CLI — only the Storage config write happens

services/flow_service.py set_flow_schedule() creates/updates a keboola.scheduler config via the Storage API and stops there:

configuration = {
    "schedule": {"cronTab": cron_tab, "timezone": timezone,
                 "state": "enabled" if enabled else "disabled"},
    "target": {"mode": "run", "componentId": FLOW_COMPONENT_ID,
               "configurationId": config_id},
}
# ... upsert ...
result = client.create_config(component_id=SCHEDULER_COMPONENT_ID, ...)   # or update_config
# <-- returns here; no Scheduler Service call

client.create_config() / update_config() only hit POST/PUT /v2/storage/.../components/keboola.scheduler/configs. A grep across all HTTP clients (client.py, manage_client.py, ...) finds no Scheduler Service endpoint (/schedules, activation, etc.) — the only activate in the codebase is for dev branches.

remove_flow_schedule() has the mirror problem: it deletes the Storage config but never calls the Scheduler Service to unregister it.

The service docstring even states this explicitly (schedule_service.py):

Schedules are stored as regular Storage API configurations of the keboola.scheduler component ... No separate Scheduler Service HTTP client is required.

That assumption is incorrect for activation — it holds for read/audit, but not for making a schedule actually run.

UI (keboola/ui) — does the two-step flow correctly

apps/kbc-ui/src/scripts/modules/scheduler/actions.ts:

export const createAndActivateScheduler = (componentId, configurationId, { cronTab, timezone = 'UTC' }) => {
  return InstalledComponentsActionCreators.createConfiguration(KEBOOLA_SCHEDULER, {
    name: `Scheduler for ${configurationId}`,
    configuration: JSON.stringify({
      schedule: { cronTab, timezone, state: SCHEDULE_STATE.ENABLED },
      target: { componentId, configurationId, mode: 'run' },
    }),
  }).then((response) => applySchedulerChanges(response.id)); // <-- step 2
};

apps/kbc-ui/src/scripts/modules/scheduler/api.ts:

applySchedulerChanges(configurationId) {
  return createRequest('POST', 'schedules')          // POST {scheduler_service_url}/schedules
    .send({ configurationId })
    .promise()
    .then((response) => response.body);
},
removeSchedule(configurationId) {
  return createRequest('DELETE', `configurations/${configurationId}`) // DELETE {scheduler_service_url}/configurations/{id}
    .promise()
    .then((response) => response.body);
},

So the correct sequence is:

  1. Create/update the keboola.scheduler Storage config with state: enabledCLI does this.
  2. POST {scheduler_service_url}/schedules with { configurationId } so the Scheduler Service loads the config and registers the trigger — CLI does NOT do this.

updateSchedulerapplySchedulerChanges, and removeSchedulerDELETE /configurations/{id} follow the same pattern. Activation/removal in the UI is also gated behind canActivateSchedule(sapiToken) (modules/admin/privileges.ts), i.e. the token must have the privilege.

Impact

  • Schedules created with kbagent flow schedule do not fire until re-saved via the UI (or the Scheduler API is called some other way). This is silent — the command reports success and the config looks enabled.
  • kbagent flow schedule-remove deletes the Storage config but may leave a registered schedule on the Scheduler Service side (needs confirmation, but the API surface implies it).

Proposed fix

Add a Scheduler Service HTTP client (base URL from the services list, SERVICE_SCHEDULER; auth via X-StorageApi-Token) and call it from FlowService:

  • After create/update in set_flow_schedule(): POST {scheduler_service_url}/schedules with { "configurationId": <scheduler_config_id> }.
  • In remove_flow_schedule(): DELETE {scheduler_service_url}/configurations/{scheduler_config_id} before/after deleting the Storage config, mirroring the UI order.
  • Respect the token privilege the same way the UI does (canActivateSchedule) — degrade gracefully / warn when the token can't activate, rather than silently no-op.

Open questions

  • Confirm exact Scheduler Service endpoints/semantics against the current backend (the UI uses POST /schedules for create+update-apply and DELETE /configurations/{id} for removal).
  • Decide behaviour when the token lacks the activate privilege (warn + leave config written, matching UI's null-return, vs hard error).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions