Skip to content

call-workflow compiler unconditionally passes payload to worker, breaking workflows that don't declare it #40152

Description

@dsyme

Bug

compiler_safe_output_jobs.go always adds payload: ${{ needs.safe_outputs.outputs.call_workflow_payload }} to the with: block of every generated call-workflow fan-out job (line ~209), regardless of whether the worker's workflow_call.inputs declares payload.

GitHub Actions rejects a uses: step at runtime if the caller passes an input that the called workflow does not declare:

"If any inputs passed are not defined in the called workflow, this results in an error."

So any worker that doesn't explicitly declare payload: in its on.workflow_call.inputs will fail immediately when the fan-out job starts — the agent job succeeds, the safe output fires, and then the call job is rejected by Actions before it does anything.

Reproducer

test-copilot-call-worker.md in githubnext/gh-aw-test declares only sentinel and aw_context inputs:

on:
  workflow_call:
    inputs:
      sentinel:
        description: "Sentinel value for the worker-created issue title"
        required: true
        type: string

The compiled caller (test-copilot-call-workflow.lock.yml) generates:

uses: ./.github/workflows/test-copilot-call-worker.lock.yml
with:
  aw_context: ${{ fromJSON(needs.safe_outputs.outputs.call_workflow_payload).aw_context }}
  payload: ${{ needs.safe_outputs.outputs.call_workflow_payload }}   # <-- not declared in worker
  sentinel: ${{ fromJSON(needs.safe_outputs.outputs.call_workflow_payload).sentinel }}

The worker lockfile has no payload input, so GitHub Actions rejects the job.

Expected behaviour

payload should only be forwarded in the with: block if the worker's workflow_call.inputs explicitly declares payload. If the worker doesn't declare it (the common case for user-authored workers), the compiler should only pass the typed, per-input fromJSON(…) entries that the worker actually declares.

This also makes call-workflow work transparently with non-agentic YAML workflows that obviously won't have a payload input.

Root cause

pkg/workflow/compiler_safe_output_jobs.go initialises the with map unconditionally:

with := map[string]any{
    "payload": "${{ needs.safe_outputs.outputs.call_workflow_payload }}",
}

The fix should gate this on whether payload appears in the extracted worker inputs (same check already applied to individual typed inputs), or simply remove the unconditional inclusion and let typed forwarding handle everything.

Workaround

Worker authors can add payload: to their workflow_call.inputs as a string input; the compiler then passes it correctly. But this is undocumented and counter-intuitive — users shouldn't need to know about the internal payload envelope to write a callable worker.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions