From 7200791a405f870385f9c8c713b49740d7d15f68 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Oct 2025 03:28:50 +0000 Subject: [PATCH 1/4] Initial plan From 2a743bddbdfdaf796e0a9382618be985656953ff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Oct 2025 03:40:48 +0000 Subject: [PATCH 2/4] Add continuation field to logs JSON output when timeout is reached Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/logs.go | 22 ++++++- pkg/cli/logs_json_test.go | 132 +++++++++++++++++++++++++++++++++++++- pkg/cli/logs_report.go | 18 +++++- pkg/cli/mcp_server.go | 11 +++- 4 files changed, 178 insertions(+), 5 deletions(-) diff --git a/pkg/cli/logs.go b/pkg/cli/logs.go index 7808deeea7..4416996015 100644 --- a/pkg/cli/logs.go +++ b/pkg/cli/logs.go @@ -687,8 +687,28 @@ func DownloadWorkflowLogs(workflowName string, count int, startDate, endDate, ou processedRuns[i].Run.MissingToolCount = len(processedRuns[i].MissingTools) } + // Build continuation data if timeout was reached and there are processed runs + var continuation *ContinuationData + if timeoutReached && len(processedRuns) > 0 { + // Get the oldest run ID from processed runs to use as before_run_id for continuation + oldestRunID := processedRuns[len(processedRuns)-1].Run.DatabaseID + + continuation = &ContinuationData{ + Message: "Timeout reached. Use these parameters to continue fetching more logs.", + WorkflowName: workflowName, + Count: count, + StartDate: startDate, + EndDate: endDate, + Engine: engine, + Branch: branch, + AfterRunID: afterRunID, + BeforeRunID: oldestRunID, // Continue from where we left off + Timeout: timeout, + } + } + // Build structured logs data - logsData := buildLogsData(processedRuns, outputDir) + logsData := buildLogsData(processedRuns, outputDir, continuation) // Render output based on format preference if jsonOutput { diff --git a/pkg/cli/logs_json_test.go b/pkg/cli/logs_json_test.go index 4339b060ae..3a101689de 100644 --- a/pkg/cli/logs_json_test.go +++ b/pkg/cli/logs_json_test.go @@ -70,7 +70,7 @@ func TestBuildLogsData(t *testing.T) { } // Build logs data - logsData := buildLogsData(processedRuns, tmpDir) + logsData := buildLogsData(processedRuns, tmpDir, nil) // Verify summary if logsData.Summary.TotalRuns != 2 { @@ -261,6 +261,136 @@ func TestBuildMissingToolsSummary(t *testing.T) { } } +// TestBuildLogsDataWithContinuation tests continuation field in logs data +func TestBuildLogsDataWithContinuation(t *testing.T) { + tmpDir := t.TempDir() + + // Create sample processed runs + processedRuns := []ProcessedRun{ + { + Run: WorkflowRun{ + DatabaseID: 12345, + Number: 1, + WorkflowName: "Test Workflow", + Status: "completed", + Conclusion: "success", + CreatedAt: time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC), + URL: "https://github.com/test/repo/actions/runs/12345", + LogsPath: filepath.Join(tmpDir, "run-12345"), + }, + }, + { + Run: WorkflowRun{ + DatabaseID: 12344, + Number: 2, + WorkflowName: "Test Workflow", + Status: "completed", + Conclusion: "success", + CreatedAt: time.Date(2024, 1, 2, 12, 0, 0, 0, time.UTC), + URL: "https://github.com/test/repo/actions/runs/12344", + LogsPath: filepath.Join(tmpDir, "run-12344"), + }, + }, + } + + // Create continuation data (simulating timeout scenario) + continuation := &ContinuationData{ + Message: "Timeout reached. Use these parameters to continue fetching more logs.", + WorkflowName: "Test Workflow", + Count: 100, + StartDate: "2024-01-01", + EndDate: "2024-12-31", + Engine: "copilot", + Branch: "main", + AfterRunID: 0, + BeforeRunID: 12344, // Continue from the oldest run + Timeout: 50, + } + + // Build logs data with continuation + logsData := buildLogsData(processedRuns, tmpDir, continuation) + + // Verify continuation field is present + if logsData.Continuation == nil { + t.Fatal("Expected continuation field to be present, got nil") + } + + // Verify continuation data + if logsData.Continuation.Message != "Timeout reached. Use these parameters to continue fetching more logs." { + t.Errorf("Expected continuation message, got '%s'", logsData.Continuation.Message) + } + if logsData.Continuation.WorkflowName != "Test Workflow" { + t.Errorf("Expected WorkflowName 'Test Workflow', got '%s'", logsData.Continuation.WorkflowName) + } + if logsData.Continuation.BeforeRunID != 12344 { + t.Errorf("Expected BeforeRunID 12344, got %d", logsData.Continuation.BeforeRunID) + } + if logsData.Continuation.Count != 100 { + t.Errorf("Expected Count 100, got %d", logsData.Continuation.Count) + } + if logsData.Continuation.Engine != "copilot" { + t.Errorf("Expected Engine 'copilot', got '%s'", logsData.Continuation.Engine) + } + + // Test JSON serialization of continuation + jsonOutput, err := json.MarshalIndent(logsData, "", " ") + if err != nil { + t.Fatalf("Failed to marshal logs data to JSON: %v", err) + } + + // Verify continuation is in JSON + var parsedData LogsData + if err := json.Unmarshal(jsonOutput, &parsedData); err != nil { + t.Fatalf("Failed to unmarshal logs data from JSON: %v", err) + } + + if parsedData.Continuation == nil { + t.Fatal("Expected continuation field in unmarshaled JSON, got nil") + } + if parsedData.Continuation.BeforeRunID != 12344 { + t.Errorf("Expected BeforeRunID 12344 in unmarshaled JSON, got %d", parsedData.Continuation.BeforeRunID) + } +} + +// TestBuildLogsDataWithoutContinuation tests that continuation is omitted when nil +func TestBuildLogsDataWithoutContinuation(t *testing.T) { + tmpDir := t.TempDir() + + processedRuns := []ProcessedRun{ + { + Run: WorkflowRun{ + DatabaseID: 12345, + WorkflowName: "Test Workflow", + LogsPath: filepath.Join(tmpDir, "run-12345"), + }, + }, + } + + // Build logs data without continuation + logsData := buildLogsData(processedRuns, tmpDir, nil) + + // Verify continuation field is nil + if logsData.Continuation != nil { + t.Errorf("Expected continuation field to be nil, got %+v", logsData.Continuation) + } + + // Test JSON serialization + jsonOutput, err := json.Marshal(logsData) + if err != nil { + t.Fatalf("Failed to marshal logs data to JSON: %v", err) + } + + // Verify continuation is omitted from JSON (due to omitempty tag) + var parsedMap map[string]any + if err := json.Unmarshal(jsonOutput, &parsedMap); err != nil { + t.Fatalf("Failed to unmarshal logs data to map: %v", err) + } + + if _, exists := parsedMap["continuation"]; exists { + t.Error("Expected continuation field to be omitted from JSON when nil") + } +} + // TestBuildMCPFailuresSummary tests MCP failures aggregation func TestBuildMCPFailuresSummary(t *testing.T) { processedRuns := []ProcessedRun{ diff --git a/pkg/cli/logs_report.go b/pkg/cli/logs_report.go index aeeb65b47d..e6f1db1a0b 100644 --- a/pkg/cli/logs_report.go +++ b/pkg/cli/logs_report.go @@ -21,9 +21,24 @@ type LogsData struct { MissingTools []MissingToolSummary `json:"missing_tools,omitempty" console:"title:🛠️ Missing Tools Summary,omitempty"` MCPFailures []MCPFailureSummary `json:"mcp_failures,omitempty" console:"title:⚠️ MCP Server Failures,omitempty"` AccessLog *AccessLogSummary `json:"access_log,omitempty" console:"title:Access Log Analysis,omitempty"` + Continuation *ContinuationData `json:"continuation,omitempty" console:"-"` LogsLocation string `json:"logs_location" console:"-"` } +// ContinuationData provides parameters to continue querying when timeout is reached +type ContinuationData struct { + Message string `json:"message"` + WorkflowName string `json:"workflow_name,omitempty"` + Count int `json:"count,omitempty"` + StartDate string `json:"start_date,omitempty"` + EndDate string `json:"end_date,omitempty"` + Engine string `json:"engine,omitempty"` + Branch string `json:"branch,omitempty"` + AfterRunID int64 `json:"after_run_id,omitempty"` + BeforeRunID int64 `json:"before_run_id,omitempty"` + Timeout int `json:"timeout,omitempty"` +} + // LogsSummary contains aggregate metrics across all runs type LogsSummary struct { TotalRuns int `json:"total_runs" console:"header:Total Runs"` @@ -78,7 +93,7 @@ type AccessLogSummary struct { } // buildLogsData creates structured logs data from processed runs -func buildLogsData(processedRuns []ProcessedRun, outputDir string) LogsData { +func buildLogsData(processedRuns []ProcessedRun, outputDir string, continuation *ContinuationData) LogsData { // Build summary var totalDuration time.Duration var totalTokens int @@ -167,6 +182,7 @@ func buildLogsData(processedRuns []ProcessedRun, outputDir string) LogsData { MissingTools: missingTools, MCPFailures: mcpFailures, AccessLog: accessLog, + Continuation: continuation, LogsLocation: absOutputDir, } } diff --git a/pkg/cli/mcp_server.go b/pkg/cli/mcp_server.go index 3bcd9824ef..ebe6228be2 100644 --- a/pkg/cli/mcp_server.go +++ b/pkg/cli/mcp_server.go @@ -235,8 +235,15 @@ Note: Output can be filtered using the jq parameter.`, JqFilter string `json:"jq,omitempty" jsonschema:"Optional jq filter to apply to JSON output"` } mcp.AddTool(server, &mcp.Tool{ - Name: "logs", - Description: "Download and analyze workflow logs", + Name: "logs", + Description: `Download and analyze workflow logs. + +Returns JSON with workflow run data and metrics. If the command times out before fetching all available logs, +a "continuation" field will be present in the response with updated parameters to continue fetching more data. +Check for the presence of the continuation field to determine if there are more logs available. + +The continuation field includes all necessary parameters (before_run_id, etc.) to resume fetching from where +the previous request stopped due to timeout.`, }, func(ctx context.Context, req *mcp.CallToolRequest, args logsArgs) (*mcp.CallToolResult, any, error) { // Build command arguments // Force output directory to /tmp/gh-aw/aw-mcp/logs for MCP server From bb76805bbcf37d50688786f5d5e63f147b818291 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Oct 2025 03:43:02 +0000 Subject: [PATCH 3/4] Format code with go fmt Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/logs_report.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/cli/logs_report.go b/pkg/cli/logs_report.go index e6f1db1a0b..5c43c281d6 100644 --- a/pkg/cli/logs_report.go +++ b/pkg/cli/logs_report.go @@ -27,16 +27,16 @@ type LogsData struct { // ContinuationData provides parameters to continue querying when timeout is reached type ContinuationData struct { - Message string `json:"message"` - WorkflowName string `json:"workflow_name,omitempty"` - Count int `json:"count,omitempty"` - StartDate string `json:"start_date,omitempty"` - EndDate string `json:"end_date,omitempty"` - Engine string `json:"engine,omitempty"` - Branch string `json:"branch,omitempty"` - AfterRunID int64 `json:"after_run_id,omitempty"` - BeforeRunID int64 `json:"before_run_id,omitempty"` - Timeout int `json:"timeout,omitempty"` + Message string `json:"message"` + WorkflowName string `json:"workflow_name,omitempty"` + Count int `json:"count,omitempty"` + StartDate string `json:"start_date,omitempty"` + EndDate string `json:"end_date,omitempty"` + Engine string `json:"engine,omitempty"` + Branch string `json:"branch,omitempty"` + AfterRunID int64 `json:"after_run_id,omitempty"` + BeforeRunID int64 `json:"before_run_id,omitempty"` + Timeout int `json:"timeout,omitempty"` } // LogsSummary contains aggregate metrics across all runs From b1594096318875468ff7e928303e98e4c90dfc33 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 21 Oct 2025 04:34:35 +0000 Subject: [PATCH 4/4] Add changeset for continuation field feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .changeset/patch-add-continuation-field-logs.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/patch-add-continuation-field-logs.md diff --git a/.changeset/patch-add-continuation-field-logs.md b/.changeset/patch-add-continuation-field-logs.md new file mode 100644 index 0000000000..7a0746dbae --- /dev/null +++ b/.changeset/patch-add-continuation-field-logs.md @@ -0,0 +1,5 @@ +--- +"gh-aw": patch +--- + +Add continuation field to logs JSON output when timeout is reached