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.
Bug
compiler_safe_output_jobs.goalways addspayload: ${{ needs.safe_outputs.outputs.call_workflow_payload }}to thewith:block of every generated call-workflow fan-out job (line ~209), regardless of whether the worker'sworkflow_call.inputsdeclarespayload.GitHub Actions rejects a
uses:step at runtime if the caller passes an input that the called workflow does not declare:So any worker that doesn't explicitly declare
payload:in itson.workflow_call.inputswill 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.mdingithubnext/gh-aw-testdeclares onlysentinelandaw_contextinputs:The compiled caller (
test-copilot-call-workflow.lock.yml) generates:The worker lockfile has no
payloadinput, so GitHub Actions rejects the job.Expected behaviour
payloadshould only be forwarded in thewith:block if the worker'sworkflow_call.inputsexplicitly declarespayload. If the worker doesn't declare it (the common case for user-authored workers), the compiler should only pass the typed, per-inputfromJSON(…)entries that the worker actually declares.This also makes
call-workflowwork transparently with non-agentic YAML workflows that obviously won't have apayloadinput.Root cause
pkg/workflow/compiler_safe_output_jobs.goinitialises thewithmap unconditionally:The fix should gate this on whether
payloadappears 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 theirworkflow_call.inputsas a string input; the compiler then passes it correctly. But this is undocumented and counter-intuitive — users shouldn't need to know about the internalpayloadenvelope to write a callable worker.