diff --git a/.github/workflows/copilot-session-insights.lock.yml b/.github/workflows/copilot-session-insights.lock.yml index 67f2d7d1d66..8780b9929f1 100644 --- a/.github/workflows/copilot-session-insights.lock.yml +++ b/.github/workflows/copilot-session-insights.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"1a205bf3354b3cf4ede0f23f8443a2f0dd553ee0a66727371426742bf0733e68","body_hash":"2a96a21304b975c51504dd556731857febd942d37b53e1fcc5b42a29855ab23e","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.223"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"1a205bf3354b3cf4ede0f23f8443a2f0dd553ee0a66727371426742bf0733e68","body_hash":"2a96a21304b975c51504dd556731857febd942d37b53e1fcc5b42a29855ab23e","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.224"}} # gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/setup-python","sha":"5fda3b95a4ea91299a34e894583c3862153e4b97","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44","digest":"sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44@sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.8","digest":"sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.8@sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # diff --git a/pkg/workflow/compiler_safe_outputs_job.go b/pkg/workflow/compiler_safe_outputs_job.go index 3d962738c18..7e8afb21a53 100644 --- a/pkg/workflow/compiler_safe_outputs_job.go +++ b/pkg/workflow/compiler_safe_outputs_job.go @@ -3,6 +3,7 @@ package workflow import ( "encoding/json" "fmt" + "maps" "sort" "strings" @@ -267,27 +268,29 @@ func (c *Compiler) buildSafeOutputsSetupAndDownloadSteps(data *WorkflowData, age // and the named convenience outputs for first-created items. // It returns the collected steps, outputs map, and the list of safe-output step names registered. func (c *Compiler) buildSafeOutputsHandlerOutputsAndActionSteps(data *WorkflowData, agentArtifactPrefix, markdownPath string) ([]string, map[string]string, []string, error) { - var steps []string - outputs := make(map[string]string) - var safeOutputStepNames []string - - // Note: Unlock step has been moved to dedicated unlock job - // The safe_outputs job now depends on the unlock job, so the issue - // will already be unlocked when this job runs - - // === Build safe output steps === - // - // IMPORTANT: Step order matters for safe outputs that depend on each other. - // The execution order ensures dependencies are satisfied: - // 1. Handler Manager - processes create_issue, update_issue, add_comment, etc. - // 2. Assign To Agent - assigns issue to agent (after handler managers complete) - // 3. Create Agent Session - creates agent session (after assignment) - // - // Note: All project-related operations (create_project, update_project, create_project_status_update) - // are now handled by the unified handler in the handler manager step. - - // Check if any handler-manager-supported types are enabled - hasHandlerManagerTypes := data.SafeOutputs.CreateIssues != nil || + state := safeOutputsHandlerOutputsAndActionState{outputs: make(map[string]string)} + if err := c.appendCustomScriptFilesStep(data, &state); err != nil { + return nil, nil, nil, err + } + c.appendUploadArtifactStagingDownloadStep(data, agentArtifactPrefix, &state) + if err := c.appendHandlerManagerStep(data, &state); err != nil { + return nil, nil, nil, err + } + c.appendSarifArtifactUploadStep(data, agentArtifactPrefix, &state) + c.appendCustomActionSteps(data, markdownPath, &state) + addNamedSafeOutputHandlerOutputs(data, state.outputs) + + return state.steps, state.outputs, state.safeOutputStepNames, nil +} + +type safeOutputsHandlerOutputsAndActionState struct { + steps []string + outputs map[string]string + safeOutputStepNames []string +} + +func hasHandlerManagerTypes(data *WorkflowData) bool { + return data.SafeOutputs.CreateIssues != nil || data.SafeOutputs.AddComments != nil || data.SafeOutputs.CreateDiscussions != nil || data.SafeOutputs.CloseIssues != nil || @@ -322,27 +325,25 @@ func (c *Compiler) buildSafeOutputsHandlerOutputsAndActionSteps(data *WorkflowDa data.SafeOutputs.UploadArtifact != nil || // upload_artifact is handled inline in the handler loop len(data.SafeOutputs.Scripts) > 0 || // Custom scripts run in the handler loop len(data.SafeOutputs.Actions) > 0 // Custom actions need handler to export their payloads +} - // Note: All project-related operations are now handled by the unified handler. - // The project handler manager has been removed. - - // Add custom script files step (writes inline scripts to the actions folder) - // This must run before the handler manager step so the files are available for require() +func (c *Compiler) appendCustomScriptFilesStep(data *WorkflowData, state *safeOutputsHandlerOutputsAndActionState) error { if len(data.SafeOutputs.Scripts) > 0 { consolidatedSafeOutputsJobLog.Printf("Adding setup step for %d custom safe-output script(s)", len(data.SafeOutputs.Scripts)) scriptSetupSteps, err := buildCustomScriptFilesStep(data.SafeOutputs.Scripts) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to build custom script files step: %w", err) + return fmt.Errorf("failed to build custom script files step: %w", err) } - steps = append(steps, scriptSetupSteps...) + state.steps = append(state.steps, scriptSetupSteps...) } + return nil +} - // Download the upload-artifact staging artifact before the handler manager runs so that - // the upload_artifact handler (which runs inline in the handler loop) can access the files. +func (c *Compiler) appendUploadArtifactStagingDownloadStep(data *WorkflowData, agentArtifactPrefix string, state *safeOutputsHandlerOutputsAndActionState) { if data.SafeOutputs.UploadArtifact != nil { consolidatedSafeOutputsJobLog.Print("Adding upload-artifact staging download step") stagingArtifactName := agentArtifactPrefix + SafeOutputsUploadArtifactStagingArtifactName - steps = append(steps, + state.steps = append(state.steps, " - name: Download upload-artifact staging\n", " continue-on-error: true\n", fmt.Sprintf(" uses: %s\n", c.getActionPin("actions/download-artifact")), @@ -351,119 +352,88 @@ func (c *Compiler) buildSafeOutputsHandlerOutputsAndActionSteps(data *WorkflowDa fmt.Sprintf(" path: %s\n", artifactStagingDirExpr), ) } +} - // 1. Handler Manager step (processes create_issue, update_issue, add_comment, assign_to_agent, - // upload_artifact, etc.) - // This processes all safe output types that are handled by the unified handler - // Critical for workflows that create projects and then add issues/PRs to those projects - if hasHandlerManagerTypes { +func (c *Compiler) appendHandlerManagerStep(data *WorkflowData, state *safeOutputsHandlerOutputsAndActionState) error { + if hasHandlerManagerTypes(data) { consolidatedSafeOutputsJobLog.Print("Using handler manager for safe outputs") handlerManagerSteps, err := c.buildHandlerManagerStep(data) if err != nil { - return nil, nil, nil, err - } - steps = append(steps, handlerManagerSteps...) - safeOutputStepNames = append(safeOutputStepNames, "process_safe_outputs") - - // Add outputs from handler manager - outputs["process_safe_outputs_temporary_id_map"] = "${{ steps.process_safe_outputs.outputs.temporary_id_map }}" - outputs["process_safe_outputs_processed_count"] = "${{ steps.process_safe_outputs.outputs.processed_count }}" - outputs["process_safe_outputs_items_succeeded"] = "${{ steps.process_safe_outputs.outputs.items_succeeded }}" - outputs["process_safe_outputs_items_failed"] = "${{ steps.process_safe_outputs.outputs.items_failed }}" - outputs["process_safe_outputs_status"] = "${{ steps.process_safe_outputs.outputs.status }}" - outputs["create_discussion_errors"] = "${{ steps.process_safe_outputs.outputs.create_discussion_errors }}" - outputs["create_discussion_error_count"] = "${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}" - outputs["code_push_failure_errors"] = "${{ steps.process_safe_outputs.outputs.code_push_failure_errors }}" - outputs["code_push_failure_count"] = "${{ steps.process_safe_outputs.outputs.code_push_failure_count }}" - - // Note: Permissions are now computed centrally by ComputePermissionsForSafeOutputs() - // at the start of this function to ensure consistent permission calculation - - // Export assign_to_agent outputs from the handler manager step - if data.SafeOutputs.AssignToAgent != nil { - consolidatedSafeOutputsJobLog.Print("Exposing assign_to_agent outputs from handler manager") - outputs["assign_to_agent_assigned"] = "${{ steps.process_safe_outputs.outputs.assign_to_agent_assigned }}" - outputs["assign_to_agent_assignment_errors"] = "${{ steps.process_safe_outputs.outputs.assign_to_agent_assignment_errors }}" - outputs["assign_to_agent_assignment_error_count"] = "${{ steps.process_safe_outputs.outputs.assign_to_agent_assignment_error_count }}" - } - - // Export create_agent_session outputs from the handler manager step - if data.SafeOutputs.CreateAgentSessions != nil { - consolidatedSafeOutputsJobLog.Print("Exposing create_agent_session outputs from handler manager") - outputs["create_agent_session_session_number"] = "${{ steps.process_safe_outputs.outputs.session_number }}" - outputs["create_agent_session_session_url"] = "${{ steps.process_safe_outputs.outputs.session_url }}" + return err } - - // Export upload_artifact outputs. - // The handler sets slot_N_* outputs on the process_safe_outputs step; we expose - // them as upload_artifact_slot_N_* job outputs for external consumers. - // The actual artifact uploads are performed directly by the JS handler via - // @actions/artifact REST API — no additional YAML steps are required. - if data.SafeOutputs.UploadArtifact != nil { - consolidatedSafeOutputsJobLog.Print("Exposing upload_artifact outputs from handler manager") - cfg := data.SafeOutputs.UploadArtifact - outputs["upload_artifact_count"] = "${{ steps.process_safe_outputs.outputs.upload_artifact_count }}" - for i := range cfg.MaxUploads { - outputs[fmt.Sprintf("upload_artifact_slot_%d_tmp_id", i)] = fmt.Sprintf("${{ steps.process_safe_outputs.outputs.slot_%d_tmp_id }}", i) - } - } - + state.steps = append(state.steps, handlerManagerSteps...) + state.safeOutputStepNames = append(state.safeOutputStepNames, "process_safe_outputs") + addHandlerManagerOutputs(data, state.outputs) } + return nil +} - // 2. SARIF output — expose sarif_file from the handler so the dedicated - // upload_code_scanning_sarif job (built in buildCodeScanningUploadJob) can access it - // via needs.safe_outputs.outputs.sarif_file and decide whether to run. - // Additionally, upload the SARIF file as a GitHub Actions artifact so the upload job - // can retrieve the actual file (job outputs only carry the path string; the file itself - // only exists in the safe_outputs job workspace). - // NOTE: We do NOT export checkout_token as a job output. GitHub Actions masks output - // values that contain secret references, so the downstream job would receive an empty - // string. The upload job computes the token directly from static secret references. +func (c *Compiler) appendSarifArtifactUploadStep(data *WorkflowData, agentArtifactPrefix string, state *safeOutputsHandlerOutputsAndActionState) { if data.SafeOutputs.CreateCodeScanningAlerts != nil && !isHandlerStaged(c.trialMode || templatableBoolIsTrue(data.SafeOutputs.Staged), data.SafeOutputs.CreateCodeScanningAlerts.Staged) { consolidatedSafeOutputsJobLog.Print("Exposing sarif_file output for upload_code_scanning_sarif job") - outputs["sarif_file"] = "${{ steps.process_safe_outputs.outputs.sarif_file }}" - - // Upload the SARIF file as an artifact so the upload_code_scanning_sarif job - // (which runs in a separate, fresh workspace) can download and process it. - steps = append(steps, buildSarifArtifactUploadStep(agentArtifactPrefix, c.getActionPin)...) + state.outputs["sarif_file"] = "${{ steps.process_safe_outputs.outputs.sarif_file }}" + state.steps = append(state.steps, buildSarifArtifactUploadStep(agentArtifactPrefix, c.getActionPin)...) } +} - // 3. Custom action steps — compiler-generated steps for each configured safe-output action. - // These steps run after the handler manager, which processes the agent payload and exports - // a JSON payload output for each action tool call. Each step is guarded by an `if:` condition - // that checks whether the handler manager exported a payload for this action. +func (c *Compiler) appendCustomActionSteps(data *WorkflowData, markdownPath string, state *safeOutputsHandlerOutputsAndActionState) { if len(data.SafeOutputs.Actions) > 0 { - // resolveAllActions was already called early in buildJobs (before generateToolsMetaJSON) - // so action configs already have Inputs/ActionDescription populated. We only call it - // again here as a safety net in case compileSafeOutputsJob is called independently. c.resolveAllActions(data, markdownPath) - actionStepYAML := c.buildActionSteps(data) - steps = append(steps, actionStepYAML...) - - // Register each action as having a handler manager output + state.steps = append(state.steps, actionStepYAML...) for actionName := range data.SafeOutputs.Actions { normalizedName := stringutil.NormalizeSafeOutputIdentifier(actionName) - safeOutputStepNames = append(safeOutputStepNames, "action_"+normalizedName) + state.safeOutputStepNames = append(state.safeOutputStepNames, "action_"+normalizedName) + } + } +} + +func addHandlerManagerOutputs(data *WorkflowData, outputs map[string]string) { + maps.Copy(outputs, map[string]string{ + "process_safe_outputs_temporary_id_map": "${{ steps.process_safe_outputs.outputs.temporary_id_map }}", + "process_safe_outputs_processed_count": "${{ steps.process_safe_outputs.outputs.processed_count }}", + "process_safe_outputs_items_succeeded": "${{ steps.process_safe_outputs.outputs.items_succeeded }}", + "process_safe_outputs_items_failed": "${{ steps.process_safe_outputs.outputs.items_failed }}", + "process_safe_outputs_status": "${{ steps.process_safe_outputs.outputs.status }}", + "create_discussion_errors": "${{ steps.process_safe_outputs.outputs.create_discussion_errors }}", + "create_discussion_error_count": "${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}", + "code_push_failure_errors": "${{ steps.process_safe_outputs.outputs.code_push_failure_errors }}", + "code_push_failure_count": "${{ steps.process_safe_outputs.outputs.code_push_failure_count }}", + }) + addConditionalHandlerManagerOutputs(data, outputs) +} + +func addConditionalHandlerManagerOutputs(data *WorkflowData, outputs map[string]string) { + if data.SafeOutputs.AssignToAgent != nil { + consolidatedSafeOutputsJobLog.Print("Exposing assign_to_agent outputs from handler manager") + outputs["assign_to_agent_assigned"] = "${{ steps.process_safe_outputs.outputs.assign_to_agent_assigned }}" + outputs["assign_to_agent_assignment_errors"] = "${{ steps.process_safe_outputs.outputs.assign_to_agent_assignment_errors }}" + outputs["assign_to_agent_assignment_error_count"] = "${{ steps.process_safe_outputs.outputs.assign_to_agent_assignment_error_count }}" + } + if data.SafeOutputs.CreateAgentSessions != nil { + consolidatedSafeOutputsJobLog.Print("Exposing create_agent_session outputs from handler manager") + outputs["create_agent_session_session_number"] = "${{ steps.process_safe_outputs.outputs.session_number }}" + outputs["create_agent_session_session_url"] = "${{ steps.process_safe_outputs.outputs.session_url }}" + } + if data.SafeOutputs.UploadArtifact != nil { + consolidatedSafeOutputsJobLog.Print("Exposing upload_artifact outputs from handler manager") + outputs["upload_artifact_count"] = "${{ steps.process_safe_outputs.outputs.upload_artifact_count }}" + for i := range data.SafeOutputs.UploadArtifact.MaxUploads { + outputs[fmt.Sprintf("upload_artifact_slot_%d_tmp_id", i)] = fmt.Sprintf("${{ steps.process_safe_outputs.outputs.slot_%d_tmp_id }}", i) } } +} - // The outputs and permissions are configured in the handler manager section above +func addNamedSafeOutputHandlerOutputs(data *WorkflowData, outputs map[string]string) { if data.SafeOutputs.AddReviewer != nil { outputs["add_reviewer_reviewers_added"] = "${{ steps.process_safe_outputs.outputs.reviewers_added }}" } - - // The outputs and permissions are configured in the handler manager section above if data.SafeOutputs.AssignMilestone != nil { outputs["assign_milestone_milestone_assigned"] = "${{ steps.process_safe_outputs.outputs.milestone_assigned }}" } - - // The outputs and permissions are configured in the handler manager section above if data.SafeOutputs.AssignToUser != nil { outputs["assign_to_user_assigned"] = "${{ steps.process_safe_outputs.outputs.assigned }}" } - - // Individual named outputs for first-created items (enables workflow_call consumers to access results) if data.SafeOutputs.CreateIssues != nil { outputs["created_issue_number"] = "${{ steps.process_safe_outputs.outputs.created_issue_number }}" outputs["created_issue_url"] = "${{ steps.process_safe_outputs.outputs.created_issue_url }}" @@ -488,8 +458,6 @@ func (c *Compiler) buildSafeOutputsHandlerOutputsAndActionSteps(data *WorkflowDa outputs["call_workflow_name"] = "${{ steps.process_safe_outputs.outputs.call_workflow_name }}" outputs["call_workflow_payload"] = "${{ steps.process_safe_outputs.outputs.call_workflow_payload }}" } - - return steps, outputs, safeOutputStepNames, nil } // buildSafeOutputsJobFromParts finalizes the step list (app-token insertion, token invalidation, diff --git a/pkg/workflow/compiler_safe_outputs_job_test.go b/pkg/workflow/compiler_safe_outputs_job_test.go index b7fe1f796ac..10f6d82eccd 100644 --- a/pkg/workflow/compiler_safe_outputs_job_test.go +++ b/pkg/workflow/compiler_safe_outputs_job_test.go @@ -230,6 +230,36 @@ func TestBuildConsolidatedSafeOutputsJobConcurrencyGroup(t *testing.T) { } } +func TestAddHandlerManagerOutputs(t *testing.T) { + data := &WorkflowData{ + SafeOutputs: &SafeOutputsConfig{ + AssignToAgent: &AssignToAgentConfig{}, + CreateAgentSessions: &CreateAgentSessionConfig{}, + UploadArtifact: &UploadArtifactConfig{MaxUploads: 2}, + CreateIssues: &CreateIssuesConfig{}, + CreatePullRequests: &CreatePullRequestsConfig{}, + AddComments: &AddCommentsConfig{}, + }, + } + outputs := make(map[string]string) + + addHandlerManagerOutputs(data, outputs) + addNamedSafeOutputHandlerOutputs(data, outputs) + + assert.Equal(t, "${{ steps.process_safe_outputs.outputs.processed_count }}", outputs["process_safe_outputs_processed_count"]) + assert.Equal(t, "${{ steps.process_safe_outputs.outputs.assign_to_agent_assigned }}", outputs["assign_to_agent_assigned"]) + assert.Equal(t, "${{ steps.process_safe_outputs.outputs.assign_to_agent_assignment_errors }}", outputs["assign_to_agent_assignment_errors"]) + assert.Equal(t, "${{ steps.process_safe_outputs.outputs.assign_to_agent_assignment_error_count }}", outputs["assign_to_agent_assignment_error_count"]) + assert.Equal(t, "${{ steps.process_safe_outputs.outputs.session_number }}", outputs["create_agent_session_session_number"]) + assert.Equal(t, "${{ steps.process_safe_outputs.outputs.session_url }}", outputs["create_agent_session_session_url"]) + assert.Equal(t, "${{ steps.process_safe_outputs.outputs.upload_artifact_count }}", outputs["upload_artifact_count"]) + assert.Equal(t, "${{ steps.process_safe_outputs.outputs.slot_0_tmp_id }}", outputs["upload_artifact_slot_0_tmp_id"]) + assert.Equal(t, "${{ steps.process_safe_outputs.outputs.slot_1_tmp_id }}", outputs["upload_artifact_slot_1_tmp_id"]) + assert.Equal(t, "${{ steps.process_safe_outputs.outputs.created_issue_number }}", outputs["created_issue_number"]) + assert.Equal(t, "${{ steps.process_safe_outputs.outputs.created_pr_url }}", outputs["created_pr_url"]) + assert.Equal(t, "${{ steps.process_safe_outputs.outputs.comment_id }}", outputs["comment_id"]) +} + func TestBuildConsolidatedSafeOutputsJobNeedsIncludesConfiguredDependencies(t *testing.T) { compiler := NewCompiler() compiler.jobManager = NewJobManager()