🤖 This issue has been generated by Claude Code.
Summary
gh-aw injects OTEL_RESOURCE_ATTRIBUTES into the workflow env with un-encoded values. In particular, a workflow whose name: contains a space (very common, e.g. name: My Workflow) produces:
OTEL_RESOURCE_ATTRIBUTES: 'gh-aw.workflow.name=My Workflow,gh-aw.repository=...,gh-aw.run.id=...,gh-aw.engine.id=claude'
The raw space in gh-aw.workflow.name=My Workflow makes the value non-compliant with the OpenTelemetry env-var format. Strict OTel SDK consumers that share this env var (e.g. the Claude Code engine's JS OTel SDK) then discard the entire OTEL_RESOURCE_ATTRIBUTES variable — so none of the resource attributes (not even gh-aw's own gh-aw.*) appear on that engine's spans.
gh-aw's own (Go) OTel SDK is lenient and accepts the raw space, so gh-aw's own setup/conclusion spans look correct — which masks the bug. It only manifests on downstream consumers that read the same variable.
Version observed: v0.79.1.
Root cause
pkg/workflow/observability_otlp.go:
// OTEL_RESOURCE_ATTRIBUTES values must escape backslash (`\`), comma (`,`), and
// equals (`=`) per the OpenTelemetry env-var resource attribute grammar.
var otelResourceValueEscaper = strings.NewReplacer(`\`, `\\`, ",", `\,`, "=", `\=`)
func escapeOTELResourceAttributeValue(value string) string {
return otelResourceValueEscaper.Replace(value)
}
func otelResourceAttributes(workflowData *WorkflowData) string {
// ...
workflowNameAttrValue = escapeOTELResourceAttributeValue(workflowName) // "My Workflow" -> "My Workflow" (space untouched)
attrs := []string{ "gh-aw.workflow.name=" + workflowNameAttrValue, ... }
return strings.Join(attrs, ",")
}
Two problems with the escaper:
- Spaces (and other non-
unreserved characters) are not encoded at all. The workflow name: flows in verbatim, so any space survives into the emitted value.
, and = are backslash-escaped (\,, \=) rather than percent-encoded. The OTel spec requires percent-encoding, not backslash escaping, so the current approach is also non-conformant (it would break a workflow name containing , or = on a strict consumer).
Why this violates the spec
Per the OpenTelemetry Resource SDK spec, OTEL_RESOURCE_ATTRIBUTES is a list of key=value pairs whose values are percent-encoded (RFC 3986 §2.1):
, and = MUST be percent-encoded; other characters MAY be.
-
"On any decoding failure, the entire environment variable value SHOULD be discarded and an error SHOULD be reported."
That last clause is exactly the observed symptom: one malformed value (the space) causes a conformant consumer to drop the whole variable, not just the offending attribute. (The format descends from the W3C Baggage baggage-octet grammar, which explicitly disallows space, comma, semicolon, backslash, and DQUOTE in values.)
Reproduction
- A workflow with a space in its name and OTLP observability enabled:
---
name: My Workflow
engine: claude
observability:
otlp:
endpoint: ${{ secrets.OTLP_ENDPOINT }}
---
gh aw compile → the generated lock has OTEL_RESOURCE_ATTRIBUTES: 'gh-aw.workflow.name=My Workflow,...' (raw space) at workflow level.
- Run it and inspect the engine's exported spans (Claude Code CLI). The spans' resource carries none of the
OTEL_RESOURCE_ATTRIBUTES keys (only the engine's built-ins such as service.name/service.version), because the engine's OTel SDK discarded the malformed variable.
For contrast, setting the name to My_Workflow (no space) makes all the attributes appear — confirming the space is the trigger.
Suggested fix
Percent-encode resource-attribute values per RFC 3986 instead of backslash-escaping. At minimum encode space, ,, and = (%20, %2C, %3D); ideally percent-encode everything outside the unreserved set. Compliant consumers (including gh-aw's own Go SDK) percent-decode on read, so this stays correct everywhere. Example:
// Replace the strings.NewReplacer backslash-escaper with RFC 3986 percent-encoding
// of every byte outside the unreserved set (ALPHA / DIGIT / "-" / "." / "_" / "~").
func escapeOTELResourceAttributeValue(value string) string {
var b strings.Builder
for i := 0; i < len(value); i++ {
c := value[i]
if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
c == '-' || c == '.' || c == '_' || c == '~' {
b.WriteByte(c)
} else {
fmt.Fprintf(&b, "%%%02X", c)
}
}
return b.String()
}
(Note: GitHub Actions expression placeholders like ${{ github.repository }} are not run through the escaper today; if any expanded value can contain spaces/reserved chars, those would need encoding at runtime too.)
Happy to send a PR.
🤖 This issue has been generated by Claude Code.
Summary
gh-aw injects
OTEL_RESOURCE_ATTRIBUTESinto the workflow env with un-encoded values. In particular, a workflow whosename:contains a space (very common, e.g.name: My Workflow) produces:The raw space in
gh-aw.workflow.name=My Workflowmakes the value non-compliant with the OpenTelemetry env-var format. Strict OTel SDK consumers that share this env var (e.g. the Claude Code engine's JS OTel SDK) then discard the entireOTEL_RESOURCE_ATTRIBUTESvariable — so none of the resource attributes (not even gh-aw's owngh-aw.*) appear on that engine's spans.gh-aw's own (Go) OTel SDK is lenient and accepts the raw space, so gh-aw's own setup/conclusion spans look correct — which masks the bug. It only manifests on downstream consumers that read the same variable.
Version observed:
v0.79.1.Root cause
pkg/workflow/observability_otlp.go:Two problems with the escaper:
unreservedcharacters) are not encoded at all. The workflowname:flows in verbatim, so any space survives into the emitted value.,and=are backslash-escaped (\,,\=) rather than percent-encoded. The OTel spec requires percent-encoding, not backslash escaping, so the current approach is also non-conformant (it would break a workflow name containing,or=on a strict consumer).Why this violates the spec
Per the OpenTelemetry Resource SDK spec,
OTEL_RESOURCE_ATTRIBUTESis a list ofkey=valuepairs whose values are percent-encoded (RFC 3986 §2.1):,and=MUST be percent-encoded; other characters MAY be.That last clause is exactly the observed symptom: one malformed value (the space) causes a conformant consumer to drop the whole variable, not just the offending attribute. (The format descends from the W3C Baggage
baggage-octetgrammar, which explicitly disallows space, comma, semicolon, backslash, and DQUOTE in values.)Reproduction
gh aw compile→ the generated lock hasOTEL_RESOURCE_ATTRIBUTES: 'gh-aw.workflow.name=My Workflow,...'(raw space) at workflow level.OTEL_RESOURCE_ATTRIBUTESkeys (only the engine's built-ins such asservice.name/service.version), because the engine's OTel SDK discarded the malformed variable.For contrast, setting the name to
My_Workflow(no space) makes all the attributes appear — confirming the space is the trigger.Suggested fix
Percent-encode resource-attribute values per RFC 3986 instead of backslash-escaping. At minimum encode space,
,, and=(%20,%2C,%3D); ideally percent-encode everything outside theunreservedset. Compliant consumers (including gh-aw's own Go SDK) percent-decode on read, so this stays correct everywhere. Example:(Note: GitHub Actions expression placeholders like
${{ github.repository }}are not run through the escaper today; if any expanded value can contain spaces/reserved chars, those would need encoding at runtime too.)Happy to send a PR.