Skip to content

[otel-advisor] add deployment.environment to setup span resource attributes #24745

@github-actions

Description

@github-actions

📡 OTel Instrumentation Improvement: add deployment.environment to setup span resource attributes

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

Problem

The sendJobConclusionSpan function sets deployment.environment as a resource attribute (staging or production, derived from awInfo.staged), but sendJobSetupSpan does not — even though it already reads aw_info.json and has the information needed to compute it.

The gap is in actions/setup/js/send_otlp_span.cjs:

  • Conclusion span sets deployment.environment at line 638
  • Setup span builds its resource attributes at lines 405–412 but never adds deployment.environment

This means environment-based filtering in any OTel backend (Grafana, Honeycomb, Datadog, Sentry) silently omits all setup spans. A dashboard query like deployment.environment = "production" returns only conclusion spans, which makes it impossible to filter the beginning of any trace by environment.

Why This Matters (DevOps Perspective)

Every trace starts with the setup span — it establishes the trace ID, captures job metadata, and anchors the parent span for downstream conclusion spans. If setup spans lack deployment.environment, then:

  • Dashboards that group or filter by environment show incomplete data; the setup half of every trace is invisible in environment-scoped views.
  • Alert rules on deployment.environment = "staging" will never fire on setup spans, creating a blind spot during the exact job-start window most likely to expose configuration errors.
  • Backend attribute indexing (Honeycomb columns, Grafana label matchers, Datadog facets) treats the two span types in the same trace as being in different environments, which breaks cardinality and group-by aggregations.
  • JSONL mirror artifacts (/tmp/gh-aw/otel.jsonl) written for setup spans lack deployment.environment, reducing their utility for local debugging without a live collector.

This is also an internal consistency issue: two spans in the same trace carry different resource schemas, which can confuse trace viewers that expect resource attributes to be uniform across a service's spans.

Current Behavior

// Current: actions/setup/js/send_otlp_span.cjs (lines 363–412)
// sendJobSetupSpan reads awInfo but only uses otel_trace_id from it.
// staged is never extracted, so deployment.environment is never set.

const awInfo = readJSONIfExists("/tmp/gh-aw/aw_info.json") || {};
const rawContextTraceId = typeof awInfo.context?.otel_trace_id === "string"
  ? awInfo.context.otel_trace_id.trim().toLowerCase()
  : "";
const contextTraceId = isValidTraceId(rawContextTraceId) ? rawContextTraceId : "";

// ... (lines 392–412)
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 })));
}
if (eventName) {
  resourceAttributes.push(buildAttr("github.event_name", eventName));
}
// deployment.environment is never added here ← gap

By contrast, sendJobConclusionSpan correctly sets it (line 638):

resourceAttributes.push(buildAttr("deployment.environment", staged ? "staging" : "production"));

Proposed Change

// Proposed addition to actions/setup/js/send_otlp_span.cjs
// In sendJobSetupSpan, after the existing awInfo read at line 363:

const awInfo = readJSONIfExists("/tmp/gh-aw/aw_info.json") || {};
// ... existing otel_trace_id extraction unchanged ...

// Add this line alongside other metadata extraction (around line 385–391):
const staged = awInfo.staged === true;

// Then append to resourceAttributes (after line 411, mirroring the conclusion span):
resourceAttributes.push(buildAttr("deployment.environment", staged ? "staging" : "production"));

The staged flag is compile-time metadata written into aw_info.json before any job runs in a given workflow execution, so it is available when the setup span fires for downstream jobs (safe-outputs, conclusion). For the activation job itself, aw_info.json may not yet exist when setup fires — in that case awInfo is {}, awInfo.staged is undefined, and staged evaluates to false, producing "production" as the default. This matches the conclusion span's behavior under the same condition.

Expected Outcome

After this change:

  • In Grafana / Honeycomb / Datadog: filtering by deployment.environment = "staging" returns both setup and conclusion spans — complete end-to-end traces for staging runs
  • In Sentry: environment-scoped issue grouping captures the full trace from job start to conclusion
  • In the JSONL mirror: setup span entries in /tmp/gh-aw/otel.jsonl include deployment.environment, making them useful for local debugging without a live collector
  • For on-call engineers: traces no longer have a split resource schema; environment-based alert rules now fire on the span that starts the trace, not just the one that ends it

Implementation Steps

  • In actions/setup/js/send_otlp_span.cjs, extract const staged = awInfo.staged === true; in sendJobSetupSpan (alongside the existing awInfo usage)
  • Append buildAttr("deployment.environment", staged ? "staging" : "production") to the resourceAttributes array in sendJobSetupSpan
  • Add three test cases to actions/setup/js/send_otlp_span.test.cjs under the sendJobSetupSpan describe block, mirroring the existing staged / deployment.environment tests for sendJobConclusionSpan (lines 1620–1700):
    • deployment.environment=production when aw_info.json is absent
    • deployment.environment=staging when awInfo.staged=true
    • deployment.environment=production when awInfo.staged=false
  • 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

Note: No Sentry MCP tooling was available in this analysis run. The gap is confirmed entirely from static code analysis: deployment.environment is added to resourceAttributes in sendJobConclusionSpan (line 638) and absent from the equivalent block in sendJobSetupSpan (lines 405–412). The test file (send_otlp_span.test.cjs) has three deployment.environment test cases for sendJobConclusionSpan (lines 1633, 1651, 1676) and zero for sendJobSetupSpan, which independently confirms the gap.

Related Files

  • actions/setup/js/send_otlp_span.cjs — primary change site (sendJobSetupSpan, lines 363–428)
  • actions/setup/js/send_otlp_span.test.cjs — test additions needed (mirror lines 1620–1700)
  • actions/setup/js/action_setup_otlp.cjs — no change needed (calls sendJobSetupSpan unmodified)
  • actions/setup/js/action_conclusion_otlp.cjs — reference implementation (already correct)

Generated by the Daily OTel Instrumentation Advisor workflow

Generated by Daily OTel Instrumentation Advisor · ● 141.5K ·

  • expires on Apr 12, 2026, 3:07 PM 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