Background
Reported by @cristim: clicking a single row checkbox on the Recommendations page sometimes ticks two Azure rows at once. Same UX on uncheck — unticking one unticks both. The two rows look like genuinely separate recommendations (different terms, or different subscriptions) but get coupled through the checkbox.
Likely root cause
internal/scheduler/scheduler.go:827 derives the recommendation ID from a 5-tuple:
key := fmt.Sprintf("%s:%s:%s:%s:%s", providerName, rec.Service, rec.Region, rec.ResourceType, rec.PaymentOption)
hash := sha256.Sum256([]byte(key))
recordID := hex.EncodeToString(hash[:])[:16]
The hash does not include Term or the cloud account / subscription ID. Two recs that differ only in those fields share an ID. Downstream:
- The frontend renders both rows, each with
data-rec-id="<same hash>" (frontend/src/recommendations.ts:1067-1069).
- The selection toggle at
recommendations.ts:1639-1660 walks input[data-rec-id] and matches by ID.
- A click on one box flips the underlying selection set; the resync redraws both boxes from
state.selectedRecommendations, so both end up "checked". Hence the two-rows-toggle-together behaviour.
This is most visible on Azure because Advisor frequently emits recs that vary only by subscription within the same provider/service/region/SKU/payment cell, but it's a generic bug — AWS and GCP can hit it too whenever 1yr+3yr recs exist for the same (provider, service, region, resource_type, payment) cell.
Suspected to share a root cause with the AWS-3yr-only bug — same broken hash drops one of two same-cell recs at storage time, while the surviving pairs collide on selection.
Repro
- On Azure, ensure at least one subscription emits two Advisor recs that share
(provider, service, region, resource_type, payment) but differ in term or subscription.
- Open the Recommendations tab.
- Tick the checkbox on the first such row.
- Observe: the second row's checkbox ticks as well.
Acceptance criteria
- Each rendered Recommendation row carries a globally unique
data-rec-id.
- The fix lives in the ID-hash function, not in the frontend (don't paper over it with row-index-based fallbacks).
- A scheduler-level test seeds two recs that differ only in term and asserts they get different IDs.
- A frontend test seeds two recs with distinct IDs in the same
(provider, service, region, resource_type, payment) cell and asserts that ticking one does NOT flip the other.
Suggested fix path
Add Term and CloudAccountID (or whatever account-equivalent the rec carries) to the hash key:
key := fmt.Sprintf("%s:%s:%s:%s:%s:%s:%s",
providerName, accountID, rec.Service, rec.Region,
rec.ResourceType, rec.Term, rec.PaymentOption)
Verify the hash is read at write time only — the column-filter machinery and the selection state both use rec.id opaquely, so changing the hash is safe as long as the same rec maps to the same ID in subsequent collections (deterministic across runs).
⚠ Verify before estimating effort: any persisted state keyed on the old IDs (account_credentials, purchase_executions.recommendations, plan rows) needs a re-key story or the next collection just produces fresh IDs and the old ones go stale gracefully — likely the latter, but confirm against internal/database/postgres/migrations/.
References
internal/scheduler/scheduler.go:817-846 — record construction + the broken hash
frontend/src/recommendations.ts:1067-1069 — row render
frontend/src/recommendations.ts:1639-1660 — selection toggle
frontend/src/state.ts:80-94 — selectedRecommendations Set keyed by string ID
- Related: AWS-3yr-only bug filed separately (likely shared root cause)
Background
Reported by @cristim: clicking a single row checkbox on the Recommendations page sometimes ticks two Azure rows at once. Same UX on uncheck — unticking one unticks both. The two rows look like genuinely separate recommendations (different terms, or different subscriptions) but get coupled through the checkbox.
Likely root cause
internal/scheduler/scheduler.go:827derives the recommendation ID from a 5-tuple:The hash does not include
Termor the cloud account / subscription ID. Two recs that differ only in those fields share an ID. Downstream:data-rec-id="<same hash>"(frontend/src/recommendations.ts:1067-1069).recommendations.ts:1639-1660walksinput[data-rec-id]and matches by ID.state.selectedRecommendations, so both end up "checked". Hence the two-rows-toggle-together behaviour.This is most visible on Azure because Advisor frequently emits recs that vary only by subscription within the same provider/service/region/SKU/payment cell, but it's a generic bug — AWS and GCP can hit it too whenever 1yr+3yr recs exist for the same (provider, service, region, resource_type, payment) cell.
Suspected to share a root cause with the AWS-3yr-only bug — same broken hash drops one of two same-cell recs at storage time, while the surviving pairs collide on selection.
Repro
(provider, service, region, resource_type, payment)but differ intermorsubscription.Acceptance criteria
data-rec-id.(provider, service, region, resource_type, payment)cell and asserts that ticking one does NOT flip the other.Suggested fix path
Add
TermandCloudAccountID(or whatever account-equivalent the rec carries) to the hash key:Verify the hash is read at write time only — the column-filter machinery and the selection state both use
rec.idopaquely, so changing the hash is safe as long as the same rec maps to the same ID in subsequent collections (deterministic across runs).⚠ Verify before estimating effort: any persisted state keyed on the old IDs (
account_credentials,purchase_executions.recommendations, plan rows) needs a re-key story or the next collection just produces fresh IDs and the old ones go stale gracefully — likely the latter, but confirm againstinternal/database/postgres/migrations/.References
internal/scheduler/scheduler.go:817-846— record construction + the broken hashfrontend/src/recommendations.ts:1067-1069— row renderfrontend/src/recommendations.ts:1639-1660— selection togglefrontend/src/state.ts:80-94— selectedRecommendations Set keyed by string ID