Fix custom safe-output job types not recognized in safe_outputs job#20682
Fix custom safe-output job types not recognized in safe_outputs job#20682
safe_outputs job#20682Conversation
… to safe_outputs job step The handler manager step in the safe_outputs job was missing the GH_AW_SAFE_OUTPUT_JOBS env var, which tells it which message types are handled by custom safe-output job steps and should be silently skipped rather than reported as "No handler loaded". This was causing Smoke Copilot (and other workflows with custom safe jobs) to fail with:⚠️ No handler loaded for message type 'send_slack_message' ##[error]1 safe output(s) failed The GH_AW_SAFE_OUTPUT_JOBS was only being set in the conclusion job, not in the safe_outputs job itself. Now buildHandlerManagerStep also includes it when custom safe jobs are defined in safe-outputs.jobs. Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
safe_outputs job
|
✅ All tools validated successfully! Agent Container Smoke Test confirms agent container is ready. |
|
🌑 The shadows whisper... Smoke Codex failed. The oracle requires further meditation... |
|
📰 BREAKING: Smoke Copilot is now investigating this pull request. Sources say the story is developing... |
|
🎬 THE END — Smoke Claude MISSION: ACCOMPLISHED! The hero saves the day! ✨ |
Agent Container Tool Check
Result: 12/12 tools available ✅ Overall Status: PASS
|
|
Smoke Test §23007859266 —
Overall:
|
There was a problem hiding this comment.
Reviewed PR #20682 which fixes GH_AW_SAFE_OUTPUT_JOBS not being set in the safe_outputs job. The core fix is correct — buildCustomSafeOutputJobsJSON is a clean helper that normalizes job names and emits the env var. Two minor observations: 1) the JSON sort trick doesn't work since Go maps have no iteration order, and 2) the debug log level for this env var addition is appropriate but could use a note. Good test coverage added.
📰 BREAKING: Report filed by Smoke Copilot
| for jobName := range data.SafeOutputs.Jobs { | ||
| normalizedName := stringutil.NormalizeSafeOutputIdentifier(jobName) | ||
| jobMapping[normalizedName] = "" | ||
| } |
There was a problem hiding this comment.
The ordered map is built after sorting keys, but Go maps have no guaranteed iteration order, so marshaling ordered directly won't preserve the sorted order. Consider using a sorted slice of key-value pairs or a custom JSON marshaler to ensure deterministic output. Alternatively, just marshal jobMapping directly — the sort loop doesn't help here.
| steps = append(steps, fmt.Sprintf(" GH_AW_SAFE_OUTPUT_JOBS: %q\n", customJobsJSON)) | ||
| consolidatedSafeOutputsStepsLog.Print("Added GH_AW_SAFE_OUTPUT_JOBS env var for custom safe job types") | ||
| } | ||
|
|
There was a problem hiding this comment.
The log message says "Added GH_AW_SAFE_OUTPUT_JOBS env var for custom safe job types" but uses consolidatedSafeOutputsStepsLog.Print (debug-level). Consider upgrading to a higher-visibility log or adding a comment explaining this is intentionally debug-only, since this is a new feature that was hard to diagnose when missing.
|
📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤 |
Smoke Test Results — Run §23007859311Overall: PARTIAL ✅ (all non-skipped tests passed)
|
There was a problem hiding this comment.
💥 Automated smoke test review - all systems nominal!
💥 [THE END] — Illustrated by Smoke Claude
There was a problem hiding this comment.
Pull request overview
Ensures custom safe-output job types are recognized by the safe_outputs handler-manager step by emitting GH_AW_SAFE_OUTPUT_JOBS in that step when custom jobs are configured, preventing spurious “No handler loaded …” warnings/failures.
Changes:
- Emit
GH_AW_SAFE_OUTPUT_JOBSinbuildHandlerManagerStepbased onsafe-outputs.jobs, using a new helper to build the JSON mapping. - Add unit tests covering presence/absence of
GH_AW_SAFE_OUTPUT_JOBSin the handler manager step. - Regenerate multiple workflow lock files to include the new env var.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| pkg/workflow/compiler_safe_outputs_steps.go | Adds GH_AW_SAFE_OUTPUT_JOBS emission to the handler-manager step and introduces a helper to build the custom-jobs JSON map. |
| pkg/workflow/compiler_safe_outputs_steps_test.go | Adds test cases verifying GH_AW_SAFE_OUTPUT_JOBS is included only when custom safe jobs exist. |
| .github/workflows/smoke-copilot.lock.yml | Lockfile regeneration reflecting the new env var in the compiled workflow. |
| .github/workflows/smoke-copilot-arm.lock.yml | Lockfile regeneration reflecting the new env var in the compiled workflow. |
| .github/workflows/daily-choice-test.lock.yml | Lockfile regeneration reflecting the new env var in the compiled workflow. |
| .github/workflows/mcp-inspector.lock.yml | Lockfile regeneration reflecting the new env var in the compiled workflow. |
| .github/workflows/notion-issue-summary.lock.yml | Lockfile regeneration reflecting the new env var in the compiled workflow. |
💡 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.
| // Sort keys for deterministic output | ||
| keys := make([]string, 0, len(jobMapping)) | ||
| for k := range jobMapping { | ||
| keys = append(keys, k) | ||
| } | ||
| sort.Strings(keys) | ||
|
|
||
| ordered := make(map[string]string, len(keys)) | ||
| for _, k := range keys { | ||
| ordered[k] = jobMapping[k] | ||
| } |
There was a problem hiding this comment.
The key-sorting/ordered map logic here is misleading: Go maps don’t preserve insertion order, and encoding/json already sorts string map keys deterministically during marshaling. Consider removing the keys/sort.Strings/ordered block (and the sort import) and marshal jobMapping directly (or, if you truly need a custom order, marshal from a sorted slice/struct instead of a map).
GH_AW_SAFE_OUTPUT_JOBSwas only set in the conclusion job, not in thesafe_outputsjob itself. When the handler manager ranprocessMessages, it found no custom job types registered and emitted⚠️ No handler loaded for message type 'send_slack_message'— failing the workflow even though the actual agent work succeeded and a dedicatedsend_slack_messagejob handled the output correctly.Changes
compiler_safe_outputs_steps.go:buildHandlerManagerStepnow emitsGH_AW_SAFE_OUTPUT_JOBSwhendata.SafeOutputs.Jobsis non-empty, matching the same JSON format already used by the conclusion jobbuildCustomSafeOutputJobsJSON(new helper): extracts normalized job names fromSafeOutputs.Jobsand builds{"send_slack_message": "", ...}— empty-value mapping signals "handled by custom job, skip silently"smoke-copilot,smoke-copilot-arm,daily-choice-test,mcp-inspector,notion-issue-summaryWarning
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:
https://api.github.com/graphql/usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw(http block)/usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw i4Sy5_5pQ60B(http block)/usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw -pack /home/REDACTED/work/gh-aw/gh-aw/cmd/gh-aw/main.go(http block)https://api.github.com/repos/actions/ai-inference/git/ref/tags/v1/usr/bin/gh gh api /repos/actions/ai-inference/git/ref/tags/v1 --jq .object.sha --get remote.origin.url e/git 144522c24eae70bagit GO111MODULE 64/bin/go e/git rev-�� om/owner/repo.git om/owner/repo.git /usr/bin/git 8808184/b405/_pkgit GO111MODULE 64/bin/go git(http block)https://api.github.com/repos/actions/checkout/git/ref/tags/v3/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v3 --jq .object.sha -json(http block)https://api.github.com/repos/actions/checkout/git/ref/tags/v5/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE ache/go/1.25.0/x64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha GOMODCACHE go /usr/bin/git -json GO111MODULE 64/bin/go git rev-�� --git-dir sh /usr/bin/git ub/workflows GOPROXY 64/bin/go /usr/bin/git(http block)/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha --show-toplevel go /usr/bin/git 18/001/test-simpgit GO111MODULE 64/pkg/tool/linu--show-toplevel git rev-�� --show-toplevel 64/pkg/tool/linux_amd64/vet /usr/bin/git 0148/001/stabiligit GO111MODULE ache/go/1.25.0/x--show-toplevel git(http block)https://api.github.com/repos/actions/checkout/git/ref/tags/v6/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha k/gh-aw/gh-aw/pkg/styles/theme.go k/gh-aw/gh-aw/pkg/styles/theme_test.go /usr/bin/gh -json GO111MODULE 64/bin/go gh api /repos/github/gh-aw/git/ref/tags/v2.0.0 l(http block)/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha --show-toplevel -tests /usr/bin/git -json GO111MODULE 64/bin/go git rev-�� --show-toplevel go ache/node/24.14.0/x64/bin/node ck '**/*.cjs' '*git GO111MODULE 64/bin/go ache/node/24.14.0/x64/bin/node(http block)/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha --show-toplevel go /usr/bin/git 3301387218 GO111MODULE 64/bin/go git rev-�� --show-toplevel go /usr/bin/git -json GO111MODULE sole.test git(http block)https://api.github.com/repos/actions/github-script/git/ref/tags/v8/usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha GOSUMDB GOWORK 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)/usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha go1.25.0 -c=4 -nolocalimports -importcfg /tmp/go-build3003734501/b389/importcfg -pack /tmp/go-build3003734501/b389/_testmain.go env ternal/tools/actGOINSECURE GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE ache/go/1.25.0/x-buildtags(http block)/usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha jzOPaIoP7 GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE ortcfg env 5a8a5bbe22444b35GOINSECURE GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE ache/go/1.25.0/x-buildtags(http block)https://api.github.com/repos/actions/setup-go/git/ref/tags/v4/usr/bin/gh gh api /repos/actions/setup-go/git/ref/tags/v4 --jq .object.sha /tmp/gh-aw-test-runs/20260312-142513-43870/test-3390384542/.github/workflows config /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/compile remote.origin.urgit GO111MODULE 64/bin/go /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/compile -o /tmp/go-build3003734501/b422/_pkg_.a -trimpath /opt/hostedtoolcache/node/24.14.0/x64/bin/node -p main -lang=go1.25 node(http block)https://api.github.com/repos/actions/setup-node/git/ref/tags/v4/usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v4 --jq .object.sha --show-toplevel -tests /usr/bin/infocmp -json GO111MODULE 64/bin/go infocmp -1 xterm-color go(http block)https://api.github.com/repos/actions/upload-artifact/git/ref/tags/v4/usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v4 --jq .object.sha 498242633/001 GO111MODULE 3734501/b402/logger.test GOINSECURE GOMOD GOMODCACHE 3734501/b402/logger.test e=/t�� t0 GO111MODULE(http block)https://api.github.com/repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b/usr/bin/gh gh api /repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b --jq .object.sha(http block)https://api.github.com/repos/github/gh-aw/actions/runs/1/artifacts/usr/bin/gh gh run download 1 --dir test-logs/run-1 go x_amd64/compile tierignore GO111MODULE 64/bin/go x_amd64/compile env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/actions/runs/12345/artifacts/usr/bin/gh gh run download 12345 --dir test-logs/run-12345 scripts/**/*.js x_amd64/link -d GO111MODULE 64/bin/go x_amd64/link env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE Q2/7j35zc_qKAYS-4QgGo9J/DPaemlwGNfMZfLzihsQh(http block)https://api.github.com/repos/github/gh-aw/actions/runs/12346/artifacts/usr/bin/gh gh run download 12346 --dir test-logs/run-12346 scripts/**/*.js x_amd64/vet -d GO111MODULE 64/bin/go x_amd64/vet env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/actions/runs/2/artifacts/usr/bin/gh gh run download 2 --dir test-logs/run-2 go x_amd64/link -d GO111MODULE 64/bin/go x_amd64/link env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE DH/9DUy4TQr_9yiBZdgGjoL/s75lMeBe-test.v=true(http block)https://api.github.com/repos/github/gh-aw/actions/runs/3/artifacts/usr/bin/gh gh run download 3 --dir test-logs/run-3 GOMOD 64/bin/go -d GO111MODULE 64/bin/go go estl�� -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/actions/runs/4/artifacts/usr/bin/gh gh run download 4 --dir test-logs/run-4 go x_amd64/vet tierignore GO111MODULE 64/bin/go x_amd64/vet env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/actions/runs/5/artifacts/usr/bin/gh gh run download 5 --dir test-logs/run-5 GOMOD 64/bin/go tierignore GO111MODULE 64/bin/go go estl�� -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/actions/workflows/usr/bin/gh gh workflow list --json name,state,path -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/git/ref/tags/a70c5eada06553e3510ac27f2c3bda9d3705bccb/usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/a70c5eada06553e3510ac27f2c3bda9d3705bccb --jq .object.sha(http block)https://api.github.com/repos/github/gh-aw/git/ref/tags/v1.0.0/usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v1.0.0 --jq .object.sha -json GO111MODULE datedAt,event,headBranch,headSha,displayTitle GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/git/ref/tags/v1.2.3/usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v1.2.3 --jq .object.sha heck '**/*.cjs' GOINSECURE GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go m/_n�� -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/git/ref/tags/v2.0.0/usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v2.0.0 --jq .object.sha heck '**/*.cjs' GOINSECURE GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go m/_n�� -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)/usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v2.0.0 --jq .object.sha -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE erignore m/_n�� -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)/usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v2.0.0 --jq .object.sha -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE erignore env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/git/ref/tags/v3.0.0/usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v3.0.0 --jq .object.sha -json GO111MODULE 64/bin/go GOINSECURE GOMOD erignore go m/_n�� -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/githubnext/agentics/git/ref/tags//usr/bin/gh gh api /repos/githubnext/agentics/git/ref/tags/# --jq .object.sha(http block)https://api.github.com/repos/nonexistent/action/git/ref/tags/v999.999.999/usr/bin/gh gh api /repos/nonexistent/action/git/ref/tags/v999.999.999 --jq .object.sha -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env 3539538574/.github/workflows GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/nonexistent/repo/actions/runs/12345/usr/bin/gh gh run view 12345 --repo nonexistent/repo --json status,conclusion -p main -lang=go1.25 go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/owner/repo/actions/workflows/usr/bin/gh gh workflow list --json name,state,path --repo owner/repo 64/bin/go GOINSECURE GOMOD FFiles,SFiles,Sw-json go ache�� -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)/usr/bin/gh gh workflow list --json name,state,path --repo owner/repo 64/bin/go GOINSECURE GOMOD GOMODCACHE go env heck '**/*.cjs' GOINSECURE GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/owner/repo/contents/file.md/tmp/go-build3003734501/b383/cli.test /tmp/go-build3003734501/b383/cli.test -test.testlogfile=/tmp/go-build3003734501/b383/testlog.txt -test.paniconexit0 -test.v=true -test.parallel=4 -test.timeout=10m0s -test.run=^Test -test.short=true GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/test-owner/test-repo/actions/secrets/usr/bin/gh gh api /repos/test-owner/test-repo/actions/secrets --jq .secrets[].name -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)If you need me to access, download, or install something from one of these locations, you can either:
🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.
✨ PR Review Safe Output Test - Run 23007859311