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:
- Create/update the
keboola.scheduler Storage config with state: enabled — CLI does this.
POST {scheduler_service_url}/schedules with { configurationId } so the Scheduler Service loads the config and registers the trigger — CLI does NOT do this.
updateScheduler → applySchedulerChanges, and removeScheduler → DELETE /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).
Problem
kbagent flow schedule(andflow schedule-remove) only writes/deletes thekeboola.schedulerStorage 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
enabledin 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.pyset_flow_schedule()creates/updates akeboola.schedulerconfig via the Storage API and stops there:client.create_config()/update_config()only hitPOST/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 onlyactivatein 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):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:apps/kbc-ui/src/scripts/modules/scheduler/api.ts:So the correct sequence is:
keboola.schedulerStorage config withstate: enabled— CLI does this.POST {scheduler_service_url}/scheduleswith{ configurationId }so the Scheduler Service loads the config and registers the trigger — CLI does NOT do this.updateScheduler→applySchedulerChanges, andremoveScheduler→DELETE /configurations/{id}follow the same pattern. Activation/removal in the UI is also gated behindcanActivateSchedule(sapiToken)(modules/admin/privileges.ts), i.e. the token must have the privilege.Impact
kbagent flow scheduledo 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 looksenabled.kbagent flow schedule-removedeletes 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 viaX-StorageApi-Token) and call it fromFlowService:set_flow_schedule():POST {scheduler_service_url}/scheduleswith{ "configurationId": <scheduler_config_id> }.remove_flow_schedule():DELETE {scheduler_service_url}/configurations/{scheduler_config_id}before/after deleting the Storage config, mirroring the UI order.canActivateSchedule) — degrade gracefully / warn when the token can't activate, rather than silently no-op.Open questions
POST /schedulesfor create+update-apply andDELETE /configurations/{id}for removal).