From 553364d14c03df7fc200cb7a2e5dc124e259a4e8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 04:11:49 +0000 Subject: [PATCH 1/3] feat: add workflow_call support to agentic maintenance with outputs Agent-Logs-Url: https://github.com/github/gh-aw/sessions/456a53d8-60cb-4b06-9b3d-9f2631dc8543 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentics-maintenance.yml | 51 +++++++++--- pkg/workflow/maintenance_workflow.go | 96 ++++++++++++++++------ pkg/workflow/maintenance_workflow_test.go | 46 +++++++++-- 3 files changed, 151 insertions(+), 42 deletions(-) diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml index 63508aea9fe..2aab2898e06 100644 --- a/.github/workflows/agentics-maintenance.yml +++ b/.github/workflows/agentics-maintenance.yml @@ -57,12 +57,31 @@ on: required: false type: string default: '' + workflow_call: + inputs: + operation: + description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, clean_cache_memories, validate)' + required: false + type: string + default: '' + run_url: + description: 'Run URL or run ID to replay safe outputs from (e.g. https://github.com/owner/repo/actions/runs/12345 or 12345). Required when operation is safe_outputs.' + required: false + type: string + default: '' + outputs: + operation_completed: + description: 'The maintenance operation that was completed (empty when none ran or a scheduled job ran)' + value: ${{ jobs.run_operation.outputs.operation }} + applied_run_url: + description: 'The run URL that safe outputs were applied from' + value: ${{ jobs.apply_safe_outputs.outputs.run_url }} permissions: {} jobs: close-expired-entities: - if: ${{ (!(github.event.repository.fork)) && (github.event_name != 'workflow_dispatch' || github.event.inputs.operation == '') }} + if: ${{ (!(github.event.repository.fork)) && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '') }} runs-on: ubuntu-slim permissions: discussions: write @@ -109,7 +128,7 @@ jobs: await main(); cleanup-cache-memory: - if: ${{ (!(github.event.repository.fork)) && (github.event_name != 'workflow_dispatch' || github.event.inputs.operation == '' || github.event.inputs.operation == 'clean_cache_memories') }} + if: ${{ (!(github.event.repository.fork)) && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '' || inputs.operation == 'clean_cache_memories') }} runs-on: ubuntu-slim permissions: actions: write @@ -136,12 +155,14 @@ jobs: await main(); run_operation: - if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.operation != '' && github.event.inputs.operation != 'safe_outputs' && github.event.inputs.operation != 'create_labels' && github.event.inputs.operation != 'clean_cache_memories' && github.event.inputs.operation != 'validate' && (!(github.event.repository.fork)) }} + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'validate' && (!(github.event.repository.fork)) }} runs-on: ubuntu-slim permissions: actions: write contents: write pull-requests: write + outputs: + operation: ${{ steps.record.outputs.operation }} steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -176,7 +197,7 @@ jobs: uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_AW_OPERATION: ${{ github.event.inputs.operation }} + GH_AW_OPERATION: ${{ inputs.operation }} GH_AW_CMD_PREFIX: ./gh-aw with: github-token: ${{ secrets.GITHUB_TOKEN }} @@ -186,8 +207,12 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/run_operation_update_upgrade.cjs'); await main(); + - name: Record outputs + id: record + run: echo "operation=${{ inputs.operation }}" >> "$GITHUB_OUTPUT" + apply_safe_outputs: - if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.operation == 'safe_outputs' && (!(github.event.repository.fork)) }} + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'safe_outputs' && (!(github.event.repository.fork)) }} runs-on: ubuntu-slim permissions: actions: read @@ -195,6 +220,8 @@ jobs: discussions: write issues: write pull-requests: write + outputs: + run_url: ${{ steps.record.outputs.run_url }} steps: - name: Checkout actions folder uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -222,7 +249,7 @@ jobs: uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_AW_RUN_URL: ${{ github.event.inputs.run_url }} + GH_AW_RUN_URL: ${{ inputs.run_url }} with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -231,8 +258,12 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/apply_safe_outputs_replay.cjs'); await main(); + - name: Record outputs + id: record + run: echo "run_url=${{ inputs.run_url }}" >> "$GITHUB_OUTPUT" + create_labels: - if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.operation == 'create_labels' && (!(github.event.repository.fork)) }} + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'create_labels' && (!(github.event.repository.fork)) }} runs-on: ubuntu-slim permissions: contents: read @@ -280,7 +311,7 @@ jobs: await main(); validate_workflows: - if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.operation == 'validate' && (!(github.event.repository.fork)) }} + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'validate' && (!(github.event.repository.fork)) }} runs-on: ubuntu-latest permissions: contents: read @@ -328,7 +359,7 @@ jobs: await main(); compile-workflows: - if: ${{ (!(github.event.repository.fork)) && (github.event_name != 'workflow_dispatch' || github.event.inputs.operation == '') }} + if: ${{ (!(github.event.repository.fork)) && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '') }} runs-on: ubuntu-slim permissions: contents: read @@ -368,7 +399,7 @@ jobs: await main(); secret-validation: - if: ${{ (!(github.event.repository.fork)) && (github.event_name != 'workflow_dispatch' || github.event.inputs.operation == '') }} + if: ${{ (!(github.event.repository.fork)) && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '') }} runs-on: ubuntu-slim permissions: contents: read diff --git a/pkg/workflow/maintenance_workflow.go b/pkg/workflow/maintenance_workflow.go index 1739f568b47..24052176776 100644 --- a/pkg/workflow/maintenance_workflow.go +++ b/pkg/workflow/maintenance_workflow.go @@ -269,6 +269,25 @@ on: required: false type: string default: '' + workflow_call: + inputs: + operation: + description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, clean_cache_memories, validate)' + required: false + type: string + default: '' + run_url: + description: 'Run URL or run ID to replay safe outputs from (e.g. https://github.com/owner/repo/actions/runs/12345 or 12345). Required when operation is safe_outputs.' + required: false + type: string + default: '' + outputs: + operation_completed: + description: 'The maintenance operation that was completed (empty when none ran or a scheduled job ran)' + value: ${{ jobs.run_operation.outputs.operation }} + applied_run_url: + description: 'The run URL that safe outputs were applied from' + value: ${{ jobs.apply_safe_outputs.outputs.run_url }} permissions: {} @@ -392,6 +411,8 @@ jobs: actions: write contents: write pull-requests: write + outputs: + operation: ${{ steps.record.outputs.operation }} steps: - name: Checkout repository uses: ` + GetActionPin("actions/checkout") + ` @@ -420,7 +441,7 @@ jobs: uses: ` + GetActionPin("actions/github-script") + ` env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_AW_OPERATION: ${{ github.event.inputs.operation }} + GH_AW_OPERATION: ${{ inputs.operation }} GH_AW_CMD_PREFIX: ` + getCLICmdPrefix(actionMode) + ` with: github-token: ${{ secrets.GITHUB_TOKEN }} @@ -429,6 +450,10 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/run_operation_update_upgrade.cjs'); await main(); + + - name: Record outputs + id: record + run: echo "operation=${{ inputs.operation }}" >> "$GITHUB_OUTPUT" `) // Add apply_safe_outputs job for workflow_dispatch with operation == 'safe_outputs' @@ -442,6 +467,8 @@ jobs: discussions: write issues: write pull-requests: write + outputs: + run_url: ${{ steps.record.outputs.run_url }} steps: - name: Checkout actions folder uses: ` + GetActionPin("actions/checkout") + ` @@ -469,7 +496,7 @@ jobs: uses: ` + GetActionPin("actions/github-script") + ` env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_AW_RUN_URL: ${{ github.event.inputs.run_url }} + GH_AW_RUN_URL: ${{ inputs.run_url }} with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -477,6 +504,10 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/apply_safe_outputs_replay.cjs'); await main(); + + - name: Record outputs + id: record + run: echo "run_url=${{ inputs.run_url }}" >> "$GITHUB_OUTPUT" `) // Add create_labels job for workflow_dispatch with operation == 'create_labels' @@ -698,16 +729,23 @@ func buildNotForkCondition() ConditionNode { } } -// buildNotDispatchOrEmptyOperation creates a condition that is true when the event -// is not a workflow_dispatch or the operation input is empty. -func buildNotDispatchOrEmptyOperation() ConditionNode { +// buildNotDispatchOrCallOrEmptyOperation creates a condition that is true when the event +// is not a workflow_dispatch or workflow_call, or the operation input is empty. +// Uses the `inputs.operation` context which works for both workflow_dispatch and workflow_call. +func buildNotDispatchOrCallOrEmptyOperation() ConditionNode { return BuildOr( - BuildNotEquals( - BuildPropertyAccess("github.event_name"), - BuildStringLiteral("workflow_dispatch"), + BuildAnd( + BuildNotEquals( + BuildPropertyAccess("github.event_name"), + BuildStringLiteral("workflow_dispatch"), + ), + BuildNotEquals( + BuildPropertyAccess("github.event_name"), + BuildStringLiteral("workflow_call"), + ), ), BuildEquals( - BuildPropertyAccess("github.event.inputs.operation"), + BuildPropertyAccess("inputs.operation"), BuildStringLiteral(""), ), ) @@ -715,14 +753,14 @@ func buildNotDispatchOrEmptyOperation() ConditionNode { // buildNotForkAndScheduledOrOperation creates a condition for jobs that run on // schedule (or empty operation) AND when a specific operation is selected. -// Condition: !fork && (not_dispatch || operation == ” || operation == op) +// Condition: !fork && (not_dispatch_or_call || operation == \'\' || operation == op) func buildNotForkAndScheduledOrOperation(operation string) ConditionNode { return BuildAnd( buildNotForkCondition(), BuildOr( - buildNotDispatchOrEmptyOperation(), + buildNotDispatchOrCallOrEmptyOperation(), BuildEquals( - BuildPropertyAccess("github.event.inputs.operation"), + BuildPropertyAccess("inputs.operation"), BuildStringLiteral(operation), ), ), @@ -730,25 +768,28 @@ func buildNotForkAndScheduledOrOperation(operation string) ConditionNode { } // buildNotForkAndScheduled creates a condition for jobs that should run on any -// non-dispatch event (e.g. schedule, push) or on workflow_dispatch with an empty -// operation, and never on forks. -// Condition: !fork && (event_name != 'workflow_dispatch' || operation == "") +// non-dispatch/call event (e.g. schedule, push) or on workflow_dispatch/workflow_call +// with an empty operation, and never on forks. +// Condition: !fork && ((event_name != \'workflow_dispatch\' && event_name != \'workflow_call\') || operation == \'\') func buildNotForkAndScheduled() ConditionNode { return BuildAnd( buildNotForkCondition(), - buildNotDispatchOrEmptyOperation(), + buildNotDispatchOrCallOrEmptyOperation(), ) } // buildDispatchOperationCondition creates a condition for jobs that should run -// only when a specific workflow_dispatch operation is selected and not a fork. -// Condition: dispatch && operation == op && !fork +// only when a specific workflow_dispatch or workflow_call operation is selected and not a fork. +// Condition: (dispatch || call) && operation == op && !fork func buildDispatchOperationCondition(operation string) ConditionNode { return BuildAnd( BuildAnd( - BuildEventTypeEquals("workflow_dispatch"), + BuildOr( + BuildEventTypeEquals("workflow_dispatch"), + BuildEventTypeEquals("workflow_call"), + ), BuildEquals( - BuildPropertyAccess("github.event.inputs.operation"), + BuildPropertyAccess("inputs.operation"), BuildStringLiteral(operation), ), ), @@ -757,14 +798,17 @@ func buildDispatchOperationCondition(operation string) ConditionNode { } // buildRunOperationCondition creates the condition for the unified run_operation -// job that handles all dispatch operations except the ones with dedicated jobs. -// Condition: dispatch && operation != ” && operation != each excluded && !fork. +// job that handles all dispatch/call operations except the ones with dedicated jobs. +// Condition: (dispatch || call) && operation != \'\' && operation != each excluded && !fork. func buildRunOperationCondition(excludedOperations ...string) ConditionNode { - // Start with: event is workflow_dispatch AND operation is not empty + // Start with: event is workflow_dispatch or workflow_call AND operation is not empty condition := BuildAnd( - BuildEventTypeEquals("workflow_dispatch"), + BuildOr( + BuildEventTypeEquals("workflow_dispatch"), + BuildEventTypeEquals("workflow_call"), + ), BuildNotEquals( - BuildPropertyAccess("github.event.inputs.operation"), + BuildPropertyAccess("inputs.operation"), BuildStringLiteral(""), ), ) @@ -774,7 +818,7 @@ func buildRunOperationCondition(excludedOperations ...string) ConditionNode { condition = BuildAnd( condition, BuildNotEquals( - BuildPropertyAccess("github.event.inputs.operation"), + BuildPropertyAccess("inputs.operation"), BuildStringLiteral(op), ), ) diff --git a/pkg/workflow/maintenance_workflow_test.go b/pkg/workflow/maintenance_workflow_test.go index 128c41df0ad..ed5a256f307 100644 --- a/pkg/workflow/maintenance_workflow_test.go +++ b/pkg/workflow/maintenance_workflow_test.go @@ -280,11 +280,11 @@ func TestGenerateMaintenanceWorkflow_OperationJobConditions(t *testing.T) { } yaml := string(content) - operationSkipCondition := `github.event_name != 'workflow_dispatch' || github.event.inputs.operation == ''` - operationRunCondition := `github.event_name == 'workflow_dispatch' && github.event.inputs.operation != '' && github.event.inputs.operation != 'safe_outputs' && github.event.inputs.operation != 'create_labels' && github.event.inputs.operation != 'clean_cache_memories' && github.event.inputs.operation != 'validate'` - applySafeOutputsCondition := `github.event_name == 'workflow_dispatch' && github.event.inputs.operation == 'safe_outputs'` - createLabelsCondition := `github.event_name == 'workflow_dispatch' && github.event.inputs.operation == 'create_labels'` - cleanCacheMemoriesCondition := `github.event_name != 'workflow_dispatch' || github.event.inputs.operation == '' || github.event.inputs.operation == 'clean_cache_memories'` + operationSkipCondition := `github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == ''` + operationRunCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'validate'` + applySafeOutputsCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'safe_outputs'` + createLabelsCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'create_labels'` + cleanCacheMemoriesCondition := `github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '' || inputs.operation == 'clean_cache_memories'` const jobSectionSearchRange = 300 const runOpSectionSearchRange = 500 @@ -354,7 +354,7 @@ func TestGenerateMaintenanceWorkflow_OperationJobConditions(t *testing.T) { } // validate_workflows job should be triggered when operation == 'validate' - validateCondition := `github.event_name == 'workflow_dispatch' && github.event.inputs.operation == 'validate'` + validateCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'validate'` validateIdx := strings.Index(yaml, "\n validate_workflows:") if validateIdx == -1 { t.Errorf("Job validate_workflows not found in generated workflow") @@ -389,6 +389,40 @@ func TestGenerateMaintenanceWorkflow_OperationJobConditions(t *testing.T) { if !strings.Contains(yaml, "run_url:") { t.Error("workflow_dispatch should include run_url input") } + + // Verify workflow_call trigger is present with same inputs + if !strings.Contains(yaml, "workflow_call:") { + t.Error("workflow should include workflow_call trigger") + } + if !strings.Contains(yaml, "workflow_call:") || !strings.Contains(yaml, "inputs:\n operation:") { + t.Error("workflow_call trigger should include operation input") + } + + // Verify workflow_call outputs are declared + if !strings.Contains(yaml, "operation_completed:") { + t.Error("workflow_call outputs should include operation_completed") + } + if !strings.Contains(yaml, "applied_run_url:") { + t.Error("workflow_call outputs should include applied_run_url") + } + + // Verify run_operation job exposes outputs + runOpIdx2 := strings.Index(yaml, "\n run_operation:") + if runOpIdx2 != -1 { + runOpSection2 := yaml[runOpIdx2 : runOpIdx2+600] + if !strings.Contains(runOpSection2, "outputs:\n operation: ${{ steps.record.outputs.operation }}") { + t.Errorf("run_operation job should declare operation output, got:\n%s", runOpSection2[:300]) + } + } + + // Verify apply_safe_outputs job exposes run_url output + applyIdx2 := strings.Index(yaml, "\n apply_safe_outputs:") + if applyIdx2 != -1 { + applySection2 := yaml[applyIdx2 : applyIdx2+600] + if !strings.Contains(applySection2, "outputs:\n run_url: ${{ steps.record.outputs.run_url }}") { + t.Errorf("apply_safe_outputs job should declare run_url output, got:\n%s", applySection2[:300]) + } + } } func TestGenerateMaintenanceWorkflow_ActionTag(t *testing.T) { From a2d24f133875707f5ea3e91ac07ba1c4258fcdec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 04:15:48 +0000 Subject: [PATCH 2/3] fix: remove redundant workflow_call check in test assertion Agent-Logs-Url: https://github.com/github/gh-aw/sessions/456a53d8-60cb-4b06-9b3d-9f2631dc8543 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/maintenance_workflow_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/workflow/maintenance_workflow_test.go b/pkg/workflow/maintenance_workflow_test.go index ed5a256f307..bd99a5f5641 100644 --- a/pkg/workflow/maintenance_workflow_test.go +++ b/pkg/workflow/maintenance_workflow_test.go @@ -394,7 +394,7 @@ func TestGenerateMaintenanceWorkflow_OperationJobConditions(t *testing.T) { if !strings.Contains(yaml, "workflow_call:") { t.Error("workflow should include workflow_call trigger") } - if !strings.Contains(yaml, "workflow_call:") || !strings.Contains(yaml, "inputs:\n operation:") { + if !strings.Contains(yaml, "inputs:\n operation:") { t.Error("workflow_call trigger should include operation input") } From eb3a5b7abba9612fbf03749e3fca14617b77a69a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 14:28:46 +0000 Subject: [PATCH 3/3] fix: use inputs.operation fallback for operation_completed output and scope workflow_call input test Agent-Logs-Url: https://github.com/github/gh-aw/sessions/923106c3-e923-4588-bf49-e98c56442d8d Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentics-maintenance.yml | 2 +- pkg/workflow/maintenance_workflow.go | 2 +- pkg/workflow/maintenance_workflow_test.go | 11 +++++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml index 2aab2898e06..c15f35c7e38 100644 --- a/.github/workflows/agentics-maintenance.yml +++ b/.github/workflows/agentics-maintenance.yml @@ -72,7 +72,7 @@ on: outputs: operation_completed: description: 'The maintenance operation that was completed (empty when none ran or a scheduled job ran)' - value: ${{ jobs.run_operation.outputs.operation }} + value: ${{ jobs.run_operation.outputs.operation || inputs.operation }} applied_run_url: description: 'The run URL that safe outputs were applied from' value: ${{ jobs.apply_safe_outputs.outputs.run_url }} diff --git a/pkg/workflow/maintenance_workflow.go b/pkg/workflow/maintenance_workflow.go index 24052176776..5ee22a5483c 100644 --- a/pkg/workflow/maintenance_workflow.go +++ b/pkg/workflow/maintenance_workflow.go @@ -284,7 +284,7 @@ on: outputs: operation_completed: description: 'The maintenance operation that was completed (empty when none ran or a scheduled job ran)' - value: ${{ jobs.run_operation.outputs.operation }} + value: ${{ jobs.run_operation.outputs.operation || inputs.operation }} applied_run_url: description: 'The run URL that safe outputs were applied from' value: ${{ jobs.apply_safe_outputs.outputs.run_url }} diff --git a/pkg/workflow/maintenance_workflow_test.go b/pkg/workflow/maintenance_workflow_test.go index bd99a5f5641..912ff5ffdd4 100644 --- a/pkg/workflow/maintenance_workflow_test.go +++ b/pkg/workflow/maintenance_workflow_test.go @@ -391,11 +391,14 @@ func TestGenerateMaintenanceWorkflow_OperationJobConditions(t *testing.T) { } // Verify workflow_call trigger is present with same inputs - if !strings.Contains(yaml, "workflow_call:") { + workflowCallIdx := strings.Index(yaml, "workflow_call:") + if workflowCallIdx == -1 { t.Error("workflow should include workflow_call trigger") - } - if !strings.Contains(yaml, "inputs:\n operation:") { - t.Error("workflow_call trigger should include operation input") + } else { + workflowCallSection := yaml[workflowCallIdx:] + if !strings.Contains(workflowCallSection, "inputs:\n operation:") { + t.Error("workflow_call trigger should include operation input") + } } // Verify workflow_call outputs are declared