Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion actions/setup/js/generate_observability_summary.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function collectObservabilityData() {
const agentOutput = readJSONIfExists(AGENT_OUTPUT_PATH) || { items: [], errors: [] };
const items = Array.isArray(agentOutput.items) ? agentOutput.items : [];
const errors = Array.isArray(agentOutput.errors) ? agentOutput.errors : [];
const traceId = awInfo.context && typeof awInfo.context.workflow_call_id === "string" ? awInfo.context.workflow_call_id : "";
const traceId = awInfo.context ? awInfo.context.otel_trace_id || awInfo.context.workflow_call_id || "" : "";
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

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

traceId selection no longer verifies that otel_trace_id / workflow_call_id are strings (and, for otel_trace_id, a valid 32-hex OTLP trace ID). If otel_trace_id is present but invalid (wrong length / non-hex / non-string), the summary will still display it and will not fall back, potentially showing a value that cannot be searched in backends. Consider normalizing (trim().toLowerCase()), validating (e.g. /^[0-9a-f]{32}$/), and only preferring otel_trace_id when valid; otherwise fall back to a string workflow_call_id (matching the previous type-guard behavior).

See below for a potential fix:

function normalizeString(value) {
  return typeof value === "string" ? value.trim() : "";
}

function normalizeOtelTraceId(value) {
  const normalized = normalizeString(value).toLowerCase();
  return /^[0-9a-f]{32}$/.test(normalized) ? normalized : "";
}

function selectTraceId(context) {
  if (!context) {
    return "";
  }

  const otelTraceId = normalizeOtelTraceId(context.otel_trace_id);
  if (otelTraceId) {
    return otelTraceId;
  }

  return normalizeString(context.workflow_call_id);
}

function collectObservabilityData() {
  const awInfo = readJSONIfExists(AW_INFO_PATH) || {};
  const agentOutput = readJSONIfExists(AGENT_OUTPUT_PATH) || { items: [], errors: [] };
  const items = Array.isArray(agentOutput.items) ? agentOutput.items : [];
  const errors = Array.isArray(agentOutput.errors) ? agentOutput.errors : [];
  const traceId = selectTraceId(awInfo.context);

Copilot uses AI. Check for mistakes.

return {
workflowName: awInfo.workflow_name || "",
Expand Down
24 changes: 22 additions & 2 deletions actions/setup/js/generate_observability_summary.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe("generate_observability_summary.cjs", () => {
engine_id: "copilot",
staged: false,
firewall_enabled: true,
context: { workflow_call_id: "trace-123" },
context: { workflow_call_id: "12345678901-1", otel_trace_id: "a3f2c8d1e4b7091f6a5c2e3d8f401b72" },
})
);
fs.writeFileSync(
Expand All @@ -57,7 +57,8 @@ describe("generate_observability_summary.cjs", () => {
expect(summary).toContain("<summary>Observability</summary>");
expect(summary).toContain("- **workflow**: triage-workflow");
expect(summary).toContain("- **engine**: copilot");
expect(summary).toContain("- **trace id**: trace-123");
expect(summary).toContain("- **trace id**: a3f2c8d1e4b7091f6a5c2e3d8f401b72");
expect(summary).not.toContain("12345678901-1");
expect(summary).toContain("- **posture**: write-capable");
expect(summary).toContain("- **created items**: 2");
expect(summary).toContain("- **blocked requests**: 1");
Expand All @@ -67,6 +68,25 @@ describe("generate_observability_summary.cjs", () => {
expect(mockCore.summary.write).toHaveBeenCalledTimes(1);
});

it("falls back to workflow_call_id when otel_trace_id is absent", async () => {
fs.writeFileSync(
"/tmp/gh-aw/aw_info.json",
JSON.stringify({
workflow_name: "triage-workflow",
engine_id: "copilot",
staged: false,
firewall_enabled: false,
context: { workflow_call_id: "12345678901-1" },
})
);

await module.main(mockCore);

expect(mockCore.summary.addRaw).toHaveBeenCalledTimes(1);
const summary = mockCore.summary.addRaw.mock.calls[0][0];
expect(summary).toContain("- **trace id**: 12345678901-1");
});

it("skips summary generation when opt-in mode is disabled", async () => {
process.env.GH_AW_OBSERVABILITY_JOB_SUMMARY = "off";

Expand Down
Loading