Repository navigation
Conversation
…ows, not matched events _hit_output_limit decided that a chunk had hit the 10,000-record output cap by reading statistics.recordsMatched. recordsMatched is the number of log events that matched the query string, which is an input count. It says nothing about how many rows came back. Any aggregation query over a busy log group trips it. A "filter ... | stats count(*) by bin(1h)" that matches 50,000 events and returns 3 rows was treated as truncated, so the chunk split its time range four times: 31 StartQuery calls instead of 1, and 48 merged rows instead of 3, with each bin repeated once per sub-range at a partial count. The row count is the only signal that the output was cut off, so use it. The existing test that drives splitting from 10,000 returned rows is unchanged and still passes.
venkat-uk
requested review from
a team,
agiuliano,
gcacace,
goranmod and
shri-tambe
as code owners
August 22, 2026 12:55
venkat-uk
force-pushed
the
fix/insights-batch-split-on-returned-rows
branch
from
August 22, 2026 13:16
5c314b3 to
96b54ab
Compare
…plit fix Signed-off-by: Venkat Ramachandran <268347452+venkat-uk@users.noreply.github.com>
Author
|
This is merged up to date with main and the cloudwatch-mcp-server tests pass. I've also added a CHANGELOG entry under Unreleased. @gcacace @shri-tambe @agiuliano @goranmod, could one of you review when you get a chance? |
Contributor
|
This pull request is now marked as stale because it hasn't seen activity for a while. Add a comment or it will be closed soon. If you wish to exclude this issue from being marked as stale, add the "backlog" label. |
shri-tambe
approved these changes
Nov 2, 2026
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4524 +/- ##
==========================================
- Coverage 93.79% 93.79% -0.01%
==========================================
Files 1080 1080
Lines 94701 94697 -4
Branches 15434 15433 -1
==========================================
- Hits 88828 88823 -5
Misses 3500 3500
- Partials 2373 2374 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
shri-tambe
enabled auto-merge
November 2, 2026 03:25
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes
Summary
Changes
_hit_output_limit()insrc/cloudwatch-mcp-server/awslabs/cloudwatch_mcp_server/cloudwatch_logs/cwl_insights_batch.pydecided that a query chunk had hit the 10,000-record output cap by readingstatistics.recordsMatched. It now reads the number of returned rows.recordsMatchedis documented as "The number of log events that matched the query string". That is an input count. It cannot tell you whether the output was cut off. Astats ... by bin(1h)query can match millions of events and return ten rows.The consequence is in
_run_chunk(). When_hit_output_limit()returns True the chunk splits its time range in half and re-runs both halves, up toMAX_RECHUNK_DEPTH(4) levels. So any query matching 10,000 or more events was split, whether or not its output was ever truncated.The returned row count is the only signal that the output was cut off, and it is the number the 10,000 cap applies to.
GetQueryResultsreturns at most 10,000 rows per call, and this tool does not page past that, solen(results) >= MAX_OUTPUT_RECORDSis exactly the truncation test.User experience
Before, a three-hour aggregation query over one log group that matched 50,000 events and returned 3 rows:
Each bin came back 16 times, once per leaf sub-range, each holding a partial count. An agent reading that sees 16 buckets where there is one, and cannot add them up because the merge carries no sub-range boundaries.
After, the same call:
Three effects for users:
StartQuerycalls become 1. Insights is billed on data scanned, and each re-run rescans its slice of the range, so the old path multiplied both cost and query time. It also consumed the per-region concurrency budget that the tool sets aside at 25% of the account limit.limitis honoured.limitis applied perStartQuery, so a split chunk returned up to 16 times the number of rows the caller asked for. The tool's own parameter help warns against overwhelming the agent context window.Nothing changes for a query that genuinely fills its output. The existing test
test_rechunk_on_output_limit, which drives splitting from 10,000 returned rows, is untouched and still passes. Time-range splitting onTimeoutandPollingTimeoutis untouched.How this was tested
Run from
src/cloudwatch-mcp-server:All tests use
unittest.mock. No AWS calls.Four tests added or changed:
test_no_rechunk_when_many_events_matched_but_few_rows_returnedis the regression test. It asserts oneStartQuerycall,re_chunked == 0, and three distinctbinvalues in the merged output.test_high_records_matched_with_few_rowspins the unit behaviour: 50,000 matched, 3 rows, not a hit.test_at_limit_via_row_count_with_statistics_presentpins that the row count decides even when statistics are present.test_at_limit_via_results_fallbackis renamed totest_at_limit_via_row_count, since it is no longer a fallback. Its assertion is unchanged.One existing test was removed:
test_at_limit_via_statsasserted_hit_output_limit({'statistics': {'recordsMatched': 10_000}, 'results': []}) is True. Zero rows returned and "hit the output cap" cannot both be true, so it encoded the defect.Tradeoffs
limitbelow 10,000 and the query fills it, the chunk no longer splits. That is deliberate: the caller bounded the output on purpose, and splitting returned more rows than they asked for. If splitting on an explicitlimitis wanted, it should be a separate opt-in rather than an accident of the wrong statistic._run_chunkmerges sub-range results by concatenation, so even a legitimate split of an aggregation query produces one partial row per sub-range. That is a pre-existing limitation of splitting aggregations at all, and worth a separate look. This change makes it much rarer by removing the case where splitting was never needed.Checklist
If your change doesn't seem to apply, please leave them unchecked.
Is this a breaking change? (N)
RFC issue number:
Checklist:
Acknowledgment
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of the project license.