From d5c1ebb36f7068aefeca26fcd7aa61dab00ffb0d Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Sat, 27 Jun 2026 05:18:52 -0400 Subject: [PATCH 1/2] fix: pass runner workflow dispatch inputs --- cmd/github-actions-runner-job/main.go | 35 ++++++++++++++++++++-- cmd/github-actions-runner-job/main_test.go | 26 +++++++++++++++- internal/ephemeral_runner_job.go | 1 + internal/module_runner_provider.go | 31 +++++++++++++++++++ 4 files changed, 89 insertions(+), 4 deletions(-) diff --git a/cmd/github-actions-runner-job/main.go b/cmd/github-actions-runner-job/main.go index a84855d..30a3754 100644 --- a/cmd/github-actions-runner-job/main.go +++ b/cmd/github-actions-runner-job/main.go @@ -166,13 +166,17 @@ func (c *providerSidecarClient) orgRegistrationToken(ctx context.Context, organi return out, nil } -func (c *providerSidecarClient) dispatchWorkflow(ctx context.Context, repository, workflow, ref string) error { +func (c *providerSidecarClient) dispatchWorkflow(ctx context.Context, repository, workflow, ref string, inputs map[string]string) error { owner, repo, err := splitRepository(repository) if err != nil { return err } path := "/v1/actions/repos/" + url.PathEscape(owner) + "/" + url.PathEscape(repo) + "/workflows/" + url.PathEscape(workflow) + "/dispatches" - return c.do(ctx, http.MethodPost, path, map[string]any{"ref": ref}, http.StatusNoContent, nil) + body := map[string]any{"ref": ref} + if len(inputs) > 0 { + body["inputs"] = inputs + } + return c.do(ctx, http.MethodPost, path, body, http.StatusNoContent, nil) } func (c *providerSidecarClient) removeOrgRunner(ctx context.Context, organization string, runnerID int64) error { @@ -244,7 +248,11 @@ func (d *runnerDriver) RunGitHubJob(ctx context.Context, mode internal.Ephemeral if ref == "" { ref = "main" } - if err := d.sidecar.dispatchWorkflow(ctx, d.req.Repository, d.req.Workflow, ref); err != nil { + inputs, err := d.workflowDispatchInputs(spec) + if err != nil { + return internal.EphemeralRunnerJobResult{}, err + } + if err := d.sidecar.dispatchWorkflow(ctx, d.req.Repository, d.req.Workflow, ref, inputs); err != nil { return internal.EphemeralRunnerJobResult{}, err } } @@ -263,6 +271,27 @@ func (d *runnerDriver) RunGitHubJob(ctx context.Context, mode internal.Ephemeral }, nil } +func (d *runnerDriver) workflowDispatchInputs(spec internal.EphemeralRunnerJobSpec) (map[string]string, error) { + inputs := make(map[string]string, len(d.req.WorkflowInputs)+5) + for key, value := range d.req.WorkflowInputs { + key = strings.TrimSpace(key) + if key == "" { + continue + } + inputs[key] = value + } + labels, err := json.Marshal(spec.Labels) + if err != nil { + return nil, fmt.Errorf("marshal workflow runner labels: %w", err) + } + inputs["runner_profile"] = "provider" + inputs["allow_github_hosted_fallback"] = "false" + inputs["runner_labels_json"] = string(labels) + inputs["stg_task_id"] = d.req.TaskID + inputs["workflow_compute_provider_task"] = d.req.TaskID + return inputs, nil +} + func (d *runnerDriver) RemoveOrgRunner(ctx context.Context, organization string, runnerID int64) error { return d.sidecar.removeOrgRunner(ctx, organization, runnerID) } diff --git a/cmd/github-actions-runner-job/main_test.go b/cmd/github-actions-runner-job/main_test.go index 60ee08b..184dbc4 100644 --- a/cmd/github-actions-runner-job/main_test.go +++ b/cmd/github-actions-runner-job/main_test.go @@ -24,6 +24,29 @@ func TestT915CommandRunsDynamicProviderEnvelopeThroughSidecarAndRunner(t *testin _, _ = w.Write([]byte(`{"token":"runner-registration-token","expires_at":"2026-06-26T22:00:00Z"}`)) case r.Method == http.MethodPost && r.URL.Path == "/v1/actions/repos/GoCodeAlone/workflow-compute/workflows/dogfood.yml/dispatches": dispatchCalls++ + var body struct { + Ref string `json:"ref"` + Inputs map[string]string `json:"inputs"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + t.Fatalf("decode dispatch body: %v", err) + } + if body.Ref != "main" { + t.Fatalf("dispatch ref: got %q", body.Ref) + } + wantLabels := `["self-hosted","linux","wfc-stg-ghp-linux-01234567-abcdef98","wfc-ghp-stg","wfc-ghp-ephemeral"]` + for key, want := range map[string]string{ + "runner_profile": "provider", + "allow_github_hosted_fallback": "false", + "runner_labels_json": wantLabels, + "stg_task_id": "task-abcdef9876543210", + "workflow_compute_provider_task": "task-abcdef9876543210", + "custom": "kept", + } { + if got := body.Inputs[key]; got != want { + t.Fatalf("dispatch input %s: got %q want %q; body=%#v", key, got, want, body.Inputs) + } + } w.WriteHeader(http.StatusNoContent) case r.Method == http.MethodDelete && r.URL.Path == "/v1/actions/orgs/GoCodeAlone/runners/42": deleteCalls++ @@ -60,7 +83,8 @@ func TestT915CommandRunsDynamicProviderEnvelopeThroughSidecarAndRunner(t *testin "repository":"GoCodeAlone/workflow-compute", "workflow":"dogfood.yml", "ref":"main", - "runner_group":"ephemeral" + "runner_group":"ephemeral", + "workflow_inputs":{"custom":"kept"} } }`) var stdout, stderr bytes.Buffer diff --git a/internal/ephemeral_runner_job.go b/internal/ephemeral_runner_job.go index eb79d04..3b91a8c 100644 --- a/internal/ephemeral_runner_job.go +++ b/internal/ephemeral_runner_job.go @@ -29,6 +29,7 @@ type EphemeralRunnerJobRequest struct { Repository string `json:"repository,omitempty"` Workflow string `json:"workflow,omitempty"` Ref string `json:"ref,omitempty"` + WorkflowInputs map[string]string `json:"workflow_inputs,omitempty"` RunnerGroup string `json:"runner_group,omitempty"` Timeout time.Duration `json:"-"` RequiredRuntimeCaps []string `json:"required_runtime_caps,omitempty"` diff --git a/internal/module_runner_provider.go b/internal/module_runner_provider.go index e9e26b6..b1201e3 100644 --- a/internal/module_runner_provider.go +++ b/internal/module_runner_provider.go @@ -917,6 +917,7 @@ func ephemeralRunnerJobRequestFromArgs(args map[string]any) (EphemeralRunnerJobR Repository: stringArg(args, "repository"), Workflow: stringArg(args, "workflow"), Ref: stringArg(args, "ref"), + WorkflowInputs: stringMapArg(args["workflow_inputs"]), RunnerGroup: stringArg(args, "runner_group"), RequiredRuntimeCaps: stringListArg(args["required_runtime_caps"]), AdvertisedCaps: stringListArg(args["advertised_caps"]), @@ -928,6 +929,36 @@ func stringArg(args map[string]any, key string) string { return strings.TrimSpace(value) } +func stringMapArg(value any) map[string]string { + switch v := value.(type) { + case map[string]string: + out := make(map[string]string, len(v)) + for key, val := range v { + key = strings.TrimSpace(key) + if key != "" { + out[key] = val + } + } + return out + case map[string]any: + out := make(map[string]string, len(v)) + for key, val := range v { + key = strings.TrimSpace(key) + if key == "" { + continue + } + str, ok := val.(string) + if !ok { + continue + } + out[key] = str + } + return out + default: + return nil + } +} + func int64Arg(args map[string]any, key string) (int64, error) { switch v := args[key].(type) { case int: From c371328e3e9cd43d28ab5108248d4505a2726102 Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Sat, 27 Jun 2026 05:24:26 -0400 Subject: [PATCH 2/2] fix: normalize runner workflow inputs --- cmd/github-actions-runner-job/main.go | 2 +- cmd/github-actions-runner-job/main_test.go | 7 ++++++- internal/module_runner_provider.go | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/cmd/github-actions-runner-job/main.go b/cmd/github-actions-runner-job/main.go index 30a3754..b74e2e4 100644 --- a/cmd/github-actions-runner-job/main.go +++ b/cmd/github-actions-runner-job/main.go @@ -274,7 +274,7 @@ func (d *runnerDriver) RunGitHubJob(ctx context.Context, mode internal.Ephemeral func (d *runnerDriver) workflowDispatchInputs(spec internal.EphemeralRunnerJobSpec) (map[string]string, error) { inputs := make(map[string]string, len(d.req.WorkflowInputs)+5) for key, value := range d.req.WorkflowInputs { - key = strings.TrimSpace(key) + key = strings.ToLower(strings.TrimSpace(key)) if key == "" { continue } diff --git a/cmd/github-actions-runner-job/main_test.go b/cmd/github-actions-runner-job/main_test.go index 184dbc4..189584d 100644 --- a/cmd/github-actions-runner-job/main_test.go +++ b/cmd/github-actions-runner-job/main_test.go @@ -47,6 +47,11 @@ func TestT915CommandRunsDynamicProviderEnvelopeThroughSidecarAndRunner(t *testin t.Fatalf("dispatch input %s: got %q want %q; body=%#v", key, got, want, body.Inputs) } } + for _, forbidden := range []string{"Runner_Profile", "ALLOW_GITHUB_HOSTED_FALLBACK", "Custom"} { + if _, ok := body.Inputs[forbidden]; ok { + t.Fatalf("dispatch inputs must normalize caller keys, found %q in %#v", forbidden, body.Inputs) + } + } w.WriteHeader(http.StatusNoContent) case r.Method == http.MethodDelete && r.URL.Path == "/v1/actions/orgs/GoCodeAlone/runners/42": deleteCalls++ @@ -84,7 +89,7 @@ func TestT915CommandRunsDynamicProviderEnvelopeThroughSidecarAndRunner(t *testin "workflow":"dogfood.yml", "ref":"main", "runner_group":"ephemeral", - "workflow_inputs":{"custom":"kept"} + "workflow_inputs":{"Custom":"kept","Runner_Profile":"manual","ALLOW_GITHUB_HOSTED_FALLBACK":"true"} } }`) var stdout, stderr bytes.Buffer diff --git a/internal/module_runner_provider.go b/internal/module_runner_provider.go index b1201e3..639caad 100644 --- a/internal/module_runner_provider.go +++ b/internal/module_runner_provider.go @@ -934,7 +934,7 @@ func stringMapArg(value any) map[string]string { case map[string]string: out := make(map[string]string, len(v)) for key, val := range v { - key = strings.TrimSpace(key) + key = strings.ToLower(strings.TrimSpace(key)) if key != "" { out[key] = val } @@ -943,7 +943,7 @@ func stringMapArg(value any) map[string]string { case map[string]any: out := make(map[string]string, len(v)) for key, val := range v { - key = strings.TrimSpace(key) + key = strings.ToLower(strings.TrimSpace(key)) if key == "" { continue }