From 93cf31ac9dbdf02bc4a163f299768dba98fa2e9c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 3 Apr 2026 22:24:54 +0000
Subject: [PATCH 1/2] Initial plan


From 3cc9fdabf4c64d62769f36d7a1878d8a523e0c3d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 3 Apr 2026 22:38:23 +0000
Subject: [PATCH 2/2] feat(logs): add classification field to RunData in logs
 tool response

Add a top-level `classification` string field to `RunData` so that
consumers of the MCP `logs` tool and `gh aw logs --json` can access
the risk classification without traversing the nested
`comparison.classification.label` path (which returns null when no
baseline is available).

Possible values: "risky", "normal", "baseline", "unclassified".

- pkg/cli/logs_report.go: new Classification field + deriveRunClassification helper
- pkg/cli/mcp_logs_guardrail.go: update schema description
- pkg/cli/logs_report_test.go: TestDeriveRunClassification unit tests

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/d90a23df-256e-4f7a-b65b-8de6e8ace6cf

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
---
 pkg/cli/logs_report.go        | 26 +++++++++++++++
 pkg/cli/logs_report_test.go   | 61 +++++++++++++++++++++++++++++++++++
 pkg/cli/mcp_logs_guardrail.go |  2 +-
 3 files changed, 88 insertions(+), 1 deletion(-)

diff --git a/pkg/cli/logs_report.go b/pkg/cli/logs_report.go
index 1c36afca462..7c4dc202e3f 100644
--- a/pkg/cli/logs_report.go
+++ b/pkg/cli/logs_report.go
@@ -80,6 +80,7 @@ type RunData struct {
 	Agent               string               `json:"agent,omitempty" console:"header:Agent,omitempty"`
 	Status              string               `json:"status" console:"header:Status"`
 	Conclusion          string               `json:"conclusion,omitempty" console:"-"`
+	Classification      string               `json:"classification" console:"-"`
 	Duration            string               `json:"duration,omitempty" console:"header:Duration,omitempty"`
 	ActionMinutes       float64              `json:"action_minutes,omitempty" console:"header:Action Minutes,omitempty"`
 	TokenUsage          int                  `json:"token_usage,omitempty" console:"header:Tokens,format:number,omitempty"`
@@ -218,6 +219,7 @@ func buildLogsData(processedRuns []ProcessedRun, outputDir string, continuation
 			Agent:               agentID,
 			Status:              run.Status,
 			Conclusion:          run.Conclusion,
+			Classification:      deriveRunClassification(comparison),
 			TokenUsage:          run.TokenUsage,
 			EffectiveTokens:     run.EffectiveTokens,
 			EstimatedCost:       run.EstimatedCost,
@@ -334,6 +336,30 @@ func buildLogsData(processedRuns []ProcessedRun, outputDir string, continuation
 	}
 }
 
+// deriveRunClassification maps a run's AuditComparisonData to one of four
+// human-readable classification labels:
+//
+//   - "risky"       – comparison detected a risk signal (e.g. posture change, new MCP failure).
+//   - "normal"      – comparison found no risk signals (stable or minor changes).
+//   - "baseline"    – no prior successful run was available to compare against;
+//     this run acts as its own baseline.
+//   - "unclassified" – comparison data is absent or incomplete.
+func deriveRunClassification(comparison *AuditComparisonData) string {
+	if comparison == nil {
+		return "unclassified"
+	}
+	if !comparison.BaselineFound {
+		return "baseline"
+	}
+	if comparison.Classification == nil {
+		return "unclassified"
+	}
+	if comparison.Classification.Label == "risky" {
+		return "risky"
+	}
+	return "normal"
+}
+
 // isValidToolName checks if a tool name appears to be valid
 // Filters out single words, common words, and other garbage that shouldn't be tools
 func isValidToolName(toolName string) bool {
diff --git a/pkg/cli/logs_report_test.go b/pkg/cli/logs_report_test.go
index fdc49e65b6c..fa1fa3d93d6 100644
--- a/pkg/cli/logs_report_test.go
+++ b/pkg/cli/logs_report_test.go
@@ -847,3 +847,64 @@ func TestBuildLogsDataIncludesDateFields(t *testing.T) {
 		t.Errorf("Expected UpdatedAt = %v, got %v", updatedAt, run.UpdatedAt)
 	}
 }
+
+// TestDeriveRunClassification tests the classification mapping helper.
+func TestDeriveRunClassification(t *testing.T) {
+	tests := []struct {
+		name       string
+		comparison *AuditComparisonData
+		want       string
+	}{
+		{
+			name:       "nil comparison returns unclassified",
+			comparison: nil,
+			want:       "unclassified",
+		},
+		{
+			name:       "no baseline found returns baseline",
+			comparison: &AuditComparisonData{BaselineFound: false},
+			want:       "baseline",
+		},
+		{
+			name: "nil classification with baseline returns unclassified",
+			comparison: &AuditComparisonData{
+				BaselineFound:  true,
+				Classification: nil,
+			},
+			want: "unclassified",
+		},
+		{
+			name: "risky label returns risky",
+			comparison: &AuditComparisonData{
+				BaselineFound:  true,
+				Classification: &AuditComparisonClassification{Label: "risky"},
+			},
+			want: "risky",
+		},
+		{
+			name: "stable label returns normal",
+			comparison: &AuditComparisonData{
+				BaselineFound:  true,
+				Classification: &AuditComparisonClassification{Label: "stable"},
+			},
+			want: "normal",
+		},
+		{
+			name: "changed label returns normal",
+			comparison: &AuditComparisonData{
+				BaselineFound:  true,
+				Classification: &AuditComparisonClassification{Label: "changed"},
+			},
+			want: "normal",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := deriveRunClassification(tt.comparison)
+			if got != tt.want {
+				t.Errorf("deriveRunClassification() = %q, want %q", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/pkg/cli/mcp_logs_guardrail.go b/pkg/cli/mcp_logs_guardrail.go
index 54ccf1df96a..51d5fe49421 100644
--- a/pkg/cli/mcp_logs_guardrail.go
+++ b/pkg/cli/mcp_logs_guardrail.go
@@ -105,7 +105,7 @@ func getLogsDataSchema() LogsDataSchema {
 			},
 			"runs": {
 				Type:        "array",
-				Description: "Array of workflow run data (database_id, workflow_name, agent, status, conclusion, duration, token_usage, estimated_cost, turns, error_count, warning_count, missing_tool_count, created_at, url, logs_path, event, branch)",
+				Description: "Array of workflow run data (database_id, workflow_name, agent, status, conclusion, classification, duration, token_usage, estimated_cost, turns, error_count, warning_count, missing_tool_count, created_at, url, logs_path, event, branch). classification is one of: risky, normal, baseline, unclassified.",
 			},
 			"tool_usage": {
 				Type:        "array",
