Skip to content

feat(otel): add github.actions.run_url resource attribute to all spans#24691

Merged
pelikhan merged 3 commits intomainfrom
copilot/otel-advisor-add-github-actions-run-url
Apr 5, 2026
Merged

feat(otel): add github.actions.run_url resource attribute to all spans#24691
pelikhan merged 3 commits intomainfrom
copilot/otel-advisor-add-github-actions-run-url

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 5, 2026

Both sendJobSetupSpan and sendJobConclusionSpan emitted github.repository and github.run_id as separate resource attributes but never a precomputed run URL — requiring manual assembly in trace UIs and producing wrong URLs on GHES where GITHUB_SERVER_URLhttps://github.com.

Changes

  • send_otlp_span.cjs: In both sendJobSetupSpan and sendJobConclusionSpan, reads GITHUB_SERVER_URL (fallback: https://github.com) and appends github.actions.run_url to resourceAttributes when both repository and runId are non-empty:
const serverUrl = process.env.GITHUB_SERVER_URL || "https://github.com";
const resourceAttributes = [buildAttr("github.repository", repository), buildAttr("github.run_id", runId)];
if (repository && runId) {
  resourceAttributes.push(
    buildAttr("github.actions.run_url", `${serverUrl}/${repository}/actions/runs/${runId}`)
  );
}
  • send_otlp_span.test.cjs: Adds 6 new tests (3 per function) covering default URL construction, custom GITHUB_SERVER_URL (GHES), and omission when either field is absent.

Adds github.actions.run_url to the resource attributes of both
sendJobSetupSpan and sendJobConclusionSpan. The URL uses
GITHUB_SERVER_URL (falling back to https://github.com) so it
correctly handles GitHub Enterprise Server installations. The
attribute is only added when both repository and runId are non-empty.

Tests added for: default URL, custom GITHUB_SERVER_URL (GHES), and
omission when fields are missing.

Closes #(issue)

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/5833a1d6-639c-4678-a409-4bf34bf6e15f

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Add github.actions.run_url resource attribute to all spans feat(otel): add github.actions.run_url resource attribute to all spans Apr 5, 2026
Copilot AI requested a review from pelikhan April 5, 2026 09:04
const serverUrl = process.env.GITHUB_SERVER_URL || "https://github.com";
const resourceAttributes = [buildAttr("github.repository", repository), buildAttr("github.run_id", runId)];
if (repository && runId) {
resourceAttributes.push(buildAttr("github.actions.run_url", `${serverUrl}/${repository}/actions/runs/${runId}`));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot use existing helper function to create run url

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in edca181. Now imports and uses buildWorkflowRunUrl from workflow_metadata_helpers.cjs in both span functions, splitting repository (owner/repo) into { owner, repo } as expected by the helper.

Copilot AI requested a review from pelikhan April 5, 2026 09:10
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 5, 2026

Hey @Copilot 👋 — great work adding the github.actions.run_url resource attribute to the OTel spans! Fixing the GHES URL issue and making trace UIs link directly to the Actions run is a genuinely useful improvement.

This PR is well-structured and aligns with the project's contribution process. Here's a quick summary of the check results:

Check Result
On-topic ✅ Yes — telemetry enhancement to existing OTel span logic
Follows process ✅ Yes — authored by Copilot coding agent, assigned to core team member
Focused ✅ Yes — single cohesive change
New dependencies ✅ None
Tests ✅ 6 new test cases covering default URL, GHES URL, and missing-field omission
Description ✅ Clear problem statement and code examples
Diff size 116 lines (8 source + 108 tests)

Verdict: 🟢 Aligned — this PR looks ready for maintainer review once CI checks pass and the draft status is removed.

Generated by Contribution Check · ● 1.9M ·

@github-actions github-actions bot added the lgtm label Apr 5, 2026
@pelikhan pelikhan marked this pull request as ready for review April 5, 2026 09:12
Copilot AI review requested due to automatic review settings April 5, 2026 09:12
@pelikhan pelikhan merged commit 8656b52 into main Apr 5, 2026
51 checks passed
@pelikhan pelikhan deleted the copilot/otel-advisor-add-github-actions-run-url branch April 5, 2026 09:12
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a precomputed GitHub Actions workflow run URL (github.actions.run_url) as an OTEL resource attribute for job setup and conclusion spans, including support for GitHub Enterprise Server via GITHUB_SERVER_URL.

Changes:

  • Emit github.actions.run_url in sendJobSetupSpan and sendJobConclusionSpan when GITHUB_REPOSITORY and GITHUB_RUN_ID are present.
  • Reuse buildWorkflowRunUrl helper for run URL construction.
  • Add unit tests covering default github.com, GHES (GITHUB_SERVER_URL), and omission when inputs are missing.
Show a summary per file
File Description
actions/setup/js/send_otlp_span.cjs Adds github.actions.run_url resource attribute generation for setup/conclusion spans.
actions/setup/js/send_otlp_span.test.cjs Adds tests validating run URL emission and GHES/default behavior.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (1)

actions/setup/js/send_otlp_span.test.cjs:1175

  • Same env-leak issue as in the setup-span tests: this suite mutates process.env.GITHUB_SERVER_URL but the sendJobConclusionSpan env save/restore list doesn’t include it, which can make tests order-dependent. Include GITHUB_SERVER_URL in envKeys (or save/restore it within these tests).
    process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "https://traces.example.com";
    process.env.GITHUB_REPOSITORY = "owner/repo";
    process.env.GITHUB_RUN_ID = "987654321";
    delete process.env.GITHUB_SERVER_URL;

    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.actions.run_url",
      value: { stringValue: "https://github.com/owner/repo/actions/runs/987654321" },
    });
  });

  it("uses GITHUB_SERVER_URL for github.actions.run_url in sendJobConclusionSpan", 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_REPOSITORY = "owner/repo";
    process.env.GITHUB_RUN_ID = "987654321";
    process.env.GITHUB_SERVER_URL = "https://github.example.com";

    await sendJobConclusionSpan("gh-aw.job.conclusion");
  • Files reviewed: 2/2 changed files
  • Comments generated: 2

Comment on lines 389 to +393
const resourceAttributes = [buildAttr("github.repository", repository), buildAttr("github.run_id", runId)];
if (repository && runId) {
const [owner, repo] = repository.split("/");
resourceAttributes.push(buildAttr("github.actions.run_url", buildWorkflowRunUrl({ runId }, { owner, repo })));
}
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

repository.split("/") assumes GITHUB_REPOSITORY is exactly owner/repo. If it’s malformed (e.g., missing /), this will emit a github.actions.run_url containing undefined. Consider validating the split (or using an existing repo-slug parser) and only adding the attribute when both owner and repo are present. Also consider normalizing GITHUB_SERVER_URL (trim trailing /) before URL construction to avoid // in the emitted run URL.

Copilot uses AI. Check for mistakes.
Comment on lines +824 to +856

process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "https://traces.example.com";
process.env.GITHUB_REPOSITORY = "owner/repo";
process.env.GITHUB_RUN_ID = "987654321";
delete process.env.GITHUB_SERVER_URL;

await sendJobSetupSpan();

const body = JSON.parse(mockFetch.mock.calls[0][1].body);
const resourceAttrs = body.resourceSpans[0].resource.attributes;
expect(resourceAttrs).toContainEqual({
key: "github.actions.run_url",
value: { stringValue: "https://github.com/owner/repo/actions/runs/987654321" },
});
});

it("uses GITHUB_SERVER_URL for github.actions.run_url in sendJobSetupSpan", 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_REPOSITORY = "owner/repo";
process.env.GITHUB_RUN_ID = "987654321";
process.env.GITHUB_SERVER_URL = "https://github.example.com";

await sendJobSetupSpan();

const body = JSON.parse(mockFetch.mock.calls[0][1].body);
const resourceAttrs = body.resourceSpans[0].resource.attributes;
expect(resourceAttrs).toContainEqual({
key: "github.actions.run_url",
value: { stringValue: "https://github.example.com/owner/repo/actions/runs/987654321" },
});
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests set/delete process.env.GITHUB_SERVER_URL, but the sendJobSetupSpan suite’s env save/restore list doesn’t include GITHUB_SERVER_URL, so the mutation can leak into later tests (including other files in the same Vitest worker). Add GITHUB_SERVER_URL to the suite’s envKeys (and restore it in afterEach), or save/restore it locally in each test.

This issue also appears on line 1151 of the same file.

Suggested change
process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "https://traces.example.com";
process.env.GITHUB_REPOSITORY = "owner/repo";
process.env.GITHUB_RUN_ID = "987654321";
delete process.env.GITHUB_SERVER_URL;
await sendJobSetupSpan();
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
const resourceAttrs = body.resourceSpans[0].resource.attributes;
expect(resourceAttrs).toContainEqual({
key: "github.actions.run_url",
value: { stringValue: "https://github.com/owner/repo/actions/runs/987654321" },
});
});
it("uses GITHUB_SERVER_URL for github.actions.run_url in sendJobSetupSpan", 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_REPOSITORY = "owner/repo";
process.env.GITHUB_RUN_ID = "987654321";
process.env.GITHUB_SERVER_URL = "https://github.example.com";
await sendJobSetupSpan();
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
const resourceAttrs = body.resourceSpans[0].resource.attributes;
expect(resourceAttrs).toContainEqual({
key: "github.actions.run_url",
value: { stringValue: "https://github.example.com/owner/repo/actions/runs/987654321" },
});
const originalGithubServerUrl = process.env.GITHUB_SERVER_URL;
try {
process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "https://traces.example.com";
process.env.GITHUB_REPOSITORY = "owner/repo";
process.env.GITHUB_RUN_ID = "987654321";
delete process.env.GITHUB_SERVER_URL;
await sendJobSetupSpan();
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
const resourceAttrs = body.resourceSpans[0].resource.attributes;
expect(resourceAttrs).toContainEqual({
key: "github.actions.run_url",
value: { stringValue: "https://github.com/owner/repo/actions/runs/987654321" },
});
} finally {
if (originalGithubServerUrl === undefined) {
delete process.env.GITHUB_SERVER_URL;
} else {
process.env.GITHUB_SERVER_URL = originalGithubServerUrl;
}
}
});
it("uses GITHUB_SERVER_URL for github.actions.run_url in sendJobSetupSpan", async () => {
const mockFetch = vi.fn().mockResolvedValue({ ok: true, status: 200, statusText: "OK" });
vi.stubGlobal("fetch", mockFetch);
const originalGithubServerUrl = process.env.GITHUB_SERVER_URL;
try {
process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "https://traces.example.com";
process.env.GITHUB_REPOSITORY = "owner/repo";
process.env.GITHUB_RUN_ID = "987654321";
process.env.GITHUB_SERVER_URL = "https://github.example.com";
await sendJobSetupSpan();
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
const resourceAttrs = body.resourceSpans[0].resource.attributes;
expect(resourceAttrs).toContainEqual({
key: "github.actions.run_url",
value: { stringValue: "https://github.example.com/owner/repo/actions/runs/987654321" },
});
} finally {
if (originalGithubServerUrl === undefined) {
delete process.env.GITHUB_SERVER_URL;
} else {
process.env.GITHUB_SERVER_URL = originalGithubServerUrl;
}
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[otel-advisor] OTel improvement: add github.actions.run_url resource attribute to all spans

3 participants