Repository navigation
Feature: reuse root workflow metadata in custom OTLP attributes, including shared imports #70377
Description
Feature request
Allow whole-value references to the root workflow's custom metadata in observability.otlp.attributes and observability.otlp.resource-attributes, including when the OTLP configuration comes from a shared import.
Motivation
Workflow metadata describes properties such as version, category and ownership. Making those values reusable gives the existing metadata field practical value beyond descriptive annotation, while keeping it the single source of truth.
OpenTelemetry is a natural destination for this information: operators want to group workflow costs, failures and outcomes by version, category or owner. For example, when a cheaper implementation is released, version-tagged telemetry lets us distinguish older installations from the new implementation rather than infer the version from the model or inspect each repository's workflow source.
Today, authors must repeat metadata values in OTLP configuration or pass them again through github.aw.import-inputs. This creates duplication and drift: metadata can be updated while telemetry continues to report an old value. A shared OTLP import should be able to reference each importing workflow's metadata without callers declaring those values twice or requiring custom deploy-time injection.
This is opt-in reuse of selected metadata, not automatic export of the entire metadata map.
Proposed configuration
Syntax below is a proposal for maintainers to confirm. It follows the existing compile-time github.aw.import-inputs.* namespace pattern; it is not a new context for GitHub Actions to evaluate.
Root workflow:
---
on:
workflow_dispatch:
engine: copilot
metadata:
version: "0.5.1"
category: automation
imports:
- shared/otlp.md
---
Perform the workflow's task.Shared component:
---
observability:
otlp:
endpoint: ${{ secrets.OTLP_ENDPOINT }}
headers:
Authorization: ${{ secrets.OTLP_TOKEN }}
attributes:
catalogue.version: "${{ github.aw.metadata.version }}"
catalogue.category: "${{ github.aw.metadata.category }}"
---The compiled custom attributes would contain ordinary strings:
{"catalogue.version":"0.5.1","catalogue.category":"automation"}The same reference syntax would work in resource-attributes, subject to its existing restrictions.
Proposed contract and acceptance criteria
- Resolve at compile time, using the root workflow's validated top-level
metadatamap. A reference inside an imported component refers to the root workflow, not the component's own metadata. - Support whole-value references only. No concatenation, embedded expressions, defaults, functions or recursive substitution. Define the supported selector spelling explicitly; initially simple identifiers such as
version,categoryandownerare sufficient. Do not accidentally interpret flat metadata keys as nested objects. - Restrict resolution to values in the two custom OTLP attribute maps. No substitutions in attribute names, endpoints, headers, permissions, arbitrary YAML or prompt content.
- A missing metadata map or referenced key produces a compile-time error identifying the attribute and missing key. An unsupported use of the new namespace in these maps should fail clearly rather than export an unresolved placeholder.
- Metadata and declarative OTLP attribute values remain strings. Preserve existing metadata validation: numeric and boolean YAML values are invalid, not silently coerced. An unquoted semantic version such as
0.1.8is already a YAML string. - Copy validated metadata strings without type inference. Reject unsafe substituted expression content where necessary: metadata must not introduce a fresh GitHub Actions expression or undergo a second evaluation.
- Leave ordinary literal values and existing GitHub Actions expressions unchanged.
- Preserve existing metadata import semantics, attribute precedence, output encoding, masking, sanitisation and resource-attribute restrictions.
- Do not automatically export any metadata; only explicitly referenced fields are selected.
- Generated locks contain resolved values rather than metadata placeholders, and existing OTLP emitters export those values as string attributes.
Source analysis
Reviewed upstream main at 53152dee6f9658a2d48600be8f861c44222ba783.
pkg/parser/schemas/main_workflow_schema.jsonalready validates metadata as a flat string map (keys up to 64 characters, values up to 1,024 characters).pkg/parser/import_input_substitution.goprovides precedent for gh-aw-owned compile-time references. Unlike import inputs, this proposal uses already-parsed metadata and should not do unrestricted raw-YAML replacement.mergeImportedWorkflowConfiguration()in pkg/workflow/compiler_orchestrator_workflow.go merges imported observability beforeinjectOTLPConfig(), providing a suitable resolution stage.pkg/workflow/observability_otlp.gocollects and emits the string maps throughGH_AW_OTLP_ATTRIBUTESandOTEL_RESOURCE_ATTRIBUTES.pkg/parser/import_observability.goaggregates custom span attributes across imports, but does not currently includeresource-attributesin that aggregation. Supporting shared resource attributes requires a companion change rather than assuming they survive that path.
Proposed implementation plan
These are suggested locations and responsibilities, not a requirement to adopt a particular internal API.
- Add a focused compiler helper in
pkg/workflow/observability_metadata.go, with tests inobservability_metadata_test.go. Go functions are sufficient; no new class, dependency or general templating engine is needed. Suggested helper:resolveOTLPMetadataReferences(workflowData *WorkflowData) error. It should read root metadata from the original validated frontmatter, inspect the effective custom attribute maps, validate whole-value references, and return resolved string maps/errors without mutating the source metadata or hashing inputs. - Wire resolution into the compiler in
compiler_orchestrator_workflow.go, aftermergeImportedObservability()and before OTLP env emission. Ensure resolution precedes consumers/validation that need the final values, and that raw/typed frontmatter fallbacks cannot reintroduce unresolved placeholders. Propagate errors using the compiler's normal file-aware diagnostics. - Preserve shared resource attributes in
pkg/parser/import_observability.go, extending extraction/aggregation of string maps with tests inimport_observability_test.go. Follow existing first-occurrence import precedence and retain existing main/import precedence rather than changing it as part of this feature. - Reuse existing OTLP encoding/export in
observability_otlp.go. Resolved strings should flow through current JSON/YAML/resource-attribute encoding, masking and validation. Explicitly guard against metadata strings containing executable expression delimiters. No runtime metadata lookup or exporter change should be needed. - Add end-to-end compiler and exporter regression coverage, extending
observability_otlp_test.goand existing import/compiler integration fixtures. If useful, add a focused case inactions/setup/js/send_otlp_span.test.cjsconfirming the resolved string is emitted with the ordinary custom-attribute path. - Document the feature in
docs/src/content/docs/reference/open-telemetry.mdx,frontmatter.md, andimports.mdwhere appropriate. Show the shared-import example and explain root scope, whole-value syntax, string-only values, missing-key errors, and opt-in export. Record the change using the repository's normal changeset process. - Validate using upstream's required
make agent-finishgate, fixing failures without weakening existing checks.
Test cases
| Area | Cases |
|---|---|
| Inline references | Root metadata resolves in both custom span and resource attributes. |
| Shared/nested imports | Shared OTLP references resolve against root metadata; a shared component's own same-named metadata must not replace the root value. |
| Multiple callers | Compile two workflows using the same shared import with different versions; values remain isolated, including reuse of cached imports. |
| Missing fields | Missing metadata map, missing key and malformed/unsupported reference produce actionable compilation errors. |
| Whole-value scope | Whitespace handling is defined; embedded/concatenated uses of the new namespace are rejected. Existing unrelated expressions remain unchanged. |
| Types | Quoted strings and version: 0.1.8 succeed; numeric and boolean metadata values remain schema-validation failures. |
| Encoding/safety | Quotes, backslashes, newlines, Unicode, empty strings and metadata value-length boundaries; expression-looking metadata cannot introduce executable expressions. Existing empty-value export behaviour remains unchanged. |
| Import aggregation | Resource attributes survive shared-import aggregation; existing collision precedence is retained. |
| Compatibility | Workflows without new references retain their existing configuration/output behaviour; metadata/hash input is not rewritten. |
| Export | Generated lock/env has resolved strings, no new metadata placeholders; existing custom-attribute export preserves the expected string type and sanitisation. |
Non-goals
General-purpose variables; arbitrary frontmatter/prompt interpolation; embedded formatting; metadata inheritance changes; typed metadata or string coercion; automatic export of all metadata; repurposing service.version; runtime GitHub/API lookups.
Would this scoped approach and compile-time namespace fit the project's direction?
Activity