Skip to content

Add optional OTel trace export for step-level timing#4366

Draft
stefanpenner wants to merge 4 commits intoactions:mainfrom
stefanpenner:otel-step-traces
Draft

Add optional OTel trace export for step-level timing#4366
stefanpenner wants to merge 4 commits intoactions:mainfrom
stefanpenner:otel-step-traces

Conversation

@stefanpenner
Copy link
Copy Markdown
Contributor

Summary

  • Adds OTelStepTracer — a zero-dependency, opt-in OpenTelemetry span exporter for step execution
  • Enabled by setting ACTIONS_RUNNER_OTLP_ENDPOINT to an OTLP/HTTP base URL
  • Emits one span per step with sub-second precision timing, action refs, and CI/CD semantic convention attributes
  • Uses deterministic trace/span IDs (MD5-based) compatible with otel-explorer, so runner step spans merge into existing GitHub Actions workflow traces

How it works

  1. On step completion (ExecutionContext.Complete()), step metadata is captured into a pending span
  2. On job completion (JobRunner.CompleteJobAsync()), all pending spans are flushed as a single OTLP JSON POST
  3. Export is best-effort — failures are silently swallowed, never impacting job execution
  4. No new NuGet dependencies — uses raw HTTP + hand-built OTLP JSON

Attributes emitted

Attribute Example
type step
source runner
github.conclusion success
github.action actions/checkout
github.action_ref v4
github.step_type node20
cicd.pipeline.task.name Run tests
cicd.pipeline.task.run.result success
cicd.pipeline.run.id 24741863790
vcs.repository.url.full https://github.com/org/repo

Configuration

Env var Required Description
ACTIONS_RUNNER_OTLP_ENDPOINT Yes OTLP/HTTP base URL (e.g. http://collector:4318)
ACTIONS_RUNNER_OTLP_INSECURE No Set true to skip TLS verification

Relation to other PRs

  • Companion to ARC OTel recorder PR #4465 which provides runner-level spans (queue/startup/execution)
  • This PR adds step-level spans from inside the runner process itself
  • Together they provide full visibility: queue → startup → execution → per-step timing

Test plan

  • Unit tests for deterministic ID generation (cross-language compatibility with Go)
  • Unit tests for enable/disable via env var
  • E2E test in otel-explorer verifying OTLP JSON ingestion and enrichment
  • Build succeeds with zero warnings
  • Manual test with Kind cluster + OTel Collector

🤖 Generated with Claude Code

stefanpenner and others added 4 commits April 7, 2026 21:59
The deduplication logic introduced in actions#4296 used StringComparer.Ordinal,
so action references differing only by owner/repo casing (e.g.
actions/checkout vs actIONS/checkout) were treated as distinct and
downloaded multiple times. Switch to OrdinalIgnoreCase for all dedup
collections and the GroupBy in GetDownloadInfoAsync.

Fixes actions#3731

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On case-sensitive filesystems (Linux), after dedup merges differently-cased
references into one download, PrepareRepositoryActionAsync and LoadAction
would look for directories using the original (non-canonical) casing and
fail to find the action. Fix by normalizing each action step's
repositoryReference.Name to match downloadInfo.NameWithOwner after dedup
lookup, in both the batch and legacy code paths.

Also makes the empty-dictionary return path in GetDownloadInfoAsync
use OrdinalIgnoreCase for consistency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace StringComparer.OrdinalIgnoreCase with ActionLookupKeyComparer
that treats the owner/repo portion of "{owner/repo}@{ref}" keys
case-insensitively while keeping the ref portion case-sensitive, since
git refs are case-sensitive.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When ACTIONS_RUNNER_OTLP_ENDPOINT is set, the runner emits OTel spans
for each step as they complete. Spans use deterministic IDs (MD5-based)
compatible with otel-explorer's GitHub Actions trace view, allowing
runner step spans to merge into workflow traces with zero configuration.

Attributes follow OTel CI/CD semantic conventions (cicd.pipeline.*) and
include GitHub-specific fields (github.action, github.step_type, etc.).

Spans are batched per job and flushed via OTLP/HTTP JSON before job
completion is reported to the server. Export is best-effort — failures
are silently swallowed to never impact job execution.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant