From bd910293050087e03585a6b0ab7242be0a424686 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Apr 2026 21:46:22 +0000 Subject: [PATCH 1/2] Initial plan From 545e3a138195001b9a4e806c3021edf108ce752d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Apr 2026 22:01:02 +0000 Subject: [PATCH 2/2] feat(otel): add github.workflow_ref resource attribute to all OTel spans Adds the `github.workflow_ref` resource attribute (populated from `process.env.GITHUB_WORKFLOW_REF`) to both the setup span and conclusion span emitted by `send_otlp_span.cjs`. This allows OTel backends to identify exactly which workflow YAML file produced each span (e.g. `github/gh-aw/.github/workflows/otel-advisor.yml@refs/heads/main`) without relying on the non-standard `gh-aw.workflow.name` attribute. Closes # Agent-Logs-Url: https://github.com/github/gh-aw/sessions/4e93ef69-8210-4b49-80f8-eada6f428002 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/send_otlp_span.cjs | 8 ++++ actions/setup/js/send_otlp_span.test.cjs | 58 ++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/actions/setup/js/send_otlp_span.cjs b/actions/setup/js/send_otlp_span.cjs index 29fb64a739f..fa5322c4e7e 100644 --- a/actions/setup/js/send_otlp_span.cjs +++ b/actions/setup/js/send_otlp_span.cjs @@ -495,6 +495,7 @@ async function sendJobSetupSpan(options = {}) { const refName = process.env.GITHUB_REF_NAME || ""; const headRef = process.env.GITHUB_HEAD_REF || ""; const sha = process.env.GITHUB_SHA || ""; + const workflowRef = process.env.GITHUB_WORKFLOW_REF || ""; const attributes = [ buildAttr("gh-aw.job.name", jobName), @@ -533,6 +534,9 @@ async function sendJobSetupSpan(options = {}) { if (sha) { resourceAttributes.push(buildAttr("github.sha", sha)); } + if (workflowRef) { + resourceAttributes.push(buildAttr("github.workflow_ref", workflowRef)); + } resourceAttributes.push(buildAttr("deployment.environment", staged ? "staging" : "production")); const payload = buildOTLPPayload({ @@ -705,6 +709,7 @@ async function sendJobConclusionSpan(spanName, options = {}) { const refName = process.env.GITHUB_REF_NAME || ""; const headRef = process.env.GITHUB_HEAD_REF || ""; const sha = process.env.GITHUB_SHA || ""; + const workflowRef = process.env.GITHUB_WORKFLOW_REF || ""; // Agent conclusion is passed to downstream jobs via GH_AW_AGENT_CONCLUSION. // Values: "success", "failure", "timed_out", "cancelled", "skipped". @@ -819,6 +824,9 @@ async function sendJobConclusionSpan(spanName, options = {}) { if (sha) { resourceAttributes.push(buildAttr("github.sha", sha)); } + if (workflowRef) { + resourceAttributes.push(buildAttr("github.workflow_ref", workflowRef)); + } resourceAttributes.push(buildAttr("deployment.environment", staged ? "staging" : "production")); // Build OTel exception span events — one per error — following the diff --git a/actions/setup/js/send_otlp_span.test.cjs b/actions/setup/js/send_otlp_span.test.cjs index 50cd0325983..fb86464e261 100644 --- a/actions/setup/js/send_otlp_span.test.cjs +++ b/actions/setup/js/send_otlp_span.test.cjs @@ -921,6 +921,7 @@ describe("sendJobSetupSpan", () => { "GITHUB_REF_NAME", "GITHUB_HEAD_REF", "GITHUB_SHA", + "GITHUB_WORKFLOW_REF", "GH_AW_INFO_VERSION", "GH_AW_INFO_STAGED", ]; @@ -1286,6 +1287,34 @@ describe("sendJobSetupSpan", () => { expect(resourceKeys).not.toContain("github.sha"); }); + it("includes github.workflow_ref as resource attribute when GITHUB_WORKFLOW_REF is set", 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_REF = "owner/repo/.github/workflows/my-workflow.yml@refs/heads/main"; + + await sendJobSetupSpan(); + + const body = JSON.parse(mockFetch.mock.calls[0][1].body); + const resourceAttrs = body.resourceSpans[0].resource.attributes; + expect(resourceAttrs).toContainEqual({ key: "github.workflow_ref", value: { stringValue: "owner/repo/.github/workflows/my-workflow.yml@refs/heads/main" } }); + }); + + it("omits github.workflow_ref resource attribute when GITHUB_WORKFLOW_REF is not set", 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 sendJobSetupSpan(); + + const body = JSON.parse(mockFetch.mock.calls[0][1].body); + const resourceAttrs = body.resourceSpans[0].resource.attributes; + const resourceKeys = resourceAttrs.map(a => a.key); + expect(resourceKeys).not.toContain("github.workflow_ref"); + }); + it("includes github.actions.run_url as resource attribute when repository and run_id are set", async () => { const mockFetch = vi.fn().mockResolvedValue({ ok: true, status: 200, statusText: "OK" }); vi.stubGlobal("fetch", mockFetch); @@ -1554,6 +1583,7 @@ describe("sendJobConclusionSpan", () => { "GITHUB_REF_NAME", "GITHUB_HEAD_REF", "GITHUB_SHA", + "GITHUB_WORKFLOW_REF", "INPUT_JOB_NAME", "GH_AW_AGENT_CONCLUSION", "GH_AW_INFO_WORKFLOW_NAME", @@ -2914,6 +2944,34 @@ describe("sendJobConclusionSpan", () => { }); }); + it("includes github.workflow_ref as resource attribute when GITHUB_WORKFLOW_REF is set", 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_REF = "owner/repo/.github/workflows/my-workflow.yml@refs/heads/main"; + + await sendJobConclusionSpan("gh-aw.job.conclusion"); + + const body = JSON.parse(mockFetch.mock.calls[0][1].body); + const resourceAttrs = body.resourceSpans[0].resource.attributes; + expect(resourceAttrs).toContainEqual({ key: "github.workflow_ref", value: { stringValue: "owner/repo/.github/workflows/my-workflow.yml@refs/heads/main" } }); + }); + + it("omits github.workflow_ref resource attribute when GITHUB_WORKFLOW_REF is not set", 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 resourceAttrs = body.resourceSpans[0].resource.attributes; + const resourceKeys = resourceAttrs.map(a => a.key); + expect(resourceKeys).not.toContain("github.workflow_ref"); + }); + describe("staged / deployment.environment", () => { let readFileSpy;