Skip to content

feat: user-defined OTLP attributes with template expansion per workflow phase#33846

Merged
pelikhan merged 12 commits into
mainfrom
copilot/add-otlp-attributes-support
May 22, 2026
Merged

feat: user-defined OTLP attributes with template expansion per workflow phase#33846
pelikhan merged 12 commits into
mainfrom
copilot/add-otlp-attributes-support

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 21, 2026

Workflows have no way to attach custom OTLP attributes (e.g. Langfuse session/user tracking attributes) to spans. This adds a flexible, user-configurable observability.otlp.attributes frontmatter field with {{ variable }} template syntax resolved against already-computed span attributes.

Frontmatter schema

observability:
  otlp:
    endpoint: https://langfuse.example.com/api/public/otel/v1/traces
    attributes:
      langfuse.session.id: "{{ gh-aw.episode.id }}"
      session.id: "{{ gh-aw.episode.id }}"
      langfuse.user.id: "{{ gh-aw.run.actor }}"
      user.id: "{{ gh-aw.run.actor }}"

Template variable names match [a-zA-Z0-9_.-] — covering all standard gh-aw OTLP attribute keys. Unknown variables expand to empty string and are omitted from the span.

Go (compiler)

  • frontmatter_types.go: Attributes map[string]string added to OTLPConfig
  • observability_otlp.go: encodes attributes as GH_AW_OTLP_ATTRIBUTES JSON env var (same pattern as GH_AW_OTLP_ENDPOINTS)
  • compiler_orchestrator_workflow.go: fixed a latent bug where the observability merge for shared-workflow imports clobbered all fields except endpoint
  • import_field_extractor.go: mergeObservabilityConfigs now collects and merges custom attributes from imports (main workflow wins; first import wins among imports)

JavaScript (runtime)

  • send_otlp_span.cjs: parseOTLPCustomAttributes / expandOTLPAttributeTemplate / buildCustomOTLPAttributes — reads env var and expands templates against the span's already-built attribute array; applied in both sendJobSetupSpan and sendJobConclusionSpan
  • emit_outcome_spans.cjs: custom attributes applied to the outcome summary span

pr-sous-chef: automated branch update from run https://github.com/github/gh-aw/actions/runs/26259944210

Generated by 👨‍🍳 PR Sous Chef · ● 209.4K ·

Copilot AI and others added 3 commits May 21, 2026 19:10
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title feat: custom OTLP attributes with template support per workflow phase feat: user-defined OTLP attributes with template expansion per workflow phase May 21, 2026
Copilot AI requested a review from pelikhan May 21, 2026 19:26
@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot update prefix gh-aw in expressions

 gh-aw.episode.id -> github.aw.episode.id

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 21, 2026

@copilot update prefix gh-aw in expressions

 gh-aw.episode.id -> github.aw.episode.id

Done — all gh-aw.* OTLP attribute/span-name identifiers have been renamed to github.aw.* across the production JS files (send_otlp_span.cjs, action_conclusion_otlp.cjs, action_setup_otlp.cjs, emit_outcome_spans.cjs), their test files, and the Go docs/test fixtures. Committed in refactor: rename OTLP attribute prefix gh-aw. → github.aw..

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 21, 2026

@copilot update prefix gh-aw in expressions

 gh-aw.episode.id -> github.aw.episode.id

Done — renamed the gh-aw. prefix to github.aw. across all OTLP attribute keys, span names, and JSDoc examples in d4d0f4e. Also updated the otelServiceName default from "gh-aw" to "github.aw" for consistency. Template expressions in frontmatter now use {{ github.aw.episode.id }}, {{ github.aw.run.actor }}, etc.

@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot remove the templating support, user can use the GitHub actions expressions

Copilot AI and others added 2 commits May 21, 2026 21:11
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 21, 2026

@copilot remove the templating support, user can use the GitHub actions expressions

Done — removed the expandOTLPAttributeTemplate function and the template expansion logic from buildCustomOTLPAttributes. Attribute values are now used as-is; users can leverage GitHub Actions expressions (e.g. ${{ vars.MY_SESSION_ID }}) in workflow frontmatter for dynamic values. Committed in refactor: remove OTLP attribute template expansion support.

@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot

  • merge main
  • the value should go through the secret redaction API

Copilot AI and others added 2 commits May 21, 2026 22:12
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 21, 2026

@copilot

  • merge main
  • the value should go through the secret redaction API
    ...

Done in two commits:

  1. Merge main — resolved conflicts in the generated .lock.yml files (heredoc hash differences), accepting main's version.

  2. Secret redaction — added actions/setup/sh/mask_otlp_attributes.sh following the same pattern as mask_otlp_headers.sh. It reads GH_AW_OTLP_ATTRIBUTES and issues ::add-mask:: for each attribute value via the GitHub Actions ::add-mask:: workflow command. The compiler now generates a "Mask OTLP custom attribute values" step (via isOTLPAttributesPresent / generateOTLPAttributesMaskStep) in all three job types (activation, main agent, safe-outputs) whenever custom attributes are configured.

@pelikhan pelikhan marked this pull request as ready for review May 21, 2026 23:37
Copilot AI review requested due to automatic review settings May 21, 2026 23:37
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

PR Code Quality Reviewer completed the code quality review.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

Design Decision Gate 🏗️ failed during design decision gate check.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

🧪 Test Quality Sentinel completed test quality analysis.

@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot revert the change gh-aw -> GitHub.aw

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds workflow-configurable custom OTLP span attributes via frontmatter, propagated to the runtime through a new GH_AW_OTLP_ATTRIBUTES env var and masked in runner logs, with additional updates to OTLP naming/service metadata.

Changes:

  • Extend observability.otlp frontmatter schema to support attributes: { <key>: <template> } and encode it into GH_AW_OTLP_ATTRIBUTES for the runtime.
  • Add masking support for custom attribute values via a new mask_otlp_attributes.sh and compiler-injected masking steps.
  • Improve observability config merging for imports (endpoints + attributes), and update OTEL service/span naming from gh-aw.* to github.aw.* in several places (tests/workflows/runtime).
Show a summary per file
File Description
pkg/workflow/frontmatter_types.go Adds OTLPConfig.Attributes to the frontmatter schema and documents template usage.
pkg/workflow/observability_otlp.go Collects/merges/encodes custom OTLP attributes into GH_AW_OTLP_ATTRIBUTES; updates default OTEL service name.
pkg/workflow/compiler_orchestrator_workflow.go Merges imported OTLP endpoints and custom attributes into RawFrontmatter with correct precedence.
pkg/parser/import_field_extractor.go Extends import-side observability merge to include custom attributes (first import wins).
pkg/workflow/compiler_yaml_main_job.go Injects a masking step for GH_AW_OTLP_ATTRIBUTES values when present.
pkg/workflow/compiler_safe_outputs_job.go Injects a masking step for GH_AW_OTLP_ATTRIBUTES values when present.
pkg/workflow/compiler_activation_job_builder.go Injects a masking step for GH_AW_OTLP_ATTRIBUTES values when present.
actions/setup/sh/mask_otlp_attributes.sh New script to ::add-mask:: each attribute value from GH_AW_OTLP_ATTRIBUTES.
actions/setup/js/send_otlp_span.cjs Runtime support for reading/appending custom attributes to emitted spans.
actions/setup/js/emit_outcome_spans.cjs Updates outcome span naming/attribute keys; appends custom OTLP attributes to the summary span.
actions/setup/js/action_setup_otlp.cjs Updates setup span naming documentation to github.aw.*.
actions/setup/js/action_conclusion_otlp.cjs Updates conclusion span naming to github.aw.*.
pkg/workflow/observability_otlp_test.go Updates tests for new service name and adds tests for custom attribute extraction/injection.
pkg/workflow/observability_otlp_mask_script_test.go Adds tests for the new masking script behavior.
actions/setup/js/emit_outcome_spans.test.cjs Updates tests for renamed outcome span names/attribute keys.
actions/setup/js/action_otlp.test.cjs Updates tests for renamed setup/conclusion span names and attribute keys.
actions/setup/js/action_conclusion_otlp.test.cjs Updates tests for renamed conclusion span naming behavior.
.github/workflows/*.lock.yml Bulk updates OTEL service names in locked workflows to the new github.aw.* convention.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (1)

actions/setup/js/send_otlp_span.cjs:1052

  • This docstring says OTEL_SERVICE_NAME defaults to "gh-aw", but the compiler now defaults/injects "github.aw". Please update the default mentioned here (and/or the fallback value used in code) so the runtime documentation matches the compiler behavior.
  • Files reviewed: 239/240 changed files
  • Comments generated: 8

Comment on lines +1597 to +1602
func TestCollectOTLPCustomAttributes(t *testing.T) {
tests := []struct {
name string
frontmatter map[string]any
want map[string]string
}{
Comment on lines +95 to +103
func TestMaskOTLPAttributesScript(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("requires Linux bash script behavior")
}

_, file, _, ok := runtime.Caller(0)
require.True(t, ok, "runtime.Caller should resolve the current test file")

scriptPath := filepath.Join(filepath.Dir(file), "..", "..", "actions", "setup", "sh", "mask_otlp_attributes.sh")
Comment on lines +826 to +862
// extractOTLPAttributesFromObsMap reads the custom OTLP attributes map from a
// raw observability section (as parsed from an import's frontmatter). Only
// string values are accepted; non-string values are silently ignored.
// Returns nil when the field is absent or empty.
//
// Note: this intentionally duplicates the logic of
// workflow.extractOTLPCustomAttributesFromObsMap. The parser package must not
// import the workflow package (circular-dependency risk), so the helper lives
// here as a local copy. Both implementations must stay in sync.
func extractOTLPAttributesFromObsMap(obs map[string]any) map[string]string {
if obs == nil {
return nil
}
otlpAny, ok := obs["otlp"]
if !ok {
return nil
}
otlpMap, ok := otlpAny.(map[string]any)
if !ok {
return nil
}
attrsAny, ok := otlpMap["attributes"]
if !ok {
return nil
}
attrsMap, ok := attrsAny.(map[string]any)
if !ok {
return nil
}
result := make(map[string]string, len(attrsMap))
for k, v := range attrsMap {
if s, ok := v.(string); ok && k != "" {
result[k] = s
}
}
return result
}
Comment on lines +594 to +616
/**
* Build additional OTLP attribute objects from the GH_AW_OTLP_ATTRIBUTES
* environment variable.
*
* Attribute values are used as-is (use GitHub Actions expressions like
* `${{ vars.MY_VALUE }}` in workflow frontmatter for dynamic values).
* Attributes whose value is an empty string are omitted. When no custom
* attributes are configured, an empty array is returned.
*
* @returns {Array<{key: string, value: object}>}
*/
function buildCustomOTLPAttributes() {
const customDefs = parseOTLPCustomAttributes();
if (!customDefs) return [];

const result = [];
for (const [key, value] of Object.entries(customDefs)) {
if (typeof key !== "string" || !key || typeof value !== "string") continue;
if (value !== "") {
result.push(buildAttr(key, value));
}
}
return result;
Comment on lines 1192 to 1197
const experimentAssignments = readExperimentAssignments();
attributes.push(...buildExperimentAttributes(experimentAssignments));
attributes.push(...buildEpisodeAttributesFromContext(awInfo, runId, runAttempt));
// Append user-defined custom attributes from observability.otlp.attributes.
attributes.push(...buildCustomOTLPAttributes());

Comment on lines +262 to +264
// Append user-defined custom attributes from observability.otlp.attributes.
summaryAttributes.push(...buildCustomOTLPAttributes());

Comment on lines +239 to +250
// Attributes defines additional custom key-value string attributes to attach
// to every OTLP span emitted by this workflow (setup, agent, and conclusion).
// Values support template variables using {{ variable }} syntax, where the
// variable name is any OTLP attribute key already computed for the span
// (e.g. {{ github.aw.episode.id }}, {{ github.actor }}).
//
// Example – emit Langfuse session/user attributes alongside the standard ones:
// attributes:
// langfuse.session.id: "{{ github.aw.episode.id }}"
// session.id: "{{ github.aw.episode.id }}"
// langfuse.user.id: "{{ github.actor }}"
// user.id: "{{ github.actor }}"
Comment thread pkg/workflow/observability_otlp.go Outdated
Comment on lines +638 to +639
func otelServiceName(workflowData *WorkflowData) string {
const defaultServiceName = "gh-aw"
const defaultServiceName = "github.aw"
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Template expansion was removed but documentation still claims it exists — this will break user workflows.

### Review Summary

Critical Issues (must fix before merge)

  1. Misleading documentation — PR description and code comments describe {{ variable }} template expansion, but commit ff24b74 removed this feature. Users will configure templates that don't work.

  2. Formatting failures — Two test files are not gofmt-formatted and will fail CI checks.

  3. Breaking change — Default service name changed from "gh-aw" to "github.aw" without migration guidance, breaking existing observability dashboards.

What Works Well

✅ Clean merge strategy for custom attributes from imports (main workflow wins, first import wins)

✅ Proper masking of attribute values via secret redaction API

✅ Comprehensive test coverage for the attribute collection and merging logic

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • proxy.golang.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "proxy.golang.org"

See Network Configuration for more information.

🔎 Code quality review by PR Code Quality Reviewer · ● 1.2M

// langfuse.session.id: "{{ github.aw.episode.id }}"
// session.id: "{{ github.aw.episode.id }}"
// langfuse.user.id: "{{ github.actor }}"
// user.id: "{{ github.actor }}"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation claims template support that was removed: The comment describes {{ variable }} template expansion (lines 241-243), but template expansion was removed in commit ff24b74. Users will configure templates expecting them to work, but values will be used literally.

💡 Suggested fix

Update the comment to remove template references:

// Attributes is an optional map of custom OTLP attribute key-value pairs to attach
// to every OTLP span emitted by this workflow (setup, agent, and conclusion).
// Values are used as-is. Use GitHub Actions expressions like ${{ vars.MY_VALUE }}
// in workflow frontmatter for dynamic values.
//
// Example – emit Langfuse session/user attributes:
//   attributes:
//     langfuse.session.id: "${{ github.run_id }}"
//     langfuse.user.id:    "${{ github.actor }}"

The current documentation will cause user confusion and misconfigured workflows.

name string
frontmatter map[string]any
want map[string]string
}{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test code is not gofmt-formatted: This test block (lines 1598-1620+) is missing proper indentation, which will fail CI formatting checks.

💡 Suggested fix

Run gofmt -w pkg/workflow/observability_otlp_test.go to auto-format the file. The issue affects the entire TestCollectOTLPCustomAttributes function and its test table.

_, file, _, ok := runtime.Caller(0)
require.True(t, ok, "runtime.Caller should resolve the current test file")

scriptPath := filepath.Join(filepath.Dir(file), "..", "..", "actions", "setup", "sh", "mask_otlp_attributes.sh")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test code is not gofmt-formatted: This test block is missing proper indentation throughout, which will fail CI formatting checks.

💡 Suggested fix

Run gofmt -w pkg/workflow/observability_otlp_mask_script_test.go to auto-format the file.

Comment thread pkg/workflow/observability_otlp.go Outdated

func otelServiceName(workflowData *WorkflowData) string {
const defaultServiceName = "gh-aw"
const defaultServiceName = "github.aw"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Breaking change without migration path: Changing the default OTEL service name from "gh-aw" to "github.aw" will break existing dashboards, alerts, and queries that filter by service.name.

💡 Suggested fix

Options:

  1. Keep the old default ("gh-aw") and document the new naming convention as recommended but opt-in
  2. Add this to release notes as a breaking change with migration guidance for users to update their dashboards/queries
  3. Support both formats temporarily with a deprecation period

This affects 240 workflow files in this PR, so the impact is significant.

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /tdd and /diagnose — requesting changes on incomplete feature implementation.

📋 Critical Issue: Template Expansion Not Implemented

Feature Advertised vs. Implemented

The PR description and documentation promise template variable expansion:

Documented behavior:

observability:
  otlp:
    attributes:
      langfuse.session.id: "{{ gh-aw.episode.id }}"  # Should expand at runtime
      user.id: "{{ gh-aw.run.actor }}"

Actual behavior: The implementation only passes static values through as-is. Template expansion is completely missing:

  • No expandOTLPAttributeTemplate function exists (claimed in PR description)
  • buildCustomOTLPAttributes() has no parameter for existing span attributes
  • Tests only validate static attribute passthrough, not template expansion

Impact

Users who configure templates will get literal strings in their OTLP spans:

{"langfuse.session.id": "{{ gh-aw.episode.id }}"}

instead of the resolved value:

{"langfuse.session.id": "ep-12345"}

This breaks the entire use case for Langfuse session tracking.

What's Needed Before Merge

  1. Implement template expansion in send_otlp_span.cjs
  2. Add tests for template variable resolution
  3. Update function signature to accept existing attributes for template resolution
  4. Validate end-to-end that templates expand correctly in real workflow runs
✅ Positive Highlights
  • ✅ Clean Go compiler integration with GH_AW_OTLP_ATTRIBUTES env var
  • ✅ Proper JSON encoding/decoding for attribute transport
  • ✅ Good test structure for static attribute handling
  • ✅ Documentation is clear about the intended behavior

Recommendation: Do not merge until template expansion is fully implemented and tested. The current PR delivers only half the feature.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · ● 1.9M

// variable name is any OTLP attribute key already computed for the span
// (e.g. {{ github.aw.episode.id }}, {{ github.actor }}).
//
// Example – emit Langfuse session/user attributes alongside the standard ones:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] Template expansion feature is documented but not implemented in runtime.

💡 Missing implementation

The documentation promises template variable expansion:

// Values support template variables using {{ variable }} syntax, where the
// variable name is any OTLP attribute key already computed for the span
// (e.g. {{ github.aw.episode.id }}, {{ github.actor }}).

But send_otlp_span.cjs only has parseOTLPCustomAttributes and buildCustomOTLPAttributes — no expandOTLPAttributeTemplate function exists. The PR description claims this function was added, but it's missing from the implementation.

Impact: Users who configure templates like "{{ gh-aw.episode.id }}" will get literal strings in their OTLP spans instead of resolved values.

What's needed: Implement template expansion in buildCustomOTLPAttributes before merging:

function expandTemplate(template, attributes) {
  return template.replace(/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g, (match, varName) => {
    const attr = attributes.find(a => a.key === varName);
    if (!attr) return "";
    if (attr.value.stringValue) return attr.value.stringValue;
    if (attr.value.intValue !== undefined) return String(attr.value.intValue);
    return "";
  });
}

expect(buildCustomOTLPAttributes()).toEqual([]);
});

it("returns static attribute values as-is", () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] Test validates static values but doesn't test the advertised template expansion feature.

💡 Missing template expansion tests

This test verifies that static attribute values pass through as-is:

process.env.GH_AW_OTLP_ATTRIBUTES = JSON.stringify({
  "langfuse.session.id": "my-session",  // ← static value
  "langfuse.user.id": "my-user",
});

But the PR description and frontmatter documentation promise template variable expansion:

attributes:
  langfuse.session.id: "{{ gh-aw.episode.id }}"  # ← template, should expand
  user.id: "{{ gh-aw.run.actor }}"

What's missing: Tests that verify template expansion resolves variables from the span's existing attributes:

it("expands template variables from span attributes", () => {
  process.env.GH_AW_OTLP_ATTRIBUTES = JSON.stringify({
    "langfuse.session.id": "{{ gh-aw.episode.id }}",
    "user.id": "{{ github.actor }}"
  });
  
  const spanAttributes = [
    { key: "gh-aw.episode.id", value: { stringValue: "ep-12345" } },
    { key: "github.actor", value: { stringValue: "pelikhan" } }
  ];
  
  const result = buildCustomOTLPAttributes(spanAttributes);
  expect(result).toContainEqual({ key: "langfuse.session.id", value: { stringValue: "ep-12345" } });
  expect(result).toContainEqual({ key: "user.id", value: { stringValue: "pelikhan" } });
});

*
* @returns {Array<{key: string, value: object}>}
*/
function buildCustomOTLPAttributes() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] Function signature doesn't support template expansion as documented.

💡 Missing attributes parameter

buildCustomOTLPAttributes() is called after all standard span attributes are built:

attributes.push(...buildExperimentAttributes(experimentAssignments));
attributes.push(...buildEpisodeAttributesFromContext(awInfo, runId, runAttempt));
attributes.push(...buildCustomOTLPAttributes());  // ← called AFTER attributes are built

But template expansion requires access to the already-computed attributes to resolve variables like {{ gh-aw.episode.id }}. The current signature function buildCustomOTLPAttributes() has no way to access them.

Fix: Change the signature to accept the existing attributes:

function buildCustomOTLPAttributes(existingAttributes = []) {
  const customDefs = parseOTLPCustomAttributes();
  if (!customDefs) return [];

  const result = [];
  for (const [key, value] of Object.entries(customDefs)) {
    if (typeof key !== "string" || !key || typeof value !== "string") continue;
    const expanded = expandTemplate(value, existingAttributes);
    if (expanded !== "") {
      result.push(buildAttr(key, expanded));
    }
  }
  return result;
}

Then update call sites:

attributes.push(...buildCustomOTLPAttributes(attributes));

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 21, 2026

@copilot revert the change gh-aw -> GitHub.aw

Done — reverted the github.aw.gh-aw. rename across all OTLP attribute keys, span names, service name defaults, and related docs/tests in the latest commit.

@github-actions
Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

⚠️ Test Quality Score: 71/100 — Acceptable

Analyzed 67 test(s): 47 design, 10 implementation (missing assertions), 10 incomplete tests flagged.

📊 Metrics & Test Classification (67 tests analyzed)
Metric Value
New/modified tests analyzed 67
✅ Design tests (behavioral contracts) 47 (70%)
⚠️ Implementation tests (low value) 10 (15%)
Tests with error/edge cases 51 (76%)
Duplicate test clusters 0
Test inflation detected Yes (2 files exceed 2:1 ratio)
🚨 Coding-guideline violations 0

Test Classification Details

Go Tests (pkg/workflow/)

Test File Classification Issues Detected
TestIsOTLPAttributesPresent observability_otlp_test.go:665 ✅ Design None
TestGenerateOTLPAttributesMaskStep observability_otlp_test.go:710 ✅ Design None
TestInjectOTLPConfig_AttributesPresenceAfterInjection observability_otlp_test.go:752 ✅ Design None
TestCollectOTLPCustomAttributes observability_otlp_test.go:1597 ✅ Design None
TestInjectOTLPConfig_CustomAttributes observability_otlp_test.go:1689 ✅ Design None
TestMergeOTLPCustomAttributes observability_otlp_test.go:1747 ✅ Design None
TestEncodeOTLPCustomAttributes observability_otlp_test.go:1778 ✅ Design None
TestMaskOTLPAttributesScript observability_otlp_mask_script_test.go:95 ✅ Design None

Go Summary: 8/8 tests are design tests with proper table-driven structure, edge cases, and no mock libraries.

JavaScript Tests (actions/setup/js/)

Design tests (49): Most vitest tests with assertions verify observable outputs correctly.

Implementation tests (10 with zero assertions):

  • Lines 3988, 5286, 5304, 5329, 5841, 5878, 5888, 5896, 5945, 6024 in send_otlp_span.test.cjs

Key JavaScript findings:

  • ✅ Low mock usage (3%) - only I/O boundaries mocked (fetch, fs)
  • 10 tests have zero assertions - they run code but don't verify behavior
  • Zero error coverage - no .toThrow(), .toThrowError(), or .rejects in this diff

Test Inflation Analysis

File Test Lines Prod Lines Ratio Status
observability_otlp_test.go +307 +134 2.29:1 ⚠️ Exceeds 2:1
send_otlp_span.test.cjs +656 +148 4.43:1 Severe inflation
observability_otlp_mask_script_test.go +63 +134 0.47:1 ✅ Proportional
action_conclusion_otlp.test.cjs +18 +9 2.0:1 ✅ At threshold
emit_outcome_spans.test.cjs +28 +49 0.57:1 ✅ Proportional

Language Support

Tests analyzed:

  • 🐹 Go (*_test.go): 8 tests — all unit tests (//go:build !integration)
  • 🟨 JavaScript (*.test.cjs): 59 tests (vitest)
⚠️ Flagged Tests — Requires Review (10 issues)

⚠️ 10 JavaScript tests with zero assertions (send_otlp_span.test.cjs)

What design invariant do these tests enforce? None — they run code but make no assertions about outcomes.

What would break if deleted? Nothing. These tests pass even if the implementation is broken.

Suggested improvement: Add assertions to verify the expected behavior. Examples:

  1. Line 5841"returns the parsed object when the env var is a valid JSON object"

    // ❌ Current: No assertions
    const result = parseOTLPCustomAttributes();
    
    // ✅ Should assert:
    expect(result).toEqual({
      "langfuse.session.id": "my-session",
      "langfuse.user.id": "my-user"
    });
  2. Line 5878"returns static attribute values as-is"

    // ❌ Current: No assertions
    const result = buildCustomOTLPAttributes();
    
    // ✅ Should assert:
    expect(result).toContainEqual({ 
      key: "langfuse.session.id", 
      value: { stringValue: "my-session" } 
    });
  3. Lines 5945, 6024 — Custom attribute integration tests

    // ❌ Current: Mocks fetch but doesn't assert span attributes
    
    // ✅ Should assert:
    const [_url, init] = mockFetch.mock.calls[0];
    const payload = JSON.parse(init.body);
    const attrs = payload.resourceSpans[0].scopeSpans[0].spans[0].attributes;
    expect(attrs).toContainEqual({ 
      key: "langfuse.session.id", 
      value: { stringValue: "ep-123" } 
    });

Missing error coverage: None of the 59 JavaScript tests in this diff use .toThrow(), .toThrowError(), or .rejects. Consider adding tests for:

  • Invalid JSON in GH_AW_OTLP_ATTRIBUTES
  • Missing required environment variables
  • Malformed custom attribute keys/values
  • Network failures when sending spans

Verdict

Check passed. 14.9% of new tests are implementation tests (threshold: 30%). However, 10 JavaScript tests have zero assertions and should be completed before merge. Test inflation is high in send_otlp_span.test.cjs (4.43:1 ratio) — consider splitting into smaller, focused test files.

📖 Understanding Test Classifications

Design Tests (High Value) verify what the system does:

  • Assert on observable outputs, return values, or state changes
  • Cover error paths and boundary conditions
  • Would catch a behavioral regression if deleted
  • Remain valid even after internal refactoring

Implementation Tests (Low Value) verify how the system does it:

  • Have zero assertions (run code but don't verify)
  • Only test the happy path with typical inputs
  • Break during legitimate refactoring even when behavior is correct
  • Give false assurance: they pass even when the system is wrong

Goal: Shift toward tests that describe the system's behavioral contract — the promises it makes to its users and collaborators.


Workflow Run: §26259479655

🧪 Test quality analysis by Test Quality Sentinel · ● 2.4M ·

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 71/100. Test quality is acceptable — 14.9% of new tests are implementation tests (threshold: 30%). However, 10 JavaScript tests lack assertions and test inflation is high in send_otlp_span.test.cjs (4.43:1). See detailed comment for recommendations.

@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot merge main and recompile

Copilot AI and others added 2 commits May 22, 2026 00:01
…ibutes-support

# Conflicts:
#	.github/workflows/daily-cache-strategy-analyzer.lock.yml
#	.github/workflows/daily-fact.lock.yml
#	.github/workflows/daily-observability-report.lock.yml
#	.github/workflows/duplicate-code-detector.lock.yml
#	.github/workflows/grumpy-reviewer.lock.yml
#	.github/workflows/issue-arborist.lock.yml
#	.github/workflows/necromancer.lock.yml
#	.github/workflows/schema-feature-coverage.lock.yml
#	.github/workflows/smoke-call-workflow.lock.yml
#	.github/workflows/smoke-codex.lock.yml

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 22, 2026

@copilot merge main and recompile

Done — merged main, rebuilt the binary, and recompiled all 233 workflows (0 errors).

@pelikhan pelikhan merged commit 6716837 into main May 22, 2026
@pelikhan pelikhan deleted the copilot/add-otlp-attributes-support branch May 22, 2026 00:55
github-actions Bot added a commit that referenced this pull request May 22, 2026
Document the new observability.otlp.attributes frontmatter field
introduced in #33846. Adds a reference row in the OpenTelemetry
Fields table, a dedicated "Custom span attributes" section with
an example using GitHub Actions expressions, a note about runner
log masking, and a corresponding mention in the frontmatter
reference.

Co-Authored-By: Claude Opus 4.7 (1M context) <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.

3 participants