Summary
The CLI cannot buy any Savings Plan. Main-path purchases fail with "Missing Region", and CSV Savings Plan and EC2 rows fail on missing Details. The dry run reports every row as a success, so it does not predict whether the purchase will work.
Where
Commit bef177f: cmd/multi_service.go:491-493, cmd/multi_service_csv.go:135-155, cmd/multi_service_helpers.go:226-234 (createDryRunResult). Library side (cloud-commitments-go 73d3366): recommendations/parser_sp.go:231-254, providers/aws/services/savingsplans/client.go:44-48,221-225, providers/aws/services/ec2/client.go:404-419.
Failure scenario
Main path: the SP parser never sets the top-level rec.Region (only Details.Region, and only for EC2Instance). purchaseSingleRec builds the client with regionalCfg.Region = rec.Region, so the Savings Plans client gets an empty region. An offline probe with a fake HTTP client that sends nothing shows CreateSavingsPlan with Region: "" fails before any request: failed to resolve service endpoint, endpoint rule error, Invalid Configuration: Missing Region. DescribeSavingsPlansOfferings runs on the same client and fails the same way.
CSV path: parseCSVRecord builds Details only for RDS, ElastiCache and EC2 rows with a non-empty Engine. SP rows reach PurchaseCommitment with nil Details ("invalid service details for Savings Plans"). EC2 rows carry no Tenancy or Scope, and buildEC2OfferingQuery refuses an empty value, so every CSV EC2 row fails too.
Dry run: cudly --services savingsplans shows every row Success=true with a projected savings total. After --purchase and confirmation, every SP row fails, the summary ignores SP failures, and the process exits 0. Nothing wrong is bought; the advertised features do not work. Verified with an SDK probe (live-style, offline transport).
Related: #1582 (closed; fixed the region filter that created the empty top-level Region), #379 (CSV Term/Payment validation) and #1327 (CSV numeric parsing) are different causes. Fix together with CLI-03 (account-level Savings Plans collected once per --regions entry; not filed here): fixing this issue alone turns that duplication into an N-fold real Savings Plan purchase.
Suggested fix
Give SP purchases a real region (us-east-1, or Details.Region for EC2Instance). Round-trip SP Details (plan type, hourly commitment, offering ID) and EC2 Tenancy/Scope through the CSV writer and reader. Make the dry run validate the same preconditions as the purchase (Details type, required fields, region). Add a test that runs the real purchase path against a stubbed SDK transport for an SP recommendation straight from the parser.
Severity
High: the Savings Plan and CSV EC2 purchase features are broken and the dry run reports them as working.
Reviewer ID: CLI-01 (cm C11)
Found by the 2026-10-08 review fleet.
Summary
The CLI cannot buy any Savings Plan. Main-path purchases fail with "Missing Region", and CSV Savings Plan and EC2 rows fail on missing Details. The dry run reports every row as a success, so it does not predict whether the purchase will work.
Where
Commit bef177f:
cmd/multi_service.go:491-493,cmd/multi_service_csv.go:135-155,cmd/multi_service_helpers.go:226-234(createDryRunResult). Library side (cloud-commitments-go 73d3366):recommendations/parser_sp.go:231-254,providers/aws/services/savingsplans/client.go:44-48,221-225,providers/aws/services/ec2/client.go:404-419.Failure scenario
Main path: the SP parser never sets the top-level
rec.Region(onlyDetails.Region, and only for EC2Instance).purchaseSingleRecbuilds the client withregionalCfg.Region = rec.Region, so the Savings Plans client gets an empty region. An offline probe with a fake HTTP client that sends nothing showsCreateSavingsPlanwithRegion: ""fails before any request:failed to resolve service endpoint, endpoint rule error, Invalid Configuration: Missing Region.DescribeSavingsPlansOfferingsruns on the same client and fails the same way.CSV path:
parseCSVRecordbuilds Details only for RDS, ElastiCache and EC2 rows with a non-empty Engine. SP rows reachPurchaseCommitmentwith nil Details ("invalid service details for Savings Plans"). EC2 rows carry no Tenancy or Scope, andbuildEC2OfferingQueryrefuses an empty value, so every CSV EC2 row fails too.Dry run:
cudly --services savingsplansshows every rowSuccess=truewith a projected savings total. After--purchaseand confirmation, every SP row fails, the summary ignores SP failures, and the process exits 0. Nothing wrong is bought; the advertised features do not work. Verified with an SDK probe (live-style, offline transport).Related: #1582 (closed; fixed the region filter that created the empty top-level Region), #379 (CSV Term/Payment validation) and #1327 (CSV numeric parsing) are different causes. Fix together with CLI-03 (account-level Savings Plans collected once per
--regionsentry; not filed here): fixing this issue alone turns that duplication into an N-fold real Savings Plan purchase.Suggested fix
Give SP purchases a real region (
us-east-1, orDetails.Regionfor EC2Instance). Round-trip SP Details (plan type, hourly commitment, offering ID) and EC2 Tenancy/Scope through the CSV writer and reader. Make the dry run validate the same preconditions as the purchase (Details type, required fields, region). Add a test that runs the real purchase path against a stubbed SDK transport for an SP recommendation straight from the parser.Severity
High: the Savings Plan and CSV EC2 purchase features are broken and the dry run reports them as working.
Reviewer ID: CLI-01 (cm C11)
Found by the 2026-10-08 review fleet.