Skip to content

[otel-advisor] OTel improvement: add github.job to resource attributes for job-level correlationΒ #30973

@github-actions

Description

@github-actions

πŸ“‘ OTel Instrumentation Improvement: add github.job to resource attributes

Analysis Date: 2026-05-08
Priority: Medium
Effort: Small (< 2h)

Problem

GITHUB_JOB β€” the GitHub Actions job ID (e.g. agent, activation, conclusion) β€” is absent from all span resource attributes. Every other standard GITHUB_* environment variable relevant to a job execution is already captured in buildGitHubActionsResourceAttributes:

Env var Resource attribute Captured?
GITHUB_REPOSITORY github.repository βœ…
GITHUB_RUN_ID github.run_id βœ…
GITHUB_RUN_ATTEMPT github.run_attempt βœ…
GITHUB_EVENT_NAME github.event_name βœ…
GITHUB_REF github.ref βœ…
GITHUB_SHA github.sha βœ…
GITHUB_WORKFLOW_REF github.workflow_ref βœ…
GITHUB_JOB github.job ❌ missing

The custom span attribute gh-aw.job.name partially covers the same field β€” but only when the job-name action input is wired up correctly, and it lives in the span attributes layer, not the resource attributes layer. Resource attributes are what OTel backends index for global filtering and dashboarding.

Why This Matters (DevOps Perspective)

When investigating a failed workflow run, an on-call engineer opens a failing span in Sentry/Grafana/Honeycomb and wants to jump directly to the GitHub Actions job log. Today they can reach the workflow run via github.actions.run_url, but they still need to click through all jobs in the run to find the right one. With github.job as a resource attribute, a backend widget or alert rule can construct the direct job URL (.../runs/{run_id}/job/{job_id}) or at minimum filter to just the job of interest.

Beyond navigation, github.job enables:

  • Dashboard grouping: "p95 job duration by job type" across all runs
  • Alert routing: "page when github.job=agent has error rate > 5%"
  • Cross-run queries: "how many times did the conclusion job fail this week?"

All of these queries are possible today using gh-aw.job.name (span-level), but github.job at the resource level ensures every span type β€” including future span types emitted from tools or side-processes β€” carries the job context automatically.

Current Behavior

buildGitHubActionsResourceAttributes (called by both sendJobSetupSpan and sendJobConclusionSpan) does not accept or emit github.job:

// Current: actions/setup/js/send_otlp_span.cjs (lines 306–331)
function buildGitHubActionsResourceAttributes({
  repository, runId, eventName = "", ref = "", refName = "",
  headRef = "", sha = "", workflowRef = "", staged, runAttempt = "1"
  // <-- no jobId parameter
}) {
  const resourceAttributes = [
    buildAttr("github.repository", repository),
    buildAttr("github.run_id", runId),
    buildAttr("github.run_attempt", runAttempt),
    // ...
    // github.job is never emitted
  ];
  // ...
}

Both callers pass the same fixed set of fields and do not read GITHUB_JOB:

// sendJobSetupSpan  (line 917) β€” no jobId
const resourceAttributes = buildGitHubActionsResourceAttributes({
  repository, runId, eventName, ref, refName, headRef, sha, workflowRef, staged, runAttempt
});

// sendJobConclusionSpan (line 1368) β€” no jobId
const resourceAttributes = buildGitHubActionsResourceAttributes({
  repository, runId, eventName, ref, refName, headRef, sha, workflowRef, staged, runAttempt
});
Proposed Change

Step 1 β€” extend buildGitHubActionsResourceAttributes to accept and emit github.job:

// actions/setup/js/send_otlp_span.cjs β€” update function signature and body
function buildGitHubActionsResourceAttributes({
  repository, runId, jobId = "", eventName = "", ref = "", refName = "",
  headRef = "", sha = "", workflowRef = "", staged, runAttempt = "1"
}) {
  const resourceAttributes = [
    buildAttr("github.repository", repository),
    buildAttr("github.run_id", runId),
    buildAttr("github.run_attempt", runAttempt),
  ];
  // ... existing attributes unchanged ...
  if (jobId) {
    resourceAttributes.push(buildAttr("github.job", jobId));
  }
  resourceAttributes.push(buildAttr("deployment.environment", staged ? "staging" : "production"));
  return resourceAttributes;
}

Step 2 β€” read GITHUB_JOB in both callers:

// In sendJobSetupSpan (near line 876, alongside other GITHUB_* reads):
const jobId = process.env.GITHUB_JOB || "";

// Update call site (line 917):
const resourceAttributes = buildGitHubActionsResourceAttributes({
  repository, runId, jobId, eventName, ref, refName, headRef, sha, workflowRef, staged, runAttempt
});
// In sendJobConclusionSpan (near line 1235, alongside other GITHUB_* reads):
const jobId = process.env.GITHUB_JOB || "";

// Update call site (line 1368):
const resourceAttributes = buildGitHubActionsResourceAttributes({
  repository, runId, jobId, eventName, ref, refName, headRef, sha, workflowRef, staged, runAttempt
});

Total diff: ~8 lines changed across one file, with matching test updates.

Expected Outcome

After this change:

  • In Grafana / Honeycomb / Datadog: spans become filterable and groupable by github.job. Dashboards can show "failure rate by job" and "p95 duration by job" without requiring gh-aw.job.name span-attribute access (which varies by backend query language).
  • In the JSONL mirror (/tmp/gh-aw/otel.jsonl): every span record's resourceSpans[0].resource.attributes array gains a {"key":"github.job","value":{"stringValue":"agent"}} entry, making post-hoc job identification from artifacts unambiguous.
  • For on-call engineers: opening a span in any OTel backend shows github.job = agent (or activation, conclusion) right in the resource attributes panel, enabling one-click navigation to the correct job log without guessing.
Implementation Steps
  • Add jobId = "" parameter to buildGitHubActionsResourceAttributes in actions/setup/js/send_otlp_span.cjs
  • Emit buildAttr("github.job", jobId) inside the function when jobId is non-empty
  • Read process.env.GITHUB_JOB in sendJobSetupSpan and pass it as jobId
  • Read process.env.GITHUB_JOB in sendJobConclusionSpan and pass it as jobId
  • Update actions/setup/js/action_otlp.test.cjs (or send_otlp_span.test.cjs) to assert that github.job appears in the resource attributes when GITHUB_JOB is set
  • Run cd actions/setup/js && npx vitest run to confirm tests pass
  • Run make fmt to ensure formatting
  • Open a PR referencing this issue
Evidence from Live Sentry Data

The Sentry MCP server was unavailable during this analysis run (empty tool list at the configured bridge endpoint). The recommendation is therefore grounded in static code analysis of actions/setup/js/send_otlp_span.cjs.

The gap is directly verifiable from code: searching for GITHUB_JOB in send_otlp_span.cjs, action_setup_otlp.cjs, and action_conclusion_otlp.cjs returns zero matches, confirming the environment variable is never read and github.job is never emitted.

Related Files
  • actions/setup/js/send_otlp_span.cjs β€” buildGitHubActionsResourceAttributes (line 306), sendJobSetupSpan (line 814), sendJobConclusionSpan (line 1181)
  • actions/setup/js/action_setup_otlp.cjs β€” calls sendJobSetupSpan
  • actions/setup/js/action_conclusion_otlp.cjs β€” calls sendJobConclusionSpan
  • actions/setup/js/action_otlp.test.cjs β€” main OTel test file to update

Generated by the Daily OTel Instrumentation Advisor workflow

Generated by Daily OTel Instrumentation Advisor Β· ● 274.2K Β· β—·

  • expires on May 15, 2026, 9:51 AM UTC

Metadata

Metadata

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions