From 80051e2450fdc097e13f6242dcb1147e4e20a3ad Mon Sep 17 00:00:00 2001 From: debidong <1953531014@qq.com> Date: Thu, 2 Jul 2026 11:21:26 +0800 Subject: [PATCH] fix: document automation schedules as UTC --- internal/cli/automation.go | 36 +++++++++++++++--------- internal/cli/automation_test.go | 22 +++++++++++++++ skills/flashduty/reference/automation.md | 18 ++++++------ 3 files changed, 54 insertions(+), 22 deletions(-) diff --git a/internal/cli/automation.go b/internal/cli/automation.go index 7ed9f00..98856f2 100644 --- a/internal/cli/automation.go +++ b/internal/cli/automation.go @@ -14,6 +14,7 @@ import ( ) const automationHTTPPostOnlyCron = "0 0 * * *" +const automationUTCNote = "Convert local wall-clock requests to UTC before passing --at or --cron-expr." func newAutomationCmd() *cobra.Command { cmd := &cobra.Command{ @@ -63,11 +64,14 @@ By default the rule is enabled. Use --disabled only when the user explicitly asks to create it disabled. team_id=0 means personal scope; --team-id >0 creates the rule under that team. The scope is immutable after creation. -Schedule helpers build a 5-field cron expression. Use --cron-expr for exact -minute-level control. For HTTP POST-only rules, pass --http-post-trigger without -a schedule; the CLI sends a valid placeholder cron and disables the schedule trigger.`, "Automations", "RuleWriteCreate"), - Example: ` flashduty automation create --name "Daily SRE brief" --schedule daily --at 09:30 --prompt "Summarize yesterday's incidents" - flashduty automation create --name "Weekly noise review" --team-id 123 --schedule weekly --weekday mon --at 10:00 --prompt-file ./prompt.md + Schedule helpers build a 5-field UTC cron expression. --at and --cron-expr are + interpreted in UTC, not the caller's local timezone. Convert local wall-clock + requests to UTC before passing --at or --cron-expr. + + For HTTP POST-only rules, pass --http-post-trigger without a schedule; the CLI + sends a valid placeholder cron and disables the schedule trigger.`, "Automations", "RuleWriteCreate"), + Example: ` flashduty automation create --name "Daily SRE brief" --schedule daily --at 01:30 --prompt "Summarize yesterday's incidents" + flashduty automation create --name "Weekly noise review" --team-id 123 --schedule weekly --weekday mon --at 02:00 --prompt-file ./prompt.md flashduty automation create --name "Webhook triage" --http-post-trigger --prompt "Handle the posted payload"`, RunE: func(cmd *cobra.Command, args []string) error { return runCommand(cmd, args, func(ctx *RunContext) error { @@ -111,10 +115,10 @@ a schedule; the CLI sends a valid placeholder cron and disables the schedule tri cmd.Flags().StringVar(&name, "name", "", "Automation name") cmd.Flags().Int64Var(&teamID, "team-id", 0, "Scope team ID; 0 means personal scope") - cmd.Flags().StringVar(&schedule, "schedule", "", "Schedule helper: hourly, daily, weekly, or cron") - cmd.Flags().StringVar(&at, "at", "", "Wall-clock time in HH:MM; for hourly schedules, only the minute is used") + cmd.Flags().StringVar(&schedule, "schedule", "", "UTC schedule helper: hourly, daily, weekly, or cron") + cmd.Flags().StringVar(&at, "at", "", "UTC time in HH:MM; for hourly schedules, only the minute is used. "+automationUTCNote) cmd.Flags().StringVar(&weekday, "weekday", "", "Weekday for weekly schedules: sun, mon, tue, wed, thu, fri, sat, or 0-7") - cmd.Flags().StringVar(&cronExpr, "cron-expr", "", "Exact 5-field cron expression; overrides --schedule helpers") + cmd.Flags().StringVar(&cronExpr, "cron-expr", "", "Exact 5-field UTC cron expression; overrides --schedule helpers. "+automationUTCNote) cmd.Flags().BoolVar(&disabled, "disabled", false, "Create the Automation disabled") cmd.Flags().BoolVar(&scheduleEnabled, "schedule-enabled", true, "Whether the schedule trigger is enabled") cmd.Flags().BoolVar(&httpPostTrigger, "http-post-trigger", false, "Create and enable an HTTP POST trigger") @@ -218,9 +222,13 @@ func newAutomationUpdateCmd() *cobra.Command { Short: "Update an Automation", Long: curatedLong(`Update mutable fields on an Automation rule. -The personal/team scope is intentionally not exposed here. Scope is immutable -after creation; create a new Automation if the target person/team scope needs to change.`, "Automations", "RuleWriteUpdate"), - Example: ` flashduty automation update auto_123 --name "Daily brief v2" --cron-expr "15 9 * * *" + The personal/team scope is intentionally not exposed here. Scope is immutable + after creation; create a new Automation if the target person/team scope needs to change. + + Schedule helpers build a 5-field UTC cron expression. --at and --cron-expr are + interpreted in UTC, not the caller's local timezone. Convert local wall-clock + requests to UTC before passing --at or --cron-expr.`, "Automations", "RuleWriteUpdate"), + Example: ` flashduty automation update auto_123 --name "Daily brief v2" --cron-expr "15 1 * * *" flashduty automation update auto_123 --disable flashduty automation update auto_123 --enable-http-post-trigger --rotate-http-post-token`, Args: requireExactArg("rule_id"), @@ -311,10 +319,10 @@ after creation; create a new Automation if the target person/team scope needs to } cmd.Flags().StringVar(&name, "name", "", "New Automation name") - cmd.Flags().StringVar(&schedule, "schedule", "", "Schedule helper: hourly, daily, weekly, or cron") - cmd.Flags().StringVar(&at, "at", "", "Wall-clock time in HH:MM; for hourly schedules, only the minute is used") + cmd.Flags().StringVar(&schedule, "schedule", "", "UTC schedule helper: hourly, daily, weekly, or cron") + cmd.Flags().StringVar(&at, "at", "", "UTC time in HH:MM; for hourly schedules, only the minute is used. "+automationUTCNote) cmd.Flags().StringVar(&weekday, "weekday", "", "Weekday for weekly schedules: sun, mon, tue, wed, thu, fri, sat, or 0-7") - cmd.Flags().StringVar(&cronExpr, "cron-expr", "", "Exact 5-field cron expression; overrides --schedule helpers") + cmd.Flags().StringVar(&cronExpr, "cron-expr", "", "Exact 5-field UTC cron expression; overrides --schedule helpers. "+automationUTCNote) cmd.Flags().BoolVar(&enableRule, "enable", false, "Enable the Automation") cmd.Flags().BoolVar(&disableRule, "disable", false, "Disable the Automation") cmd.Flags().BoolVar(&enableSchedule, "enable-schedule", false, "Enable the schedule trigger") diff --git a/internal/cli/automation_test.go b/internal/cli/automation_test.go index 889cfb6..82d53c1 100644 --- a/internal/cli/automation_test.go +++ b/internal/cli/automation_test.go @@ -34,6 +34,28 @@ func TestAutomationCreateDailyDefaultsEnabled(t *testing.T) { assertBody(t, stub.lastBody, "prompt", "Summarize yesterday's incidents") } +func TestAutomationScheduleHelpDocumentsUTC(t *testing.T) { + saveAndResetGlobals(t) + + for _, args := range [][]string{ + {"automation", "create", "--help"}, + {"automation", "update", "auto_123", "--help"}, + } { + out, err := execCommand(args...) + if err != nil { + t.Fatalf("%v unexpected error: %v", args, err) + } + for _, want := range []string{ + "UTC", + "Convert local wall-clock requests to UTC before passing --at or --cron-expr.", + } { + if !strings.Contains(out, want) { + t.Fatalf("%v help missing %q\n%s", args, want, out) + } + } + } +} + func TestAutomationCreateHTTPPostOnly(t *testing.T) { saveAndResetGlobals(t) stub := newGFStub(t) diff --git a/skills/flashduty/reference/automation.md b/skills/flashduty/reference/automation.md index 49be1ad..c173923 100644 --- a/skills/flashduty/reference/automation.md +++ b/skills/flashduty/reference/automation.md @@ -29,12 +29,14 @@ Prereq: `SKILL.md` read. Automations create AI SRE sessions on a schedule or thr ## Scheduling - Default create behavior: enabled immediately. Use `--disabled` only if the user asks for a disabled Automation. -- No timezone flag is exposed by the current API. Build the requested wall-clock schedule in the account/customer timezone context. +- No timezone flag is exposed by the current API. Automation schedules are stored and sent as UTC cron. +- If the user asks for a local wall-clock schedule, first identify the intended timezone from the session context, runner `date`, or the user's wording. Convert that local time to UTC before calling the CLI. If the timezone is unclear, ask before creating or updating the schedule. - Helper schedules: - - `--schedule hourly --at 00:15` -> minute 15 of every hour. - - `--schedule daily --at 09:30` -> every day at 09:30. - - `--schedule weekly --weekday mon --at 10:00` -> every Monday at 10:00. -- For exact minute-level control, use `--cron-expr ' '`. + - `--schedule hourly --at 00:15` -> minute 15 of every UTC hour. + - `--schedule daily --at 01:30` -> every day at 01:30 UTC. + - `--schedule weekly --weekday mon --at 02:00` -> every Monday at 02:00 UTC. +- For exact minute-level control, use `--cron-expr ' '` in UTC. +- Example: Asia/Shanghai 11:00 is UTC 03:00, so use `--schedule daily --at 03:00` or `--cron-expr "0 3 * * *"`. - HTTP POST-only rule: pass `--http-post-trigger` without schedule flags. The CLI sends a placeholder cron and disables the schedule trigger. ## Hot flow - create from chat @@ -44,7 +46,7 @@ fduty automation create \ --name "Daily SRE brief" \ --team-id \ --schedule daily \ - --at 09:30 \ + --at 01:30 \ --prompt "Summarize yesterday's incidents, noisy alerts, and follow-up risks." \ --output-format toon ``` @@ -72,7 +74,7 @@ fduty automation update --rotate-http-post-token --output-format toon ```bash fduty automation create \ --name "Weekday 08:05 review" \ - --cron-expr "5 8 * * 1-5" \ + --cron-expr "5 0 * * 1-5" \ --prompt "Review open incidents and alert noise before the workday." \ --output-format toon ``` @@ -85,7 +87,7 @@ fduty automation get --output-format toon fduty automation runs --since 7d --output-format toon fduty automation update --disable --output-format toon -fduty automation update --enable --cron-expr "30 9 * * *" --output-format toon +fduty automation update --enable --cron-expr "30 1 * * *" --output-format toon fduty automation delete --force ```