π‘ 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
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 Β· β·
π‘ OTel Instrumentation Improvement: add
github.jobto resource attributesAnalysis 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 standardGITHUB_*environment variable relevant to a job execution is already captured inbuildGitHubActionsResourceAttributes:GITHUB_REPOSITORYgithub.repositoryGITHUB_RUN_IDgithub.run_idGITHUB_RUN_ATTEMPTgithub.run_attemptGITHUB_EVENT_NAMEgithub.event_nameGITHUB_REFgithub.refGITHUB_SHAgithub.shaGITHUB_WORKFLOW_REFgithub.workflow_refGITHUB_JOBgithub.jobThe custom span attribute
gh-aw.job.namepartially covers the same field β but only when thejob-nameaction 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. Withgithub.jobas 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.jobenables:github.job=agenthas error rate > 5%"conclusionjob fail this week?"All of these queries are possible today using
gh-aw.job.name(span-level), butgithub.jobat 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 bothsendJobSetupSpanandsendJobConclusionSpan) does not accept or emitgithub.job:Both callers pass the same fixed set of fields and do not read
GITHUB_JOB:Proposed Change
Step 1 β extend
buildGitHubActionsResourceAttributesto accept and emitgithub.job:Step 2 β read
GITHUB_JOBin both callers:Total diff: ~8 lines changed across one file, with matching test updates.
Expected Outcome
After this change:
github.job. Dashboards can show "failure rate by job" and "p95 duration by job" without requiringgh-aw.job.namespan-attribute access (which varies by backend query language)./tmp/gh-aw/otel.jsonl): every span record'sresourceSpans[0].resource.attributesarray gains a{"key":"github.job","value":{"stringValue":"agent"}}entry, making post-hoc job identification from artifacts unambiguous.github.job = agent(oractivation,conclusion) right in the resource attributes panel, enabling one-click navigation to the correct job log without guessing.Implementation Steps
jobId = ""parameter tobuildGitHubActionsResourceAttributesinactions/setup/js/send_otlp_span.cjsbuildAttr("github.job", jobId)inside the function whenjobIdis non-emptyprocess.env.GITHUB_JOBinsendJobSetupSpanand pass it asjobIdprocess.env.GITHUB_JOBinsendJobConclusionSpanand pass it asjobIdactions/setup/js/action_otlp.test.cjs(orsend_otlp_span.test.cjs) to assert thatgithub.jobappears in the resource attributes whenGITHUB_JOBis setcd actions/setup/js && npx vitest runto confirm tests passmake fmtto ensure formattingEvidence 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_JOBinsend_otlp_span.cjs,action_setup_otlp.cjs, andaction_conclusion_otlp.cjsreturns zero matches, confirming the environment variable is never read andgithub.jobis 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β callssendJobSetupSpanactions/setup/js/action_conclusion_otlp.cjsβ callssendJobConclusionSpanactions/setup/js/action_otlp.test.cjsβ main OTel test file to updateGenerated by the Daily OTel Instrumentation Advisor workflow