Skip to content

Warn callers when logs MCP tool returns stale data with no date range specified - #53719

Merged
pelikhan merged 5 commits into
mainfrom
copilot/deep-report-fix-stale-data
Aug 18, 2026
Merged

Warn callers when logs MCP tool returns stale data with no date range specified#53719
pelikhan merged 5 commits into
mainfrom
copilot/deep-report-fix-stale-data

Conversation

Copilot AI commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

The logs MCP tool's default code path (no start_date/end_date) can silently settle on an old pagination window in high-activity repos and return stale runs with no indication anything is wrong—leading deep-report and audit workflows to draw false conclusions about current fleet health.

Changes

  • Staleness detection (pkg/cli/logs_orchestrator.go): added staleLogsWarning(), which checks whether no date range was requested and the newest run in the result set is older than 48 hours; if so, produces a warning advising the caller to retry with an explicit start_date.
  • Threading date params through rendering: added startDate/endDate to renderLogsOutputOptions so renderLogsOutput can invoke the staleness check and fold the warning into the output's message field alongside existing hints.
  • Surfacing in the MCP response (pkg/cli/mcp_logs_guardrail.go): buildLogsFileResponse now extracts this warning and appends it directly to the tool's top-level response text, so it's visible immediately without opening the cached output file.
  • Tests: unit tests covering staleLogsWarning behavior (explicit dates, recent data, no runs, stale data) and confirming the guardrail response surfaces the warning.

Example: calling the tool as agenticworkflows logs --count 30 when the newest returned run is 11 days old now returns a response message like:

WARNING: no date range was specified and the most recent run returned is 11 days old; results may be stale. Retry with an explicit start_date (e.g. "-1d") to get current data.

Copilot AI and others added 2 commits August 18, 2026 15:18
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix stale data issue in agenticworkflows logs Warn callers when logs MCP tool returns stale data with no date range specified Aug 18, 2026
Copilot AI requested a review from pelikhan August 18, 2026 15:20
@pelikhan
pelikhan marked this pull request as ready for review August 18, 2026 15:21
Copilot AI balanced review requested due to automatic review settings August 18, 2026 15:21
@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

Test Quality Sentinel skipped because pre-fetch PR data was unavailable: unable to fetch test file diff

🧪 Test quality analysis by Test Quality Sentinel

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

🔎 Code quality review by PR Code Quality Reviewer

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Ponytail Reviewer completed successfully!

Lean already. Ship.

Generated by Ponytail Reviewer for #53719

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions

Copy link
Copy Markdown
Contributor

Comment Memory

reviewed_at: 2026-08-18T00:00:00Z
review_event: COMMENT
top_themes:
  - no actionable blocking issues in stale logs warning flow
  - sub-agent unavailable; review completed with primary analysis only
files_reviewed:
  - pkg/cli/logs_orchestrator.go
  - pkg/cli/logs_orchestrator_render.go
  - pkg/cli/logs_orchestrator_types.go
  - pkg/cli/logs_orchestrator_unit_test.go
  - pkg/cli/mcp_logs_guardrail.go
  - pkg/cli/mcp_logs_guardrail_test.go
comment_count: 0

Note

This comment is managed by comment memory.

It stores persistent context for this thread in the code block at the top of this comment.
Edit only the text inside the backtick fences; workflow metadata and the footer are regenerated automatically.

Learn more about comment memory

🔎 Code quality review by PR Code Quality Reviewer · gpt54 · 13.2 AIC · ⌖ 8.62 AIC · ⊞ 7K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verdict: comment

I don't see a changed-line bug here that should block merge.

Review notes
  • The new stale-data warning is correctly gated off when start_date or end_date is explicitly supplied, which avoids false positives for intentionally bounded queries.
  • The warning is threaded into both rendered logs output and the MCP guardrail response, so callers can see it without opening the cached file.
  • I did not find a correctness regression in the message-merging logic or the added tests.
  • The requested grumpy-coder sub-agent was unavailable in this environment, so this review relies on direct analysis of the diff.

🔎 Code quality review by PR Code Quality Reviewer · gpt54 · 13.2 AIC · ⌖ 8.62 AIC · ⊞ 7K
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review: Warn callers when logs MCP tool returns stale data

The implementation is clean and well-tested. One non-blocking issue to consider:

Hint messages mixed with WARNING prefix

In renderLogsOutput, both the stale-data warning and the usage-only artifact hint are joined into logsData.Message with a single space. Then in buildLogsFileResponse, extractLogsMessage pulls the whole combined string and unconditionally prefixes it with "WARNING: ".

This means a response that includes both messages would render as:

WARNING: No start_date/end_date was specified ... When only the usage artifact was downloaded, add a hint...

The usage-only hint text gets a misleading WARNING: prefix. Consider keeping the stale-data warning and informational hints in separate fields, or only prepending WARNING: when the message originated from the staleness check rather than for the whole combined string.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 16.9 AIC · ⌖ 8.81 AIC · ⊞ 5.7K

@github-actions

Copy link
Copy Markdown
Contributor

PR #53719 Review Summary

The staleness-detection approach is solid and well-structured. Five issues are flagged:

  1. Duration format (logs_orchestrator.go:83) — age.Round(time.Hour) emits "264h0m0s" not "11 days old"; needs a human-readable helper.
  2. Test variable name (logs_orchestrator_unit_test.go:398) — oldest is actually the newest timestamp; rename for clarity.
  3. Message assembly pattern (mcp_logs_guardrail.go:163) — two inconsistent composition styles; unify using []string + strings.Join as in renderLogsOutput.
  4. Silent JSON parse failure (mcp_logs_guardrail.go:60) — extractLogsMessage drops parse errors without logging; add a debug log.
  5. Test assertion style (mcp_logs_guardrail_test.go:218) — new test uses t.Errorf while the rest of the file uses testify; align to assert/require.

@copilot please address the review comments above.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 42.7 AIC · ⌖ 11.7 AIC · ⊞ 7.8K ·
Comment /matt to run again

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds stale-result warnings for date-unbounded workflow-log queries and surfaces them through MCP responses.

Changes:

  • Detects results whose newest run exceeds the 48-hour threshold.
  • Threads date filters through rendering and combines output hints.
  • Adds unit coverage for detection and MCP response surfacing.
Show a summary per file
File Description
pkg/cli/logs_orchestrator.go Implements staleness detection.
pkg/cli/logs_orchestrator_types.go Adds rendering date options.
pkg/cli/logs_orchestrator_render.go Adds warnings to rendered messages.
pkg/cli/mcp_logs_guardrail.go Surfaces messages in MCP responses.
pkg/cli/logs_orchestrator_unit_test.go Tests staleness conditions.
pkg/cli/mcp_logs_guardrail_test.go Tests MCP warning surfacing.

Review details

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 6/6 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment thread pkg/cli/logs_orchestrator_render.go Outdated
// result is unexpectedly old, warn the caller so stale data is never served
// silently (see issue: logs MCP tool returns stale data without date params).
var hints []string
if warning := staleLogsWarning(processedRuns, opts.startDate, opts.endDate); warning != "" {
Comment thread pkg/cli/mcp_logs_guardrail.go Outdated
// 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 != "" {

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Skills-Based Review 🧠

Applied /diagnosing-bugs, /tdd, and /codebase-design — requesting changes on a few targeted issues before merging.

📋 Key Themes & Highlights

Key Themes

  • Duration formatting: age.Round(time.Hour) produces "264h0m0s" not "11 days old" — the warning message will confuse callers and agents.
  • Test variable naming: oldest in the stale-warning test actually holds the newest timestamp; a confusing mismatch.
  • Message assembly pattern: two different composition styles (fmt.Sprintf vs if/else) exist side-by-side in buildLogsFileResponse; the hints []string + strings.Join pattern used in renderLogsOutput should be applied here too.
  • Silent failure in extractLogsMessage: parse errors are swallowed with no debug log, making issues hard to diagnose.
  • Test assertion style: the new guardrail test uses t.Errorf where the rest of the file uses testify assertions.

Positive Highlights

  • ✅ Clean separation of staleness detection into its own pure function — easy to test in isolation.
  • ✅ The staleness threshold constant is well-documented with the rationale comment.
  • ✅ Warning is threaded all the way to the MCP tool response so callers see it without opening the file.
  • ✅ Good test coverage: the four staleLogsWarning sub-tests cover the key decision tree branches.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 42.7 AIC · ⌖ 11.7 AIC · ⊞ 7.8K
Comment /matt to run again

return ""
}
return fmt.Sprintf(
"No start_date/end_date was specified, and the most recent run in this result is %s old (created %s). "+

Copy link
Copy Markdown
Contributor

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:

func humanizeDuration(d time.Duration) string {
    days := int(d.Hours()) / 24
    if days >= 1 {
        return fmt.Sprintf("%d day(s)", days)
    }
    return fmt.Sprintf("%d hour(s)", int(d.Hours()))
}

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.

}
assert.Empty(t, staleLogsWarning(runs, "", ""))
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/diagnosing-bugs] The staleLogsWarning in the test for "warns when no dates given and newest run is old" uses a variable named oldest but assigns it the newest timestamp (the other run is oldest.Add(-time.Hour)). The naming mismatch makes the test harder to follow and could mask a logic inversion.

💡 Suggested rename
newest := 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.

Comment thread pkg/cli/mcp_logs_guardrail.go Outdated
if warning := extractLogsMessage(outputStr); warning != "" {
if response.Message == "" {
response.Message = "WARNING: " + warning
} else {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/codebase-design] buildLogsFileResponse now contains two separate message-assembly patterns: one for the continuation case (inline fmt.Sprintf) and one for the stale-data warning (if/else append). This makes it hard to see the final message shape at a glance and will grow messier as more warning types are added.

💡 Suggested refactor

Collect all message fragments into a []string and strings.Join them at the end — the same pattern used in renderLogsOutput:

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 "WARNING: " prefix logic.

@copilot please address this.


var response MCPLogsGuardrailResponse
if err := json.Unmarshal([]byte(result), &response); err != nil {
t.Fatalf("Response should be valid JSON: %v", err)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] TestBuildLogsFileResponse_SurfacesStaleDataWarning uses t.Errorf for assertions but the test above it (TestBuildLogsFileResponse_CompleteResultsNotPartial) uses assert/require helpers. Mixing styles makes the test suite harder to scan. The t.Errorf path also continues running after the first failure, which can produce misleading secondary failures.

💡 Suggested fix

Switch to require/assert from the existing testify import:

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.

var parsed struct {
Message string `json:"message"`
}
if err := json.Unmarshal([]byte(outputStr), &parsed); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/diagnosing-bugs] extractLogsMessage silently returns "" on JSON parse error, meaning a malformed outputStr will cause the stale-data warning to be silently dropped rather than surfaced. This makes the guardrail harder to debug in production.

💡 Suggested fix

Log the parse error at debug level so it's visible under DEBUG=cli:*:

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.

@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (144 new lines in pkg/) but does not have a linked Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/53719-warn-on-stale-logs-without-date-range.md — review and complete it before merging.

🔒 This PR cannot merge until an ADR is linked in the PR body.

📋 What to do next
  1. Review the draft ADR committed to your branch at docs/adr/53719-warn-on-stale-logs-without-date-range.md — it was generated from the PR diff
  2. Complete the missing sections — add context the AI couldn't infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-53719: Warn Callers When logs MCP Tool Returns Stale Data Without a Date Range

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

❓ Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 0042-use-postgresql.md for PR #42).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · sonnet46 · 77.8 AIC · ⌖ 22.1 AIC · ⊞ 4.4K ·
Comment /review to run again

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

…d, humanized age

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@pelikhan
pelikhan merged commit 76155fd into main Aug 18, 2026
30 checks passed
@pelikhan
pelikhan deleted the copilot/deep-report-fix-stale-data branch August 18, 2026 17:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[deep-report] logs MCP tool returns stale ~11-day-old data without explicit date-range params

3 participants