From 9cbe9d569005a8c8b86d3a2d38c99e7ac35b3f50 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:47:44 +0000 Subject: [PATCH 1/2] Initial plan From 072ca7c644dea8ef2ac37a7ccae5d4e3706c473a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:54:30 +0000 Subject: [PATCH 2/2] fix: add env-var fallbacks for gh-aw.workflow.name in conclusion spans (#1088) Agent-Logs-Url: https://github.com/github/gh-aw/sessions/a17346bf-8708-478f-b2cf-77ba64d49dd3 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/send_otlp_span.cjs | 2 +- actions/setup/js/send_otlp_span.test.cjs | 71 ++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/actions/setup/js/send_otlp_span.cjs b/actions/setup/js/send_otlp_span.cjs index 3f85a66380..ab72514506 100644 --- a/actions/setup/js/send_otlp_span.cjs +++ b/actions/setup/js/send_otlp_span.cjs @@ -681,7 +681,7 @@ async function sendJobConclusionSpan(spanName, options = {}) { const rawParentSpanId = (process.env.GITHUB_AW_OTEL_PARENT_SPAN_ID || "").trim().toLowerCase(); const parentSpanId = isValidSpanId(rawParentSpanId) ? rawParentSpanId : ""; - const workflowName = awInfo.workflow_name || ""; + const workflowName = awInfo.workflow_name || process.env.GH_AW_INFO_WORKFLOW_NAME || process.env.GITHUB_WORKFLOW || ""; const engineId = awInfo.engine_id || ""; const model = awInfo.model || ""; const staged = awInfo.staged === true; diff --git a/actions/setup/js/send_otlp_span.test.cjs b/actions/setup/js/send_otlp_span.test.cjs index 77067e13ec..cfcbcd837f 100644 --- a/actions/setup/js/send_otlp_span.test.cjs +++ b/actions/setup/js/send_otlp_span.test.cjs @@ -1500,6 +1500,8 @@ describe("sendJobConclusionSpan", () => { "GITHUB_SHA", "INPUT_JOB_NAME", "GH_AW_AGENT_CONCLUSION", + "GH_AW_INFO_WORKFLOW_NAME", + "GITHUB_WORKFLOW", ]; let mkdirSpy, appendSpy; @@ -1584,6 +1586,75 @@ describe("sendJobConclusionSpan", () => { expect(attrs["gh-aw.run.attempt"]).toBe("1"); }); + it("reads gh-aw.workflow.name from aw_info.json when present", async () => { + const mockFetch = vi.fn().mockResolvedValue({ ok: true, status: 200, statusText: "OK" }); + vi.stubGlobal("fetch", mockFetch); + + process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "https://traces.example.com"; + process.env.GH_AW_INFO_WORKFLOW_NAME = "env-workflow"; + process.env.GITHUB_WORKFLOW = "github-workflow"; + + const readFileSpy = vi.spyOn(fs, "readFileSync").mockImplementation(filePath => { + if (filePath === "/tmp/gh-aw/aw_info.json") { + return JSON.stringify({ workflow_name: "aw-info-workflow" }); + } + throw Object.assign(new Error("ENOENT"), { code: "ENOENT" }); + }); + + await sendJobConclusionSpan("gh-aw.job.conclusion"); + readFileSpy.mockRestore(); + + const body = JSON.parse(mockFetch.mock.calls[0][1].body); + const span = body.resourceSpans[0].scopeSpans[0].spans[0]; + const attrs = Object.fromEntries(span.attributes.map(a => [a.key, a.value.stringValue])); + expect(attrs["gh-aw.workflow.name"]).toBe("aw-info-workflow"); + }); + + it("falls back to GH_AW_INFO_WORKFLOW_NAME when aw_info.json is absent", async () => { + const mockFetch = vi.fn().mockResolvedValue({ ok: true, status: 200, statusText: "OK" }); + vi.stubGlobal("fetch", mockFetch); + + process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "https://traces.example.com"; + process.env.GH_AW_INFO_WORKFLOW_NAME = "env-workflow"; + process.env.GITHUB_WORKFLOW = "github-workflow"; + + await sendJobConclusionSpan("gh-aw.job.conclusion"); + + const body = JSON.parse(mockFetch.mock.calls[0][1].body); + const span = body.resourceSpans[0].scopeSpans[0].spans[0]; + const attrs = Object.fromEntries(span.attributes.map(a => [a.key, a.value.stringValue])); + expect(attrs["gh-aw.workflow.name"]).toBe("env-workflow"); + }); + + it("falls back to GITHUB_WORKFLOW when aw_info.json and GH_AW_INFO_WORKFLOW_NAME are absent", async () => { + const mockFetch = vi.fn().mockResolvedValue({ ok: true, status: 200, statusText: "OK" }); + vi.stubGlobal("fetch", mockFetch); + + process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "https://traces.example.com"; + process.env.GITHUB_WORKFLOW = "github-workflow"; + + await sendJobConclusionSpan("gh-aw.job.conclusion"); + + const body = JSON.parse(mockFetch.mock.calls[0][1].body); + const span = body.resourceSpans[0].scopeSpans[0].spans[0]; + const attrs = Object.fromEntries(span.attributes.map(a => [a.key, a.value.stringValue])); + expect(attrs["gh-aw.workflow.name"]).toBe("github-workflow"); + }); + + it("sets gh-aw.workflow.name to empty string when all sources are absent", async () => { + const mockFetch = vi.fn().mockResolvedValue({ ok: true, status: 200, statusText: "OK" }); + vi.stubGlobal("fetch", mockFetch); + + process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "https://traces.example.com"; + + await sendJobConclusionSpan("gh-aw.job.conclusion"); + + const body = JSON.parse(mockFetch.mock.calls[0][1].body); + const span = body.resourceSpans[0].scopeSpans[0].spans[0]; + const attrs = Object.fromEntries(span.attributes.map(a => [a.key, a.value.stringValue])); + expect(attrs["gh-aw.workflow.name"]).toBe(""); + }); + it("includes effective_tokens attribute when GH_AW_EFFECTIVE_TOKENS is set", async () => { const mockFetch = vi.fn().mockResolvedValue({ ok: true, status: 200, statusText: "OK" }); vi.stubGlobal("fetch", mockFetch);