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",