-
Notifications
You must be signed in to change notification settings - Fork 527
Warn callers when logs MCP tool returns stale data with no date range specified
#53719
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
2908858
f5a9881
6aa603b
2e5c78f
e9d6d4f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -368,3 +368,43 @@ func TestCollectProcessedWorkflowRunsAccumulatesBatches(t *testing.T) { | |
| assert.Equal(t, int64(1), runs[0].Run.DatabaseID) | ||
| assert.Equal(t, int64(3), runs[2].Run.DatabaseID) | ||
| } | ||
|
|
||
| // TestStaleLogsWarning verifies that a warning is only emitted when no explicit | ||
| // start_date/end_date was requested and the newest run in the result set is older | ||
| // than the staleness threshold. This guards against the "logs" tool silently | ||
| // serving stale data without any indication when called with only a count. | ||
| func TestStaleLogsWarning(t *testing.T) { | ||
| t.Run("no warning when start date explicitly provided", func(t *testing.T) { | ||
| runs := []ProcessedRun{{Run: WorkflowRun{CreatedAt: time.Now().Add(-30 * 24 * time.Hour)}}} | ||
| assert.Empty(t, staleLogsWarning(runs, "-1d", "")) | ||
| }) | ||
|
|
||
| t.Run("no warning when end date explicitly provided", func(t *testing.T) { | ||
| runs := []ProcessedRun{{Run: WorkflowRun{CreatedAt: time.Now().Add(-30 * 24 * time.Hour)}}} | ||
| assert.Empty(t, staleLogsWarning(runs, "", "2024-01-01")) | ||
| }) | ||
|
|
||
| t.Run("no warning when no runs", func(t *testing.T) { | ||
| assert.Empty(t, staleLogsWarning(nil, "", "")) | ||
| }) | ||
|
|
||
| t.Run("no warning when newest run is recent", func(t *testing.T) { | ||
| runs := []ProcessedRun{ | ||
| {Run: WorkflowRun{CreatedAt: time.Now().Add(-1 * time.Hour)}}, | ||
| {Run: WorkflowRun{CreatedAt: time.Now().Add(-40 * 24 * time.Hour)}}, | ||
| } | ||
| assert.Empty(t, staleLogsWarning(runs, "", "")) | ||
| }) | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/diagnosing-bugs] The 💡 Suggested renamenewest := time.Now().Add(-11 * 24 * time.Hour)
runs := []ProcessedRun{
{Run: WorkflowRun{CreatedAt: newest}},
{Run: WorkflowRun{CreatedAt: newest.Add(-time.Hour)}},
}While here, assert the warning contains "11 day" to lock in the human-readable format. @copilot please address this. |
||
| t.Run("warns when no dates given and newest run is old", func(t *testing.T) { | ||
| oldest := time.Now().Add(-11 * 24 * time.Hour) | ||
| runs := []ProcessedRun{ | ||
| {Run: WorkflowRun{CreatedAt: oldest}}, | ||
| {Run: WorkflowRun{CreatedAt: oldest.Add(-time.Hour)}}, | ||
| } | ||
| warning := staleLogsWarning(runs, "", "") | ||
| require.NotEmpty(t, warning) | ||
| assert.Contains(t, warning, "No start_date/end_date was specified") | ||
| assert.Contains(t, warning, "start_date") | ||
| }) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,6 +51,18 @@ func extractLogsContinuation(outputStr string) *ContinuationData { | |
| return parsed.Continuation | ||
| } | ||
|
|
||
| // extractLogsMessage returns the top-level "message" field embedded in the logs | ||
| // JSON output (e.g. a stale-data warning), or "" when absent or unparseable. | ||
| func extractLogsMessage(outputStr string) string { | ||
| var parsed struct { | ||
| Message string `json:"message"` | ||
| } | ||
| if err := json.Unmarshal([]byte(outputStr), &parsed); err != nil { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/diagnosing-bugs] 💡 Suggested fixLog the parse error at debug level so it's visible under func extractLogsMessage(outputStr string) string {
var parsed struct {
Message string `json:"message"`
}
if err := json.Unmarshal([]byte(outputStr), &parsed); err != nil {
mcpLogsGuardrailLog.Printf("extractLogsMessage: failed to parse output JSON: %v", err)
return ""
}
return parsed.Message
}@copilot please address this. |
||
| return "" | ||
| } | ||
| return parsed.Message | ||
| } | ||
|
|
||
| // buildLogsFileResponse writes the logs JSON output to a content-addressed cache | ||
| // file and returns a JSON response containing the file path. | ||
| // The file is named by the SHA256 hash of its content so that identical results | ||
|
|
@@ -142,6 +154,16 @@ func buildLogsFileResponse(outputStr string) string { | |
| response.Continuation = continuation | ||
| response.Message = fmt.Sprintf("PARTIAL RESULTS: the download stopped before all matching runs were collected. %s Partial logs data has been written to '%s'. Use the file_path to read the collected data and the continuation parameters to fetch the remaining logs.", continuation.Message, filePath) | ||
| } | ||
| // Surface any top-level warning (e.g. stale-data warning when no date range was | ||
| // requested) directly in the tool response so callers see it without having to | ||
| // open the file. | ||
| if warning := extractLogsMessage(outputStr); warning != "" { | ||
|
|
||
| if response.Message == "" { | ||
| response.Message = "WARNING: " + warning | ||
| } else { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/codebase-design] 💡 Suggested refactorCollect all message fragments into a var msgs []string
if continuation != nil {
msgs = append(msgs, fmt.Sprintf("PARTIAL RESULTS: ... '%s'.", filePath))
}
if warning := extractLogsMessage(outputStr); warning != "" {
msgs = append(msgs, "WARNING: "+warning)
}
if len(msgs) > 0 {
response.Message = strings.Join(msgs, " ")
}This also removes the duplicated @copilot please address this. |
||
| response.Message = fmt.Sprintf("%s WARNING: %s", response.Message, warning) | ||
| } | ||
| } | ||
|
|
||
| responseJSON, err := json.MarshalIndent(response, "", " ") | ||
| if err != nil { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -207,3 +207,24 @@ func TestBuildLogsFileResponse_CompleteResultsNotPartial(t *testing.T) { | |
| // Cleanup | ||
| _ = os.Remove(response.FilePath) | ||
| } | ||
|
|
||
| func TestBuildLogsFileResponse_SurfacesStaleDataWarning(t *testing.T) { | ||
| output := `{"summary":{"total_runs":1},"runs":[],"message":"No start_date/end_date was specified, and the most recent run in this result is 264h0m0s old."}` | ||
|
|
||
| result := buildLogsFileResponse(output) | ||
|
|
||
| var response MCPLogsGuardrailResponse | ||
| if err := json.Unmarshal([]byte(result), &response); err != nil { | ||
| t.Fatalf("Response should be valid JSON: %v", err) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/tdd] 💡 Suggested fixSwitch to require.NoError(t, json.Unmarshal([]byte(result), &response))
assert.Contains(t, response.Message, "WARNING:")
assert.Contains(t, response.Message, "No start_date/end_date was specified")@copilot please address this. |
||
| } | ||
|
|
||
| if !strings.Contains(response.Message, "WARNING:") { | ||
| t.Errorf("Message should surface the embedded warning, got %q", response.Message) | ||
| } | ||
| if !strings.Contains(response.Message, "No start_date/end_date was specified") { | ||
| t.Errorf("Message should include the stale-data warning text, got %q", response.Message) | ||
| } | ||
|
|
||
| // Cleanup | ||
| _ = os.Remove(response.FilePath) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[/diagnosing-bugs]
age.Round(time.Hour)formats as"264h0m0s"— callers see a raw Go duration string rather than "11 days old" as shown in the PR description. A human-readable form is far more actionable for agents and users.💡 Suggested fix
Add a small helper and use it in the Sprintf:
Also add a test that asserts the stale-data warning for 11-day-old data contains
"11 day"to prevent format regressions.@copilot please address this.