Summary
parseRecommendations in providers/aws/recommendations/parser_ri.go:30 logs a
warning to stderr and continues when a single recommendation detail fails to
parse. The failed detail is dropped from the returned slice and no signal of
the drop reaches the caller. That is defensible graceful degradation for a
batch job, but it became load-bearing when cudly_search_recommendations
started promising a complete offer menu, so it should now fail loud (at least
for the MCP caller).
// providers/aws/recommendations/parser_ri.go
rec, err := c.parseRecommendationDetail(ctx, &details, params)
if err != nil {
log.Printf("Warning: Failed to parse recommendation detail %d: %v", i, err)
continue
}
Why the search fan-out made this load-bearing
mcp/tools/search_recommendations.go fans an AWS reservation search out over
every (term, payment option) combination when the caller omits term_years
and/or payment_option, because GetReservationPurchaseRecommendation returns
only one cell per request. fetchSearchCombos deliberately fails the WHOLE
search when any one combo's API call errors, and its own comment says why:
silently returning 5 of 6 offers is indistinguishable from "these are all
your options" and recreates the exact defect this fan-out exists to fix.
That guarantee only holds at the combo boundary. Inside a combo, the shared
parser silently drops individual offers, so the same defect the fan-out was
built to remove is reproduced one level down, where nothing checks for it.
Failure scenario
- A model calls
cudly_search_recommendations with provider="aws",
service="ec2" and no term_years / payment_option, so the tool issues
all 6 combos.
- For the 3yr/all-upfront combo, Cost Explorer returns several recommendation
details. The best one carries a quantity or cost field the parser cannot
handle (unparseable numeric string, a field AWS newly leaves unset, an
instance type the sizing path rejects).
parseRecommendations logs to stderr and continues. The MCP client never
sees stderr; the JSON-RPC response carries the remaining offers only.
- The tool returns
success with a menu the model presents as complete. The
model recommends the best SURVIVING offer, and the human buys it. The
dropped offer may have been the cheapest one.
This is silent under-reporting on a purchase-decision path: the caller cannot
distinguish "AWS had nothing better" from "we could not parse the better one".
The same drop also reaches the scheduler's discovery sweep and the CLI's CSV
output, which is why the fix is not a one-line change here.
parseRecommendations is shared. internal/scheduler's discovery sweep calls
into the same path and legitimately prefers partial progress over none in a
batch job, so flipping continue to a hard error changes behaviour for a
consumer that wants the current semantics. The fix needs a per-caller policy
(strict for the interactive/purchase-decision path, tolerant for the batch
sweep) or a returned skipped-count the MCP tool can refuse on, plus tests on
both sides. That is outside the scope of PR LeanerCloud/cloud-commitments-cli#1495, which only touches
mcp/tools.
Suggested direction (not prescriptive)
- Have
parseRecommendations report what it dropped (count plus per-detail
reason) instead of only logging it.
- Let the caller decide:
cudly_search_recommendations refuses the search with
an error naming the unparseable detail, matching fetchSearchCombos'
existing all-or-nothing contract; the scheduler keeps tolerating and records
the skipped count.
- Regression test with a fixture whose detail genuinely fails
parseRecommendationDetail, asserting the MCP search errors rather than
returning a short menu, and that the scheduler path still returns the
survivors.
References
Summary
parseRecommendationsinproviders/aws/recommendations/parser_ri.go:30logs awarning to stderr and
continues when a single recommendation detail fails toparse. The failed detail is dropped from the returned slice and no signal of
the drop reaches the caller. That is defensible graceful degradation for a
batch job, but it became load-bearing when
cudly_search_recommendationsstarted promising a complete offer menu, so it should now fail loud (at least
for the MCP caller).
Why the search fan-out made this load-bearing
mcp/tools/search_recommendations.gofans an AWS reservation search out overevery (term, payment option) combination when the caller omits
term_yearsand/or
payment_option, becauseGetReservationPurchaseRecommendationreturnsonly one cell per request.
fetchSearchCombosdeliberately fails the WHOLEsearch when any one combo's API call errors, and its own comment says why:
That guarantee only holds at the combo boundary. Inside a combo, the shared
parser silently drops individual offers, so the same defect the fan-out was
built to remove is reproduced one level down, where nothing checks for it.
Failure scenario
cudly_search_recommendationswithprovider="aws",service="ec2"and noterm_years/payment_option, so the tool issuesall 6 combos.
details. The best one carries a quantity or cost field the parser cannot
handle (unparseable numeric string, a field AWS newly leaves unset, an
instance type the sizing path rejects).
parseRecommendationslogs to stderr andcontinues. The MCP client neversees stderr; the JSON-RPC response carries the remaining offers only.
successwith a menu the model presents as complete. Themodel recommends the best SURVIVING offer, and the human buys it. The
dropped offer may have been the cheapest one.
This is silent under-reporting on a purchase-decision path: the caller cannot
distinguish "AWS had nothing better" from "we could not parse the better one".
The same drop also reaches the scheduler's discovery sweep and the CLI's CSV
output, which is why the fix is not a one-line change here.
Why this is filed rather than fixed in PR LeanerCloud/cloud-commitments-cli#1495
parseRecommendationsis shared.internal/scheduler's discovery sweep callsinto the same path and legitimately prefers partial progress over none in a
batch job, so flipping
continueto a hard error changes behaviour for aconsumer that wants the current semantics. The fix needs a per-caller policy
(strict for the interactive/purchase-decision path, tolerant for the batch
sweep) or a returned skipped-count the MCP tool can refuse on, plus tests on
both sides. That is outside the scope of PR LeanerCloud/cloud-commitments-cli#1495, which only touches
mcp/tools.Suggested direction (not prescriptive)
parseRecommendationsreport what it dropped (count plus per-detailreason) instead of only logging it.
cudly_search_recommendationsrefuses the search withan error naming the unparseable detail, matching
fetchSearchCombos'existing all-or-nothing contract; the scheduler keeps tolerating and records
the skipped count.
parseRecommendationDetail, asserting the MCP search errors rather thanreturning a short menu, and that the scheduler path still returns the
survivors.
References
providers/aws/recommendations/parser_ri.go:18-40(parseRecommendations)mcp/tools/search_recommendations.go(fetchSearchCombos,searchCombos)d76aa7c00.