π‘ OTel Instrumentation Improvement: restore the dedicated agent span and GenAI semantic attributes
Analysis Date: 2026-05-21
Priority: High
Effort: Small (< 2h)
Problem
In actions/setup/js/send_otlp_span.cjs:1666 the conclusion-span builder reads the job name directly from the environment:
const jobName = process.env.INPUT_JOB_NAME || "";
but actions/setup/js/action_conclusion_otlp.cjs does not normalize the GitHub Actions hyphen form (INPUT_JOB-NAME) into the canonical underscore form before invoking sendJobConclusionSpan β in contrast to action_setup_otlp.cjs:80-84, which explicitly does that normalization:
// action_setup_otlp.cjs (has the fix)
const inputJobName = getActionInput("JOB_NAME");
if (inputJobName) {
process.env.INPUT_JOB_NAME = inputJobName;
}
When the runner exposes the input only under the hyphen form during the action's post phase, process.env.INPUT_JOB_NAME is empty inside sendJobConclusionSpan. As a result, the jobName === "agent" branch is never taken on conclusion in production, so:
- The dedicated
gh-aw.agent.agent span (the one that wraps pure LLM execution time) is never emitted.
- The OTel GenAI semantic-convention attributes gated by
if (jobName === "agent") at send_otlp_span.cjs:1787-1800 are silently dropped from gh-aw.agent.conclusion: gen_ai.operation.name, gen_ai.workflow.name, gen_ai.response.model, gen_ai.response.finish_reasons.
- The
gh-aw.job.name span attribute is missing on every conclusion span (activation, agent, detection, safe_outputs, conclusion).
Why This Matters (DevOps Perspective)
The gh-aw.agent.agent span is the only span that isolates LLM execution time from the broader agent job (AWF container boot, MCP proxy startup, workspace audit, post-processing). Without it, a DevOps engineer cannot answer:
- "How long did the LLM call actually take this run?" β
gh-aw.agent.conclusion includes ~30s of pre/post-agent overhead, so any latency SLO built on it is noisy.
- "Is Anthropic/Copilot API latency degrading?" β today the agent.conclusion span mixes provider latency with workflow framework time, so a 20 % LLM slowdown is invisible until it dominates the total.
- "Why did this finish_reason occur?" β
gen_ai.response.finish_reasons is meant to flag length-truncated runs in Grafana/Sentry dashboards, but it's currently missing on all spans.
- "Filter spans by job" β
gh-aw.job.name is missing on every conclusion span, so dashboards must fall back to parsing span names with regex.
This also breaks out-of-the-box LLM dashboards (Grafana GenAI, Datadog LLM observability) that key on the OTel GenAI semantic-convention attributes β they show empty for gh-aw despite the code intending to emit them.
Current Behavior
actions/setup/js/action_conclusion_otlp.cjs:72-89:
async function run() {
const endpoints = process.env.GH_AW_OTLP_ENDPOINTS;
const spanName = buildSpanName(getActionInput("JOB_NAME"));
// Use the job-start timestamp set by action_setup_otlp so the conclusion span
// duration covers the actual job execution window, not just this step's overhead.
const startMs = parseJobStartMs(process.env.GITHUB_AW_OTEL_JOB_START_MS);
// ...
await sendOtlpSpan.sendJobConclusionSpan(spanName, { startMs });
// ...
}
getActionInput("JOB_NAME") correctly resolves the value from either form (so the span name is right: gh-aw.agent.conclusion), but the env var is never written back to the canonical INPUT_JOB_NAME. Then in actions/setup/js/send_otlp_span.cjs:1666:
const jobName = process.env.INPUT_JOB_NAME || "";
// ...
if (jobName) attributes.push(buildAttr("gh-aw.job.name", jobName));
// ...
if (jobName === "agent") {
attributes.push(buildAttr("gen_ai.operation.name", "chat"));
// ... (other GenAI semantic attributes)
}
// ...
const hasDedicatedAgentSpan = jobName === "agent" && /* ... */;
jobName ends up empty, so the gated attributes and the dedicated agent span are skipped.
Proposed Change
Mirror the normalization that already exists in action_setup_otlp.cjs so process.env.INPUT_JOB_NAME is canonical before sendJobConclusionSpan reads it:
// actions/setup/js/action_conclusion_otlp.cjs
async function run() {
const endpoints = process.env.GH_AW_OTLP_ENDPOINTS;
const inputJobName = getActionInput("JOB_NAME");
// Normalize to the canonical underscore form so sendJobConclusionSpan
// (which reads process.env.INPUT_JOB_NAME) always finds the value, even
// when the runner only exposes the INPUT_JOB-NAME hyphen form in the post step.
if (inputJobName) {
process.env.INPUT_JOB_NAME = inputJobName;
}
const spanName = buildSpanName(inputJobName);
const startMs = parseJobStartMs(process.env.GITHUB_AW_OTEL_JOB_START_MS);
// ... rest unchanged
await sendOtlpSpan.sendJobConclusionSpan(spanName, { startMs });
}
No changes required in send_otlp_span.cjs itself.
Expected Outcome
After this change:
- In Grafana Tempo / Honeycomb / Datadog:
{ name = "gh-aw.agent.agent" } returns real spans whose duration excludes setup/cleanup overhead, enabling LLM-only latency SLOs.
gen_ai.operation.name, gen_ai.workflow.name, gen_ai.response.model, and gen_ai.response.finish_reasons populate on agent spans β out-of-the-box GenAI dashboards in Grafana/Datadog/Honeycomb start working without custom mappings.
gh-aw.job.name appears on every conclusion span, so dashboards can group by gh-aw.job.name instead of regex-parsing span names.
- In the JSONL mirror: the same enrichments appear, so artifact-based debugging matches what's in the collector.
- For on-call engineers: a length-truncated agent run (e.g.
gen_ai.response.finish_reasons = ["length"]) becomes discoverable in dashboards; today it's silently lost.
Implementation Steps
Evidence from Live OTel Data (Sentry/Grafana)
Grafana Tempo β span name enumeration (last 24 h, datasource grafanacloud-traces)
tempo_get-attribute-values name="name" β [
"gh-aw.activation.conclusion", "gh-aw.activation.setup",
"gh-aw.agent.conclusion", "gh-aw.agent.setup",
"gh-aw.conclusion.conclusion", "gh-aw.conclusion.setup",
"gh-aw.detection.conclusion", "gh-aw.detection.setup",
"gh-aw.pre_activation.conclusion", "gh-aw.pre_activation.setup",
"gh-aw.safe_outputs.conclusion", "gh-aw.safe_outputs.setup",
"gh-aw.unlock.conclusion", "gh-aw.unlock.setup"
]
gh-aw.agent.agent is absent β zero occurrences across all gh-aw workflows in 24 h, despite jobName === "agent" and the agent span emission logic existing in code. A focused {name="gh-aw.agent.agent"} TraceQL query also returned 0 traces (inspectedBytes: 1.96 MB, 95/95 jobs completed).
Trace 8f9e94eb07269ea215125b1868b62d37 (workflow aw-portfolio-yield, run 26219695393, failed agent run):
gh-aw.agent.setup attributes include gh-aw.job.name: "agent" β setup span correctly carries the attribute because action_setup_otlp.cjs already normalizes.
gh-aw.agent.conclusion attributes do not include gh-aw.job.name, nor any of gen_ai.operation.name, gen_ai.workflow.name, gen_ai.response.model, gen_ai.response.finish_reasons β confirming jobName === "" at conclusion time.
- The span name
gh-aw.agent.conclusion is correct, which proves getActionInput("JOB_NAME") resolves the value via the hyphen-form fallback β narrowing the bug to the missing process.env writeback in action_conclusion_otlp.cjs.
Trace a4879c57add4415850fc4a7ad5de5c71 (current advisor run) shows the same pattern: setup spans carry gh-aw.job.name, conclusion spans do not.
Existing setup-side regression test at actions/setup/js/action_otlp.test.cjs:185 covers the hyphen-form case for setup spans but has no equivalent for conclusion β which is why the bug was not caught.
Related Files
actions/setup/js/action_conclusion_otlp.cjs β the file to edit
actions/setup/js/action_setup_otlp.cjs β already contains the analogous fix to mirror
actions/setup/js/send_otlp_span.cjs β reads process.env.INPUT_JOB_NAME at line 1666 and gates the dedicated agent span / GenAI attributes on it
actions/setup/js/action_input_utils.cjs β defines getActionInput, which handles both env-var forms
actions/setup/js/action_otlp.test.cjs β add the hyphen-form regression test next to line 185
Generated by the Daily OTel Instrumentation Advisor workflow
Generated by π Daily OTel Instrumentation Advisor Β· β 20.7M Β· β·
π‘ OTel Instrumentation Improvement: restore the dedicated agent span and GenAI semantic attributes
Analysis Date: 2026-05-21
Priority: High
Effort: Small (< 2h)
Problem
In
actions/setup/js/send_otlp_span.cjs:1666the conclusion-span builder reads the job name directly from the environment:but
actions/setup/js/action_conclusion_otlp.cjsdoes not normalize the GitHub Actions hyphen form (INPUT_JOB-NAME) into the canonical underscore form before invokingsendJobConclusionSpanβ in contrast toaction_setup_otlp.cjs:80-84, which explicitly does that normalization:When the runner exposes the input only under the hyphen form during the action's post phase,
process.env.INPUT_JOB_NAMEis empty insidesendJobConclusionSpan. As a result, thejobName === "agent"branch is never taken on conclusion in production, so:gh-aw.agent.agentspan (the one that wraps pure LLM execution time) is never emitted.if (jobName === "agent")atsend_otlp_span.cjs:1787-1800are silently dropped fromgh-aw.agent.conclusion:gen_ai.operation.name,gen_ai.workflow.name,gen_ai.response.model,gen_ai.response.finish_reasons.gh-aw.job.namespan attribute is missing on every conclusion span (activation, agent, detection, safe_outputs, conclusion).Why This Matters (DevOps Perspective)
The
gh-aw.agent.agentspan is the only span that isolates LLM execution time from the broader agent job (AWF container boot, MCP proxy startup, workspace audit, post-processing). Without it, a DevOps engineer cannot answer:gh-aw.agent.conclusionincludes ~30s of pre/post-agent overhead, so any latency SLO built on it is noisy.gen_ai.response.finish_reasonsis meant to flaglength-truncated runs in Grafana/Sentry dashboards, but it's currently missing on all spans.gh-aw.job.nameis missing on every conclusion span, so dashboards must fall back to parsing span names with regex.This also breaks out-of-the-box LLM dashboards (Grafana GenAI, Datadog LLM observability) that key on the OTel GenAI semantic-convention attributes β they show empty for gh-aw despite the code intending to emit them.
Current Behavior
actions/setup/js/action_conclusion_otlp.cjs:72-89:getActionInput("JOB_NAME")correctly resolves the value from either form (so the span name is right:gh-aw.agent.conclusion), but the env var is never written back to the canonicalINPUT_JOB_NAME. Then inactions/setup/js/send_otlp_span.cjs:1666:jobNameends up empty, so the gated attributes and the dedicated agent span are skipped.Proposed Change
Mirror the normalization that already exists in
action_setup_otlp.cjssoprocess.env.INPUT_JOB_NAMEis canonical beforesendJobConclusionSpanreads it:No changes required in
send_otlp_span.cjsitself.Expected Outcome
After this change:
{ name = "gh-aw.agent.agent" }returns real spans whose duration excludes setup/cleanup overhead, enabling LLM-only latency SLOs.gen_ai.operation.name,gen_ai.workflow.name,gen_ai.response.model, andgen_ai.response.finish_reasonspopulate on agent spans β out-of-the-box GenAI dashboards in Grafana/Datadog/Honeycomb start working without custom mappings.gh-aw.job.nameappears on every conclusion span, so dashboards cangroup by gh-aw.job.nameinstead of regex-parsing span names.gen_ai.response.finish_reasons = ["length"]) becomes discoverable in dashboards; today it's silently lost.Implementation Steps
actions/setup/js/action_conclusion_otlp.cjs: add theinputJobNamenormalization shown above before computingspanName.actions/setup/js/action_otlp.test.cjsmirroring the existing setup test at line 185 ("uses job name from INPUT_JOB-NAME (hyphen form) in setup span"): assert thatsendJobConclusionSpanseesjobName === "agent"when onlyINPUT_JOB-NAMEis set, sogh-aw.agent.agentis emitted and the GenAI attributes are present.cd actions/setup/js && npx vitest runto confirm tests pass (including the existing tests at lines 273β290 for the underscore-form happy path).make fmt.Evidence from Live OTel Data (Sentry/Grafana)
Grafana Tempo β span name enumeration (last 24 h, datasource
grafanacloud-traces)gh-aw.agent.agentis absent β zero occurrences across all gh-aw workflows in 24 h, despitejobName === "agent"and the agent span emission logic existing in code. A focused{name="gh-aw.agent.agent"}TraceQL query also returned 0 traces (inspectedBytes: 1.96 MB, 95/95 jobs completed).Trace
8f9e94eb07269ea215125b1868b62d37(workflowaw-portfolio-yield, run26219695393, failed agent run):gh-aw.agent.setupattributes includegh-aw.job.name: "agent"β setup span correctly carries the attribute becauseaction_setup_otlp.cjsalready normalizes.gh-aw.agent.conclusionattributes do not includegh-aw.job.name, nor any ofgen_ai.operation.name,gen_ai.workflow.name,gen_ai.response.model,gen_ai.response.finish_reasonsβ confirmingjobName === ""at conclusion time.gh-aw.agent.conclusionis correct, which provesgetActionInput("JOB_NAME")resolves the value via the hyphen-form fallback β narrowing the bug to the missingprocess.envwriteback inaction_conclusion_otlp.cjs.Trace
a4879c57add4415850fc4a7ad5de5c71(current advisor run) shows the same pattern: setup spans carrygh-aw.job.name, conclusion spans do not.Existing setup-side regression test at
actions/setup/js/action_otlp.test.cjs:185covers the hyphen-form case for setup spans but has no equivalent for conclusion β which is why the bug was not caught.Related Files
actions/setup/js/action_conclusion_otlp.cjsβ the file to editactions/setup/js/action_setup_otlp.cjsβ already contains the analogous fix to mirroractions/setup/js/send_otlp_span.cjsβ readsprocess.env.INPUT_JOB_NAMEat line 1666 and gates the dedicated agent span / GenAI attributes on itactions/setup/js/action_input_utils.cjsβ definesgetActionInput, which handles both env-var formsactions/setup/js/action_otlp.test.cjsβ add the hyphen-form regression test next to line 185Generated by the Daily OTel Instrumentation Advisor workflow