fix: forward declared workflow_call inputs in call-workflow fan-out jobs#21085
fix: forward declared workflow_call inputs in call-workflow fan-out jobs#21085
workflow_call inputs in call-workflow fan-out jobs#21085Conversation
The compiler now reads each worker's declared `workflow_call.inputs` and
emits one `with:` entry per input (beyond the canonical `payload`) using
`fromJSON(needs.safe_outputs.outputs.call_workflow_payload).<inputName>`.
Worker steps can now reference `${{ inputs.<name> }}` directly without
parsing the JSON envelope.
- pkg/workflow/compiler_safe_output_jobs.go: extract inputs via
findWorkflowFile + extractWorkflowCallInputs / extractMDWorkflowCallInputs
(non-fatal, mirrors the existing permissions pattern)
- tests: TestBuildCallWorkflowJobs_ForwardsDeclaredInputsFromPayload (unit)
and TestCallWorkflowCompile_ForwardsTypedInputsAlongsidePayload (E2E)
- docs: updated Worker Inputs and Compiled Output sections
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
|
@copilot update smoke workflow call agentic workflow with an input and ensure the lock file is updated . |
Adds a typed `task-description` input to the smoke-workflow-call worker
and updates smoke-call-workflow to pass it. Recompiles both lock files
so the fan-out job now shows the new fromJSON-derived forwarding entry:
task-description: ${{ fromJSON(needs.safe_outputs.outputs.call_workflow_payload).task-description }}
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Done in 0a90528. Added a with:
payload: ${{ needs.safe_outputs.outputs.call_workflow_payload }}
task-description: ${{ fromJSON(needs.safe_outputs.outputs.call_workflow_payload).task-description }} |
There was a problem hiding this comment.
Pull request overview
Fixes call-workflow fan-out job generation so that declared workflow_call.inputs on worker workflows (besides the canonical payload) are forwarded into the with: block as fromJSON(...) projections, allowing workers to use ${{ inputs.<name> }} directly.
Changes:
- Update
buildCallWorkflowJobsto always includepayloadand additionally forward each declared worker input (excludingpayload) asfromJSON(needs.safe_outputs.outputs.call_workflow_payload).<inputName>. - Add unit + E2E tests asserting typed inputs are forwarded and
payloadis not duplicated. - Update reference docs and smoke workflows/lock files to document and exercise typed input forwarding.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/workflow/compiler_safe_output_jobs.go | Adds typed input extraction from worker workflows and emits fromJSON(...)-derived with: entries in fan-out jobs. |
| pkg/workflow/safe_outputs_call_workflow_test.go | Adds a unit test to verify declared worker inputs are forwarded from the payload into the call job’s with:. |
| pkg/workflow/call_workflow_compilation_test.go | Adds an E2E compile test asserting the generated lock YAML contains both payload and typed input projections. |
| docs/src/content/docs/reference/safe-outputs.md | Documents canonical payload transport plus compiler-derived typed input forwarding and shows updated compiled output examples. |
| .github/workflows/smoke-workflow-call.md | Adds a typed task-description input (and payload) to the smoke worker to exercise the forwarding path. |
| .github/workflows/smoke-workflow-call.lock.yml | Regenerated lock output reflecting the new inputs and their usage in the workflow. |
| .github/workflows/smoke-call-workflow.md | Updates smoke orchestrator instructions to pass task-description when calling the worker. |
| .github/workflows/smoke-call-workflow.lock.yml | Regenerated lock output so the fan-out job forwards task-description alongside payload. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| var workflowInputs map[string]any | ||
| var inputErr error | ||
| switch { | ||
| case fileResult.lockExists: | ||
| workflowInputs, inputErr = extractWorkflowCallInputs(fileResult.lockPath) | ||
| case fileResult.ymlExists: | ||
| workflowInputs, inputErr = extractWorkflowCallInputs(fileResult.ymlPath) | ||
| case fileResult.mdExists: | ||
| workflowInputs, inputErr = extractMDWorkflowCallInputs(fileResult.mdPath) | ||
| } | ||
| if inputErr != nil { | ||
| compilerSafeOutputJobsLog.Printf("Warning: could not extract workflow_call inputs for '%s': %v. "+ | ||
| "Typed inputs will not be forwarded in the with: block.", workflowName, inputErr) | ||
| } else { | ||
| typedInputCount := 0 | ||
| for inputName := range workflowInputs { | ||
| if inputName == "payload" { | ||
| continue | ||
| } | ||
| with[inputName] = fmt.Sprintf("${{ fromJSON(needs.safe_outputs.outputs.call_workflow_payload).%s }}", inputName) | ||
| typedInputCount++ | ||
| } | ||
| compilerSafeOutputJobsLog.Printf("Forwarding %d typed inputs for call-workflow job '%s'", typedInputCount, jobName) |
| // TestCallWorkflowCompile_ForwardsTypedInputsAlongsidePayload tests that the compiler | ||
| // emits fromJSON-derived with: entries for each declared workflow_call input (except payload) | ||
| // alongside the canonical payload entry in the generated fan-out job. | ||
| func TestCallWorkflowCompile_ForwardsTypedInputsAlongsidePayload(t *testing.T) { | ||
| tmpDir := testutil.TempDir(t, "call-workflow-forward-inputs") | ||
| workflowsDir := filepath.Join(tmpDir, ".github", "workflows") | ||
| require.NoError(t, os.MkdirAll(workflowsDir, 0755)) | ||
|
|
||
| typedInputs := ` payload: | ||
| type: string | ||
| required: false | ||
| source_repo: | ||
| description: Repository to check out | ||
| type: string | ||
| required: false | ||
| issue_number: | ||
| description: Issue number to process | ||
| type: string | ||
| required: false | ||
| ` | ||
| createWorker(t, workflowsDir, "worker-a", typedInputs) | ||
|
|
||
| gatewayMD := `--- | ||
| on: workflow_dispatch | ||
| engine: copilot | ||
| permissions: | ||
| contents: read | ||
| safe-outputs: | ||
| add-comment: | ||
| max: 1 | ||
| call-workflow: | ||
| workflows: | ||
| - worker-a | ||
| --- | ||
|
|
||
| # Gateway — typed input forwarding | ||
| ` | ||
| yaml := compileAndReadLock(t, filepath.Join(workflowsDir, "gateway.md"), gatewayMD) | ||
|
|
||
| // Canonical payload transport must always be present | ||
| assert.Contains(t, yaml, "payload: ${{ needs.safe_outputs.outputs.call_workflow_payload }}", | ||
| "Should always forward canonical payload") | ||
|
|
||
| // Typed inputs must be derived from the payload via fromJSON | ||
| assert.Contains(t, yaml, "fromJSON(needs.safe_outputs.outputs.call_workflow_payload).source_repo", | ||
| "Should forward source_repo via fromJSON") | ||
| assert.Contains(t, yaml, "fromJSON(needs.safe_outputs.outputs.call_workflow_payload).issue_number", | ||
| "Should forward issue_number via fromJSON") | ||
|
|
||
| // payload must not appear as a fromJSON entry (it must remain the raw string) | ||
| assert.NotContains(t, yaml, "fromJSON(needs.safe_outputs.outputs.call_workflow_payload).payload", | ||
| "payload itself must not be duplicated as a fromJSON entry") | ||
| } |
…ow test Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Fixed in cea4552 — replaced |
|
@copilot merge main |
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Done in acf0121. Merged main — resolved one conflict in Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
|
@copilot review comments and suggestions |
|
@copilot review comments and suggestions |
…2E test - Add `default` case to the findWorkflowFile result switch to log a warning when no worker file exists (all *.Exists flags false), rather than silently logging "Forwarding 0 typed inputs" - Change `else` to `else if workflowInputs != nil` so the forwarding block is skipped entirely when the default case fires - Add TestCallWorkflowCompile_ForwardsHyphenatedInputs E2E test that compiles a gateway whose worker declares `task-description` (hyphen) and asserts the lock file contains the correct fromJSON projection Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Addressed both reviewer suggestions in 2aff7fa:
|
The compiler exposed typed
workflow_call.inputsto the agent via MCP tool generation but dropped them when emitting fan-out jobs, forwarding only a singlepayloadstring. Workers that used${{ inputs.source_repo }}or similar received nothing—forcing either post-compile patches orfromJSON(inputs.payload)in frontmatter, which fails strict-mode validation.Changes
compiler_safe_output_jobs.go— after building the defaultpayloadentry, resolve the worker file (findWorkflowFile:.lock.yml→.yml→.md) and extract itsworkflow_call.inputs. For each input other thanpayload, emit afromJSON-derivedwith:entry. Non-fatal on resolution failure (mirrors the existing permissions extraction pattern).safe_outputs_call_workflow_test.go— unit testTestBuildCallWorkflowJobs_ForwardsDeclaredInputsFromPayload: asserts typed inputs appear asfromJSON(...)entries andpayloadis not duplicated.call_workflow_compilation_test.go— E2E testTestCallWorkflowCompile_ForwardsTypedInputsAlongsidePayload: compiles a gateway whose worker declarespayload,source_repo, andissue_number; asserts the lock file contains all threewith:entries.docs/reference/safe-outputs.md— updated Worker Inputs and Compiled Output sections to document both transport mechanisms.smoke-workflow-call.md— added atask-descriptiontyped input (under bothworkflow_callandworkflow_dispatchtriggers) so the smoke worker exercises the new forwarding path end-to-end.smoke-call-workflow.md— updated orchestrator instructions to pass"smoke test checkout validation"as thetask-descriptionargument when calling the worker.Lock files regenerated —
smoke-call-workflow.lock.ymlnow forwardstask-descriptionviafromJSON(...)alongsidepayload;smoke-workflow-call.lock.ymlexposes both inputs in itsworkflow_calltrigger.Generated output (before → after)
payloadremains the canonical transport; the additional entries are compiler-derived projections so worker steps can use${{ inputs.<name> }}without parsing JSON.📍 Connect Copilot coding agent with Jira, Azure Boards or Linear to delegate work to Copilot in one click without leaving your project management tool.