From df1fcc9f1c57722d599e74dfd4f6e84b5a9b6983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Kutryj?= Date: Fri, 19 Jun 2026 11:10:08 +0200 Subject: [PATCH 1/3] feat(task): support parameters on `task run` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `task run ` posted run_now with a nil body, so task parameters were silently dropped — parameterized scheduled tasks could not be triggered from the CLI. Add `--param KEY=VALUE` (repeatable), `--branch`, and `--pipeline-file`. The body is built only when an override is supplied, so the parameter-less default is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- cmd/task.go | 58 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/cmd/task.go b/cmd/task.go index 14c07f7..5d9d80c 100644 --- a/cmd/task.go +++ b/cmd/task.go @@ -20,8 +20,8 @@ var taskCmd = &cobra.Command{ var taskProjectFlag string var taskListCmd = &cobra.Command{ - Use: "list", - Short: "List scheduled tasks for a project", + Use: "list", + Short: "List scheduled tasks for a project", Example: ` sem-ai task list --project my-project`, RunE: func(cmd *cobra.Command, args []string) error { if !config.IsConfigured() { @@ -77,17 +77,51 @@ var taskShowCmd = &cobra.Command{ }, } +var ( + taskRunParamsFlag []string + taskRunBranchFlag string + taskRunFileFlag string +) + var taskRunCmd = &cobra.Command{ - Use: "run ", - Short: "Trigger a scheduled task to run now", - Args: cobra.ExactArgs(1), - Example: ` sem-ai task run `, + Use: "run ", + Short: "Trigger a scheduled task to run now", + Args: cobra.ExactArgs(1), + Example: ` sem-ai task run + sem-ai task run --param KEY=VALUE --param KEY2=VALUE2 + sem-ai task run --branch main --pipeline-file .semaphore/pipeline.yml --param KEY=VALUE`, RunE: func(cmd *cobra.Command, args []string) error { if !config.IsConfigured() { return fmt.Errorf("not configured — run 'sem-ai connect' first") } + + // Build a run_now body only when overrides are supplied; otherwise + // send nil to preserve the parameter-less default behaviour. + var body []byte + if len(taskRunParamsFlag) > 0 || taskRunBranchFlag != "" || taskRunFileFlag != "" { + reqBody := map[string]any{} + if taskRunBranchFlag != "" { + reqBody["branch"] = taskRunBranchFlag + } + if taskRunFileFlag != "" { + reqBody["pipeline_file"] = taskRunFileFlag + } + params := map[string]string{} + for _, p := range taskRunParamsFlag { + i := strings.IndexByte(p, '=') + if i <= 0 { + return fmt.Errorf("invalid --param %q: expected KEY=VALUE", p) + } + params[p[:i]] = p[i+1:] + } + if len(params) > 0 { + reqBody["parameters"] = params + } + body, _ = json.Marshal(reqBody) + } + c := client.New() - resp, err := c.PostAction("tasks", args[0], "run_now", nil) + resp, err := c.PostAction("tasks", args[0], "run_now", body) if err != nil { output.Error("api_error", err.Error(), 1) return err @@ -138,9 +172,9 @@ var ( ) var taskCreateCmd = &cobra.Command{ - Use: "create ", - Short: "Create a scheduled task (periodic job)", - Args: cobra.ExactArgs(1), + Use: "create ", + Short: "Create a scheduled task (periodic job)", + Args: cobra.ExactArgs(1), Example: ` sem-ai task create nightly-tests --project my-app --branch main --file .semaphore/nightly.yml --cron "0 2 * * *"`, RunE: func(cmd *cobra.Command, args []string) error { if !config.IsConfigured() { @@ -240,6 +274,10 @@ func init() { taskCreateCmd.Flags().StringVar(&taskCreateFileFlag, "file", ".semaphore/semaphore.yml", "pipeline YAML file") taskCreateCmd.Flags().StringVar(&taskCreateCronFlag, "cron", "", "cron expression for recurring tasks") + taskRunCmd.Flags().StringArrayVar(&taskRunParamsFlag, "param", nil, "task parameter as KEY=VALUE (repeatable)") + taskRunCmd.Flags().StringVar(&taskRunBranchFlag, "branch", "", "git ref the task pipeline runs on (e.g. master); defaults to the task's configured branch") + taskRunCmd.Flags().StringVar(&taskRunFileFlag, "pipeline-file", "", "pipeline YAML file the task runs; defaults to the task's configured file") + taskCmd.AddCommand(taskListCmd) taskCmd.AddCommand(taskShowCmd) taskCmd.AddCommand(taskRunCmd) From 59612169810ea8140ab9794fc7fd9ecea1e1ac4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Kutryj?= Date: Fri, 19 Jun 2026 11:10:08 +0200 Subject: [PATCH 2/3] fix(pipeline): send promote --param as top-level keys, not a nested array The POST /promotions endpoint maps every non-reserved top-level body key to a promotion env var whose value must be a string. Sending parameters nested as "parameters": [{name, value}] left the "parameters" key itself as a list-valued env var, so the server rejected it (EnvVariable value is invalid) and returned HTTP 500. Send each --param as a flat top-level body key instead, so it encodes as a string promotion env var. Co-Authored-By: Claude Opus 4.8 (1M context) --- cmd/pipeline.go | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/cmd/pipeline.go b/cmd/pipeline.go index 0c418d9..36ebfea 100644 --- a/cmd/pipeline.go +++ b/cmd/pipeline.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/url" + "strings" "github.com/semaphoreio/sem-ai/pkg/client" "github.com/semaphoreio/sem-ai/pkg/config" @@ -320,22 +321,14 @@ Safety: "override": promoteOverrideFlag, } - // Parse --param key=val pairs - if len(promoteParamsFlag) > 0 { - envVars := make([]map[string]string, 0, len(promoteParamsFlag)) - for _, p := range promoteParamsFlag { - for i := range p { - if p[i] == '=' { - envVars = append(envVars, map[string]string{ - "name": p[:i], - "value": p[i+1:], - }) - break - } - } - } - if len(envVars) > 0 { - reqBody["parameters"] = envVars + // Promotion parameters go as top-level body keys: the v1alpha + // /promotions endpoint maps every key except + // pipeline_id/name/override/request_token/switch_id/user_id to a + // promotion env var. A nested "parameters" array makes the server + // encode a list as an env-var string value and 500. + for _, p := range promoteParamsFlag { + if k, v, ok := strings.Cut(p, "="); ok { + reqBody[k] = v } } From cb48d864c4fe5cfa9072375b022a6c75ae8b9c5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Kutryj?= Date: Fri, 19 Jun 2026 11:10:08 +0200 Subject: [PATCH 3/3] docs(skills): document parameterized task run + promote manage-infra: `task run --param/--branch/--pipeline-file`. deploy: note that each promote `--param` becomes a promotion env var, with a generic example. Co-Authored-By: Claude Opus 4.8 (1M context) --- assets/plugin/skills/deploy/SKILL.md | 3 ++- assets/plugin/skills/manage-infra/SKILL.md | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/assets/plugin/skills/deploy/SKILL.md b/assets/plugin/skills/deploy/SKILL.md index 1cad21f..2cce5fe 100644 --- a/assets/plugin/skills/deploy/SKILL.md +++ b/assets/plugin/skills/deploy/SKILL.md @@ -27,8 +27,9 @@ sem-ai promote-and-wait --target "Staging Deploy" --confirm # Override conditions (deploy despite failures) sem-ai pipeline promote --target "Staging" --confirm --override -# With parameters +# With parameters (each --param becomes a promotion env var) sem-ai pipeline promote --target "Production" --confirm --param version=1.2.3 +sem-ai pipeline promote --target "Production Deploy" --confirm --param SERVICE=web ``` ## Deployment targets — read diff --git a/assets/plugin/skills/manage-infra/SKILL.md b/assets/plugin/skills/manage-infra/SKILL.md index c7a2b53..0e6ddb9 100644 --- a/assets/plugin/skills/manage-infra/SKILL.md +++ b/assets/plugin/skills/manage-infra/SKILL.md @@ -36,6 +36,12 @@ sem-ai task list --project

sem-ai task show sem-ai task run # trigger now sem-ai task delete + +# Parameterized tasks: pass parameters, branch, and pipeline file. +# Parameters go to the task's run_now as a map; branch/pipeline-file pick +# the ref + YAML the task pipeline runs. +sem-ai task run --param KEY=VALUE [--param ...] \ + [--branch ] [--pipeline-file ] ``` ## Artifacts