diff --git a/actions/setup/js/send_otlp_span.cjs b/actions/setup/js/send_otlp_span.cjs index 1b102e37f98..21351fd5585 100644 --- a/actions/setup/js/send_otlp_span.cjs +++ b/actions/setup/js/send_otlp_span.cjs @@ -1329,9 +1329,7 @@ async function sendJobConclusionSpan(spanName, options = {}) { if (detectionReason) { attributes.push(buildAttr("gh-aw.detection.reason", detectionReason)); } - if (spanName === "gh-aw.conclusion.conclusion") { - attributes.push(buildAttr("gh-aw.otlp.export_errors", readOTLPExportErrorCount())); - } + attributes.push(buildAttr("gh-aw.otlp.export_errors", readOTLPExportErrorCount())); if (errorMessages.length > 0) { attributes.push(buildAttr("gh-aw.error.count", outputErrors.length)); attributes.push(buildAttr("gh-aw.error.messages", errorMessages.join(" | "))); diff --git a/actions/setup/js/send_otlp_span.test.cjs b/actions/setup/js/send_otlp_span.test.cjs index d3b0619cc32..33772ec4763 100644 --- a/actions/setup/js/send_otlp_span.test.cjs +++ b/actions/setup/js/send_otlp_span.test.cjs @@ -2792,6 +2792,28 @@ describe("sendJobConclusionSpan", () => { expect(attrs["gh-aw.otlp.export_errors"]).toBe(3); }); + it("emits gh-aw.otlp.export_errors on non-conclusion job spans", async () => { + const mockFetch = vi.fn().mockResolvedValue({ ok: true, status: 200, statusText: "OK" }); + vi.stubGlobal("fetch", mockFetch); + + process.env.GH_AW_OTLP_ENDPOINTS = JSON.stringify([{ url: "https://traces.example.com" }]); + + const readFileSpy = vi.spyOn(fs, "readFileSync").mockImplementation(filePath => { + if (filePath === "/tmp/gh-aw/otlp-export-errors.count") { + return "2"; + } + throw Object.assign(new Error("ENOENT"), { code: "ENOENT" }); + }); + + await sendJobConclusionSpan("gh-aw.agent.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.intValue ?? a.value.stringValue])); + expect(attrs["gh-aw.otlp.export_errors"]).toBe(2); + }); + it("omits effective_tokens attribute when GH_AW_EFFECTIVE_TOKENS is absent", async () => { const mockFetch = vi.fn().mockResolvedValue({ ok: true, status: 200, statusText: "OK" }); vi.stubGlobal("fetch", mockFetch);