runner: send empty Task.needs so multi-stage jobs run (generateWorkflow injects a runs-on-less mock per need, which act skips on if: success())

ed9a7cd · gmorell · 2026-07-02 16:47

1 files +9 -23

Files changed

modified lib/git_gud_web/controllers/runner_controller.ex
+9 −23
@@ -226,31 +226,17 @@ defmodule GitGudWeb.RunnerController do
226 226 end
227 227 end
228 228
229 # Built from the snapshot captured when the job was promoted to
230 # `queued` (Workflows.resolve_dependents/1), so the upstream results are
231 # the definitively-terminal values — never re-read at fetch time, which
232 # raced and could yield RESULT_UNSPECIFIED (act then skips the job on
233 # `if: success()`). Snapshot shape:
234 # %{"<need>" => %{"status" => "...", "outputs" => %{}}}
235 defp build_needs(%GitGud.Workflows.WorkflowJob{needs_snapshot: snap})
236 when is_map(snap) and map_size(snap) > 0 do
237 Map.new(snap, fn {jid, data} ->
238 {jid,
239 %Runner.V1.TaskNeed{
240 result: status_to_result_enum(Map.get(data, "status")),
241 outputs: Map.get(data, "outputs") || %{}
242 }}
243 end)
244 end
245
229 + # Always send an EMPTY needs map. forgejo-runner's generateWorkflow
230 + # injects a MOCK job (with no `runs-on`) for every Task.needs entry and
231 + # rebuilds the target job's RawNeeds from them; act then can't resolve
232 + # the runs-on-less mock and skips the job via `if: success()`. With no
233 + # entries, no mock is injected and RawNeeds is emptied, so `success()`
234 + # passes and the job runs. Dispatch is already gated on upstream success
235 + # by Workflows.resolve_dependents/1, so the dependency stays honoured.
236 + # Trade-off: `${{ needs.<id>.result/outputs }}` isn't surfaced to the
237 + # job — none of our workflows read cross-job needs outputs.
246 238 defp build_needs(_job), do: %{}
247 239
248 defp status_to_result_enum("success"), do: :RESULT_SUCCESS
249 defp status_to_result_enum("failure"), do: :RESULT_FAILURE
250 defp status_to_result_enum("cancelled"), do: :RESULT_CANCELLED
251 defp status_to_result_enum("skipped"), do: :RESULT_SKIPPED
252 defp status_to_result_enum(_), do: :RESULT_UNSPECIFIED
253
254 240 # Forgejo's runner proto switched `Task.context` from
255 241 # `map<string,string>` to `google.protobuf.Struct` so values can carry
256 242 # nested objects, numbers, and bools. Our context is still all

Parents: 3aa2fef