π‘ OTel Instrumentation Improvement: Enrich Resource Attributes
Analysis Date: 2026-04-05
Priority: High
Effort: Small (< 2h)
Problem
buildOTLPPayload() in actions/setup/js/send_otlp_span.cjs (line 107) sets only one resource attribute β service.name. In OpenTelemetry, resource attributes describe what produced the telemetry and are indexed at the service level by every major backend (Grafana, Honeycomb, Datadog). Span attributes describe individual operations and are not indexed for service-level filtering.
Currently:
github.repository, gh-aw.run.id are repeated in every span as span attributes, wasting payload bytes and preventing service-level grouping.
service.version is passed as scopeVersion (the instrumentation scope) but is never set as a resource attribute, so backends can't filter or alert by gh-aw version.
GITHUB_EVENT_NAME (push, pull_request, workflow_dispatch, etc.) is never captured anywhere, making it impossible to filter spans or dashboards by what triggered the workflow.
A DevOps engineer cannot today answer "Show me all failed spans for repository github/gh-aw triggered by workflow_dispatch on version 1.2.3" β not because the data doesn't exist in the environment, but because it was never added to the OTLP payload.
Why This Matters (DevOps Perspective)
- Dashboard filtering: Grafana/Honeycomb service views group by resource attributes. Without
service.version, there is no way to compare latency or failure rates across gh-aw releases.
- Repository attribution: Multi-repo OTel collectors receive spans from many tenants.
github.repository as a resource attribute enables per-repo SLOs and alerting.
- Trigger-based alerting: Knowing
github.event_name lets on-call engineers quickly narrow whether failures correlate with a specific trigger (e.g., only workflow_dispatch runs are failing).
- Reduced per-span cost: Moving
github.repository and github.run_id from span attributes (repeated on every span) to resource attributes means the backend stores them once per batch, not per span.
Current Behavior
// Current: actions/setup/js/send_otlp_span.cjs (lines 103β130)
function buildOTLPPayload({ traceId, spanId, parentSpanId, spanName, startMs, endMs,
serviceName, scopeVersion, attributes, statusCode, statusMessage }) {
// ...
return {
resourceSpans: [
{
resource: {
attributes: [buildAttr("service.name", serviceName)], // β only service.name
},
scopeSpans: [
{
scope: { name: "gh-aw", version: scopeVersion || "unknown" },
// scopeVersion is captured here (instrumentation scope) but NOT as a resource attr
spans: [ /* ... span-level attrs carry gh-aw.repository, gh-aw.run.id */ ],
},
],
},
],
};
}
And in sendJobSetupSpan (line 366) and sendJobConclusionSpan (line 497), GITHUB_EVENT_NAME is never read:
// Environment variables consumed by sendJobSetupSpan β GITHUB_EVENT_NAME is absent
const workflowName = process.env.GH_AW_INFO_WORKFLOW_NAME || process.env.GITHUB_WORKFLOW || "";
const engineId = process.env.GH_AW_INFO_ENGINE_ID || "";
const runId = process.env.GITHUB_RUN_ID || "";
const actor = process.env.GITHUB_ACTOR || "";
const repository = process.env.GITHUB_REPOSITORY || "";
// β GITHUB_EVENT_NAME never read
Proposed Change
Step 1 β Enrich buildOTLPPayload to accept and populate resource attributes:
// Proposed change to actions/setup/js/send_otlp_span.cjs
// Extend OTLPSpanOptions typedef:
// `@property` {Array<{key: string, value: object}>} [resourceAttributes] - Extra resource attributes
function buildOTLPPayload({ traceId, spanId, parentSpanId, spanName, startMs, endMs,
serviceName, scopeVersion, attributes,
resourceAttributes, // β new parameter
statusCode, statusMessage }) {
const code = typeof statusCode === "number" ? statusCode : 1;
const status = { code };
if (statusMessage) status.message = statusMessage;
const baseResourceAttrs = [buildAttr("service.name", serviceName)];
if (scopeVersion && scopeVersion !== "unknown") {
baseResourceAttrs.push(buildAttr("service.version", scopeVersion));
}
const allResourceAttrs = resourceAttributes
? [...baseResourceAttrs, ...resourceAttributes]
: baseResourceAttrs;
return {
resourceSpans: [
{
resource: { attributes: allResourceAttrs },
scopeSpans: [
{
scope: { name: "gh-aw", version: scopeVersion || "unknown" },
spans: [
{
traceId, spanId,
...(parentSpanId ? { parentSpanId } : {}),
name: spanName,
kind: 2,
startTimeUnixNano: toNanoString(startMs),
endTimeUnixNano: toNanoString(endMs),
status,
attributes,
},
],
},
],
},
],
};
}
Step 2 β Pass resource attributes from sendJobSetupSpan and sendJobConclusionSpan:
// In sendJobSetupSpan (actions/setup/js/send_otlp_span.cjs, around line 360):
const eventName = process.env.GITHUB_EVENT_NAME || ""; // β add this line
const resourceAttributes = [
buildAttr("github.repository", repository),
buildAttr("github.run_id", runId),
];
if (eventName) resourceAttributes.push(buildAttr("github.event_name", eventName));
const payload = buildOTLPPayload({
traceId, spanId,
spanName: jobName ? `gh-aw.${jobName}.setup` : "gh-aw.job.setup",
startMs, endMs,
serviceName,
scopeVersion: process.env.GH_AW_INFO_VERSION || "unknown",
attributes,
resourceAttributes, // β pass resource attributes
});
Apply the same pattern in sendJobConclusionSpan.
Expected Outcome
After this change:
- In Grafana / Honeycomb / Datadog: Spans can be grouped and filtered by
service.version (compare failure rates across gh-aw releases), github.repository (per-repo SLOs), and github.event_name (per-trigger dashboards). The service catalog in backends like Grafana Tempo will automatically surface these dimensions.
- In the JSONL mirror (
/tmp/gh-aw/otel.jsonl): Resource attributes appear in the resource.attributes array, giving post-hoc debuggers repository and version context without needing to cross-reference job logs.
- For on-call engineers: Incident triage narrows from "all gh-aw failures" to "failures on version X, triggered by pull_request in this repository" β dramatically reducing MTTR.
Implementation Steps
Related Files
actions/setup/js/send_otlp_span.cjs β buildOTLPPayload, sendJobSetupSpan, sendJobConclusionSpan
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 β tests to update
Generated by the Daily OTel Instrumentation Advisor workflow
Generated by Daily OTel Instrumentation Advisor Β· β 147.5K Β· β·
π‘ OTel Instrumentation Improvement: Enrich Resource Attributes
Analysis Date: 2026-04-05
Priority: High
Effort: Small (< 2h)
Problem
buildOTLPPayload()inactions/setup/js/send_otlp_span.cjs(line 107) sets only one resource attribute βservice.name. In OpenTelemetry, resource attributes describe what produced the telemetry and are indexed at the service level by every major backend (Grafana, Honeycomb, Datadog). Span attributes describe individual operations and are not indexed for service-level filtering.Currently:
github.repository,gh-aw.run.idare repeated in every span as span attributes, wasting payload bytes and preventing service-level grouping.service.versionis passed asscopeVersion(the instrumentation scope) but is never set as a resource attribute, so backends can't filter or alert by gh-aw version.GITHUB_EVENT_NAME(push, pull_request, workflow_dispatch, etc.) is never captured anywhere, making it impossible to filter spans or dashboards by what triggered the workflow.A DevOps engineer cannot today answer "Show me all failed spans for repository
github/gh-awtriggered byworkflow_dispatchon version1.2.3" β not because the data doesn't exist in the environment, but because it was never added to the OTLP payload.Why This Matters (DevOps Perspective)
service.version, there is no way to compare latency or failure rates across gh-aw releases.github.repositoryas a resource attribute enables per-repo SLOs and alerting.github.event_namelets on-call engineers quickly narrow whether failures correlate with a specific trigger (e.g., onlyworkflow_dispatchruns are failing).github.repositoryandgithub.run_idfrom span attributes (repeated on every span) to resource attributes means the backend stores them once per batch, not per span.Current Behavior
And in
sendJobSetupSpan(line 366) andsendJobConclusionSpan(line 497),GITHUB_EVENT_NAMEis never read:Proposed Change
Step 1 β Enrich
buildOTLPPayloadto accept and populate resource attributes:Step 2 β Pass resource attributes from
sendJobSetupSpanandsendJobConclusionSpan:Apply the same pattern in
sendJobConclusionSpan.Expected Outcome
After this change:
service.version(compare failure rates across gh-aw releases),github.repository(per-repo SLOs), andgithub.event_name(per-trigger dashboards). The service catalog in backends like Grafana Tempo will automatically surface these dimensions./tmp/gh-aw/otel.jsonl): Resource attributes appear in theresource.attributesarray, giving post-hoc debuggers repository and version context without needing to cross-reference job logs.Implementation Steps
OTLPSpanOptionstypedef insend_otlp_span.cjsto includeresourceAttributes?: Array<{key: string, value: object}>buildOTLPPayloadto: (a) promotescopeVersionβservice.versionresource attr; (b) merge caller-suppliedresourceAttributesinto the resource blocksendJobSetupSpan: readGITHUB_EVENT_NAME; buildresourceAttributesarray withgithub.repository,github.run_id,github.event_name; pass tobuildOTLPPayloadsendJobConclusionSpan: apply the same resource attribute additionsactions/setup/js/action_otlp.test.cjsto assert:resource.attributescontainsservice.version,github.repository,github.run_id, andgithub.event_namecd actions/setup/js && npx vitest runto confirm tests passmake fmtto ensure formattingRelated Files
actions/setup/js/send_otlp_span.cjsβbuildOTLPPayload,sendJobSetupSpan,sendJobConclusionSpanactions/setup/js/action_setup_otlp.cjsβ callssendJobSetupSpanactions/setup/js/action_conclusion_otlp.cjsβ callssendJobConclusionSpanactions/setup/js/action_otlp.test.cjsβ tests to updateGenerated by the Daily OTel Instrumentation Advisor workflow