Add observability.otlp.ignore-if-missing to downgrade missing OTLP config to warnings#32173
Conversation
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
observability.otlp.ignore-if-missing to downgrade missing OTLP config to warnings
|
@copilot align ignore-if-missing with other popular actions field names |
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
|
@copilot remove ignore-if-missing support or mention for this feature |
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds opt-in handling for missing/empty OTLP endpoint and header values at workflow runtime. Instead of failing the MCP gateway when OTLP secrets resolve to empty strings, users can set an if-missing mode that lets the gateway proceed without OTLP configuration.
Changes:
- New
observability.otlp.if-missingfrontmatter field (string enum:error/warn/ignore) plumbed through types, schema, and docs. - Compiler emits
GH_AW_OTLP_IF_MISSING: ignoreinto the workflow env when set toignore. start_mcp_gateway.cjsreads that env var and stripsgateway.opentelemetry(or empty headers) with a warning instead of failing.
Show a summary per file
| File | Description |
|---|---|
| pkg/workflow/observability_otlp.go | New helpers to read if-missing from parsed and raw frontmatter and inject GH_AW_OTLP_IF_MISSING. |
| pkg/workflow/observability_otlp_test.go | Go tests for the new helper and env-var injection path. |
| pkg/workflow/frontmatter_types.go | Adds IfMissing field to OTLPConfig. |
| pkg/parser/schemas/main_workflow_schema.json | Schema entry with enum error/warn/ignore. |
| docs/src/content/docs/reference/frontmatter.md | Documents the field and new env var. |
| actions/setup/js/start_mcp_gateway.cjs | Runtime: strips OTLP gateway block / empty headers under ignore, switches stdin payload to re-serialized JSON. |
| actions/setup/js/assign_agent_helpers.cjs | Comment-only update to reflect renamed field. |
Note: PR description references an older ignore-if-missing: true boolean API and GH_AW_OTLP_IGNORE_IF_MISSING env var, which no longer match the shipped implementation.
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/start_mcp_gateway.cjs:346
- The gateway stdin payload was previously the exact bytes read from stdin (
mcpConfig); it is nowJSON.stringify(configObj), which re-serializes the parsed JSON. This silently strips comments-of-formatting/whitespace, reorders keys to insertion order, and changes any numeric/string representations the gateway might rely on. For typical JSON this is fine, but the change is unconditional — it now happens even whenif-missing: ignoreis not configured. Consider only re-serializing whenapplyOTLPIgnoreIfMissingactually mutatedconfigObj, to minimize the blast radius of this change.
child.stdin.write(JSON.stringify(configObj));
- Files reviewed: 7/7 changed files
- Comments generated: 8
| // IfMissing controls runtime behavior when OTLP endpoint/header values are | ||
| // missing (for example because a referenced secret is unset). Supported values: | ||
| // - "error" (default): fail workflow startup | ||
| // - "warn": keep OTLP config and continue (gateway may still fail with invalid OTLP config) | ||
| // - "ignore": log a warning and skip OTLP gateway config | ||
| IfMissing string `json:"if-missing,omitempty"` |
| "enum": ["error", "warn", "ignore"], | ||
| "default": "error", | ||
| "description": "How to handle missing OTLP endpoint/header values at runtime (for example from unset secrets). 'error' fails workflow startup (default), 'warn' continues with OTLP config, and 'ignore' logs a warning and skips OTLP gateway configuration." |
| const headers = otelConfig["headers"]; | ||
| if (typeof headers === "string" && headers.trim() === "") { | ||
| delete otelConfig["headers"]; | ||
| core.info("WARNING: OTLP headers are missing/empty and GH_AW_OTLP_IF_MISSING=ignore; continuing without MCP gateway OTLP headers."); | ||
| } |
| const endpoint = typeof otelConfig["endpoint"] === "string" ? otelConfig["endpoint"].trim() : ""; | ||
| if (!endpoint) { | ||
| delete gateway["opentelemetry"]; | ||
| core.info("WARNING: OTLP endpoint is missing/empty and GH_AW_OTLP_IF_MISSING=ignore; skipping MCP gateway OTLP configuration."); | ||
| return; | ||
| } |
| core.info("WARNING: OTLP endpoint is missing/empty and GH_AW_OTLP_IF_MISSING=ignore; skipping MCP gateway OTLP configuration."); | ||
| return; | ||
| } | ||
| const headers = otelConfig["headers"]; | ||
| if (typeof headers === "string" && headers.trim() === "") { | ||
| delete otelConfig["headers"]; | ||
| core.info("WARNING: OTLP headers are missing/empty and GH_AW_OTLP_IF_MISSING=ignore; continuing without MCP gateway OTLP headers."); |
| function applyOTLPIgnoreIfMissing(configObj) { | ||
| if (!isOTLPIfMissingIgnore(process.env.GH_AW_OTLP_IF_MISSING)) { | ||
| return; | ||
| } | ||
| const gw = configObj.gateway; | ||
| if (!gw || typeof gw !== "object" || Array.isArray(gw)) { | ||
| return; | ||
| } | ||
| const gateway = /** @type {Record<string, unknown>} */ gw; | ||
| const otel = gateway["opentelemetry"]; | ||
| if (!otel || typeof otel !== "object" || Array.isArray(otel)) { | ||
| return; | ||
| } | ||
| const otelConfig = /** @type {Record<string, unknown>} */ otel; | ||
| const endpoint = typeof otelConfig["endpoint"] === "string" ? otelConfig["endpoint"].trim() : ""; | ||
| if (!endpoint) { | ||
| delete gateway["opentelemetry"]; | ||
| core.info("WARNING: OTLP endpoint is missing/empty and GH_AW_OTLP_IF_MISSING=ignore; skipping MCP gateway OTLP configuration."); | ||
| return; | ||
| } | ||
| const headers = otelConfig["headers"]; | ||
| if (typeof headers === "string" && headers.trim() === "") { | ||
| delete otelConfig["headers"]; | ||
| core.info("WARNING: OTLP headers are missing/empty and GH_AW_OTLP_IF_MISSING=ignore; continuing without MCP gateway OTLP headers."); | ||
| } | ||
| } |
| // getOTLPIgnoreIfMissing returns true when observability.otlp.if-missing is set | ||
| // to "ignore". | ||
| func getOTLPIgnoreIfMissing(config *FrontmatterConfig, frontmatter map[string]any) bool { | ||
| if config != nil && config.Observability != nil && config.Observability.OTLP != nil { | ||
| if isOTLPIfMissingIgnore(config.Observability.OTLP.IfMissing) { | ||
| return true | ||
| } | ||
| } | ||
| if frontmatter == nil { | ||
| return false | ||
| } | ||
| obsAny, ok := frontmatter["observability"] | ||
| if !ok { | ||
| return false | ||
| } | ||
| obsMap, ok := obsAny.(map[string]any) | ||
| if !ok { | ||
| return false | ||
| } | ||
| otlpAny, ok := obsMap["otlp"] | ||
| if !ok { | ||
| return false | ||
| } | ||
| otlpMap, ok := otlpAny.(map[string]any) | ||
| if !ok { | ||
| return false | ||
| } | ||
| if v, ok := otlpMap["if-missing"].(string); ok && isOTLPIfMissingIgnore(v) { | ||
| return true | ||
| } | ||
| return false | ||
| } |
| func isOTLPIfMissingIgnore(mode string) bool { | ||
| switch strings.ToLower(strings.TrimSpace(mode)) { | ||
| case "ignore": | ||
| return true | ||
| case "", "error", "warn": | ||
| return false | ||
| default: | ||
| // Unknown values are rejected by schema validation. Keep fail-closed behavior. | ||
| return false | ||
| } | ||
| } |
|
@copilot review all comments and all review pull request comments |
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>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Done — I reviewed all PR/review comments and addressed the actionable items in commits Key fixes now in the branch include:
|
|
|
New Feature
What does this feature do?
This adds an
observability.otlp.ignore-if-missingflag so workflows can continue when OTLP endpoint/header secrets resolve to empty values at runtime. Instead of failing gateway startup, gh-aw now logs a warning and skips OTLP gateway config for that run.Why is this feature needed?
Current behavior is fail-fast when OTLP config is present but runtime values are missing (commonly unset secrets). This makes observability config brittle in shared/imported workflows where telemetry may be optional.
Implementation details
Frontmatter + schema support
observability.otlp.ignore-if-missingto typed frontmatter (OTLPConfig) and JSON schema.Runtime propagation
GH_AW_OTLP_IGNORE_IF_MISSING: truewhen the flag is enabled.Gateway behavior change
start_mcp_gateway.cjsnow applies ignore-if-missing semantics:gateway.opentelemetryblock (no hard failure),Docs update