From fb86e59775def7bf3ebe39deb200d2061e58a17c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 2 May 2026 09:47:00 +0000 Subject: [PATCH 1/2] Initial plan From e56d8a73246f00f4065dbaf17b2ded8d1ac1853e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 2 May 2026 09:53:54 +0000 Subject: [PATCH 2/2] feat: add gh-aw.trigger.comment_id to setup and conclusion OTLP spans Agent-Logs-Url: https://github.com/github/gh-aw/sessions/3d629da3-870a-4475-b751-2807361c4eed Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/send_otlp_span.cjs | 11 ++++-- actions/setup/js/send_otlp_span.test.cjs | 44 ++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/actions/setup/js/send_otlp_span.cjs b/actions/setup/js/send_otlp_span.cjs index 3390148cc6..a9b01bd808 100644 --- a/actions/setup/js/send_otlp_span.cjs +++ b/actions/setup/js/send_otlp_span.cjs @@ -536,9 +536,10 @@ function isValidSpanId(id) { * trace ID so that dispatched child workflows share the parent's OTLP trace; * `context.otel_parent_span_id` is used as the parent span ID so the child's setup span * is properly nested under the parent's setup span in the trace hierarchy; and - * `context.item_type`, `context.item_number`, and `context.trigger_label` are emitted as - * `gh-aw.trigger.item_type`, `gh-aw.trigger.item_number`, and `gh-aw.trigger.label` - * attributes so every span can be linked back to the GitHub item that triggered the workflow + * `context.item_type`, `context.item_number`, `context.trigger_label`, and `context.comment_id` + * are emitted as `gh-aw.trigger.item_type`, `gh-aw.trigger.item_number`, `gh-aw.trigger.label`, + * and `gh-aw.trigger.comment_id` attributes so every span can be linked back to the GitHub item + * (and specific comment) that triggered the workflow * * @param {SendJobSetupSpanOptions} [options] * @returns {Promise<{ traceId: string, spanId: string }>} The trace and span IDs used. @@ -574,6 +575,7 @@ async function sendJobSetupSpan(options = {}) { const itemType = typeof awInfo.context?.item_type === "string" ? awInfo.context.item_type : ""; const itemNumber = typeof awInfo.context?.item_number === "string" ? awInfo.context.item_number : ""; const triggerLabel = typeof awInfo.context?.trigger_label === "string" ? awInfo.context.trigger_label : ""; + const commentId = typeof awInfo.context?.comment_id === "string" ? awInfo.context.comment_id : ""; const traceId = optionsTraceId || inputTraceId || contextTraceId || generateTraceId(); @@ -633,6 +635,7 @@ async function sendJobSetupSpan(options = {}) { if (itemType) attributes.push(buildAttr("gh-aw.trigger.item_type", itemType)); if (itemNumber) attributes.push(buildAttr("gh-aw.trigger.item_number", itemNumber)); if (triggerLabel) attributes.push(buildAttr("gh-aw.trigger.label", triggerLabel)); + if (commentId) attributes.push(buildAttr("gh-aw.trigger.comment_id", commentId)); // Include experiment assignments so each span can be correlated with the // A/B variant selected for this run (written by pick_experiment.cjs). @@ -832,6 +835,7 @@ async function sendJobConclusionSpan(spanName, options = {}) { const itemType = typeof awInfo.context?.item_type === "string" ? awInfo.context.item_type : ""; const itemNumber = typeof awInfo.context?.item_number === "string" ? awInfo.context.item_number : ""; const triggerLabel = typeof awInfo.context?.trigger_label === "string" ? awInfo.context.trigger_label : ""; + const commentId = typeof awInfo.context?.comment_id === "string" ? awInfo.context.comment_id : ""; const jobName = process.env.INPUT_JOB_NAME || ""; const runId = process.env.GITHUB_RUN_ID || ""; const runAttempt = awInfo.run_attempt || process.env.GITHUB_RUN_ATTEMPT || "1"; @@ -893,6 +897,7 @@ async function sendJobConclusionSpan(spanName, options = {}) { if (itemType) attributes.push(buildAttr("gh-aw.trigger.item_type", itemType)); if (itemNumber) attributes.push(buildAttr("gh-aw.trigger.item_number", itemNumber)); if (triggerLabel) attributes.push(buildAttr("gh-aw.trigger.label", triggerLabel)); + if (commentId) attributes.push(buildAttr("gh-aw.trigger.comment_id", commentId)); if (!isNaN(effectiveTokens) && effectiveTokens > 0) { attributes.push(buildAttr("gh-aw.effective_tokens", effectiveTokens)); } diff --git a/actions/setup/js/send_otlp_span.test.cjs b/actions/setup/js/send_otlp_span.test.cjs index 5687b33114..d5b031b238 100644 --- a/actions/setup/js/send_otlp_span.test.cjs +++ b/actions/setup/js/send_otlp_span.test.cjs @@ -1596,6 +1596,7 @@ describe("sendJobSetupSpan", () => { expect(span.attributes).toContainEqual({ key: "gh-aw.trigger.item_number", value: { stringValue: "42" } }); const keys = span.attributes.map(a => a.key); expect(keys).not.toContain("gh-aw.trigger.label"); + expect(keys).not.toContain("gh-aw.trigger.comment_id"); }); it("emits gh-aw.trigger.label when trigger_label is non-empty", async () => { @@ -1620,6 +1621,26 @@ describe("sendJobSetupSpan", () => { expect(span.attributes).toContainEqual({ key: "gh-aw.trigger.label", value: { stringValue: "copilot" } }); }); + it("emits gh-aw.trigger.comment_id when comment_id is non-empty", 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"; + + readFileSpy.mockImplementation(filePath => { + if (filePath === "/tmp/gh-aw/aw_info.json") { + return JSON.stringify({ context: { item_type: "pull_request", item_number: "99", trigger_label: "copilot", comment_id: "123456789" } }); + } + throw Object.assign(new Error("ENOENT"), { code: "ENOENT" }); + }); + + await sendJobSetupSpan(); + + const body = JSON.parse(mockFetch.mock.calls[0][1].body); + const span = body.resourceSpans[0].scopeSpans[0].spans[0]; + expect(span.attributes).toContainEqual({ key: "gh-aw.trigger.comment_id", value: { stringValue: "123456789" } }); + }); + it("omits trigger attributes when aw_info.json is absent", async () => { const mockFetch = vi.fn().mockResolvedValue({ ok: true, status: 200, statusText: "OK" }); vi.stubGlobal("fetch", mockFetch); @@ -1634,6 +1655,7 @@ describe("sendJobSetupSpan", () => { expect(keys).not.toContain("gh-aw.trigger.item_type"); expect(keys).not.toContain("gh-aw.trigger.item_number"); expect(keys).not.toContain("gh-aw.trigger.label"); + expect(keys).not.toContain("gh-aw.trigger.comment_id"); }); }); @@ -3484,6 +3506,7 @@ describe("sendJobConclusionSpan", () => { expect(span.attributes).toContainEqual({ key: "gh-aw.trigger.item_number", value: { stringValue: "7" } }); const keys = span.attributes.map(a => a.key); expect(keys).not.toContain("gh-aw.trigger.label"); + expect(keys).not.toContain("gh-aw.trigger.comment_id"); }); it("emits gh-aw.trigger.label when trigger_label is non-empty", async () => { @@ -3508,6 +3531,26 @@ describe("sendJobConclusionSpan", () => { expect(span.attributes).toContainEqual({ key: "gh-aw.trigger.label", value: { stringValue: "bug" } }); }); + it("emits gh-aw.trigger.comment_id when comment_id is non-empty", 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"; + + readFileSpy.mockImplementation(filePath => { + if (filePath === "/tmp/gh-aw/aw_info.json") { + return JSON.stringify({ context: { item_type: "pull_request", item_number: "456", trigger_label: "bug", comment_id: "987654321" } }); + } + throw Object.assign(new Error("ENOENT"), { code: "ENOENT" }); + }); + + 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]; + expect(span.attributes).toContainEqual({ key: "gh-aw.trigger.comment_id", value: { stringValue: "987654321" } }); + }); + it("omits trigger attributes when aw_info.json is absent", async () => { const mockFetch = vi.fn().mockResolvedValue({ ok: true, status: 200, statusText: "OK" }); vi.stubGlobal("fetch", mockFetch); @@ -3522,6 +3565,7 @@ describe("sendJobConclusionSpan", () => { expect(keys).not.toContain("gh-aw.trigger.item_type"); expect(keys).not.toContain("gh-aw.trigger.item_number"); expect(keys).not.toContain("gh-aw.trigger.label"); + expect(keys).not.toContain("gh-aw.trigger.comment_id"); }); });