Skip to content

fix(wix-manage): send each variant complete on a Catalog V3 update - #926

Closed
danshw wants to merge 3 commits into
mainfrom
fix/stores-variant-full-state
Closed

fix(wix-manage): send each variant complete on a Catalog V3 update#926
danshw wants to merge 3 commits into
mainfrom
fix/stores-variant-full-state

Conversation

@danshw

@danshw danshw commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

variantsInfo.variants replaces the array and each variant object — it does not merge them. The recipe already said to pass the entire existing array, and to include each existing variant id. An agent can satisfy both of those and still rebuild each variant from only its id plus the field it wants to change, which drops every field it did not carry. The update is then rejected on the first required one:

HTTP 400 {"message":"product is invalid:
`-- variantsInfo is invalid:
    `-- variants [at index 0] is invalid:
       `-- price must not be empty",
 "details":{"validationError":{"fieldViolations":[
   {"field":"product.variantsInfo.variants[0].price",
    "description":"must not be empty","violatedRule":"REQUIRED_FIELD"}]}}}

How it shows up

Setting a SKU on an existing product. From a recorded run, the agent read the recipe, located the product and re-read it with Get Product — then assembled the PATCH as:

const updatedVariants = currentVariants.map(v => ({ id: v.id, sku: "MUG-001" }));

The variant's price went with it, and the write was rejected. The agent recovered by retrying with the variant spread intact, so the user still got the right SKU — one rejected write and an extra round trip later.

The error table made this harder to diagnose than it needed to be: it attributed price must not be empty to a variant "created or replaced without a price" and prescribed the fix "on every new variant", so an agent updating an existing variant reads that row as not applying to it.

Changes

Two lines in update-product-with-options.md:

  • The paragraph on updating existing variants now says each variant object is replaced whole rather than merged, and to start from the variant as returned by Get Product.
  • The price must not be empty row now names the existing-variant case and says to carry price on every variant sent, not just new ones.

No request or response shapes are restated — both edits are about how to assemble a call from what Get Product already returned.

Coverage

New scenario yaml/wix-manage-evals/stores/update-product-sku-full-variant.yml, three assertions with the path judge gating at 7, per this repo's eval-scenario guide. Its 1-3 band is specifically a rejected-then-retried Update Product caused by a REQUIRED_FIELD violation on variantsInfo.variants[...] from fields the agent did not carry over, so the scenario fails on the behaviour this PR is meant to remove rather than on the outcome, which was already correct.

`variantsInfo.variants` replaces the array *and* each variant object —
it does not merge. The recipe said to pass the entire existing array and
to include each existing variant `id`, which an agent can satisfy while
still rebuilding each variant from only its `id` plus the field being
changed. That drops every field it did not carry, and the update is
rejected on the first required one:

    HTTP 400 {"message":"product is invalid:\n`-- variantsInfo is
    invalid:\n    `-- variants [at index 0] is invalid:\n       `--
    price must not be empty","details":{"validationError":
    {"fieldViolations":[{"field":"product.variantsInfo.variants[0].price",
    "description":"must not be empty","violatedRule":"REQUIRED_FIELD"}]}}}

Setting a SKU on an existing product hits this: the agent reads the
product, maps its variants to `{ id, sku }`, and loses the price. It
recovers by retrying with the full variant, so the user still gets the
right result — one rejected write later.

Say that each variant object is replaced whole, and correct the error
table, which attributed `price must not be empty` to new variants only
and so read as not applying to an existing-variant update.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@danshw

danshw commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

Eval comparison — the fix works; the scenario still cannot certify it

Fourth run, and the first in which both arms read the recipe, so recipe content was genuinely the only variable.

PR production
Recipe read yes yes
Correctness 10/10 10/10
Path judge 5/10 5/10
Steps 5 7

Production reproduced the defect this change documents, verbatim from its path judge:

First attempt matched choice by name but failed because variants only carry choice IDs, requiring subsequent attempts to resolve choiceId from product options (using choice.id first, then correcting to choice.choiceId). Suspected gap: lack of clear documentation on how variants map to option choices in Catalog V3.

The PR arm did not hit it at all — it went straight to a working update in two calls. That is the change doing its job.

It nonetheless scored 5, for an unrelated reason: it parsed the Search Products response with the Catalog V1/V2 shape (productResults.results) rather than V3 (products), and had to re-run. Both arms therefore fail the gating path judge, which yields not-sure and returns before any approval decision.

So the change is supported by this run — the documented friction appears without it and not with it — but the scenario cannot award required while a separate response-shape confusion keeps the PR arm below the threshold. That is a distinct defect and belongs in the Find Products recipe, not here.

danshw and others added 2 commits August 8, 2026 20:56
The single-variant seed let an agent satisfy the rule by accident:
spreading the one variant it read back is the obvious move, so the run
often passed without the replace-not-merge behaviour ever being tested.

Seed the product with a Size option and two variants at different
prices, and ask for the SKU of one of them. Now the agent has to send
both variants complete — dropping the untouched variant, or rebuilding
either one from just its `id` plus the changed field, is rejected.

Judge bands updated to match: correctness checks the other variant
survives with its price, and the path judge's 1-3 band covers both the
REQUIRED_FIELD rejection and the dropped-variant case.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The recipe tells you to use `optionChoiceNames` in variants, so an agent
reasonably expects to read them back. Get Product does not return them
by default — each variant's `choices` carries `optionChoiceIds` only,
and `optionChoiceNames` is populated only when the request's `fields`
array includes `"VARIANT_OPTION_CHOICE_NAMES"`.

Matching a variant on a choice name the response never carried does not
raise; it selects nothing. In a recorded run the agent asked for one
size of a two-variant product, matched the choice by name, got no
variant back, and had to spend two more calls inspecting the product's
options before it could resolve the choice GUID and retry.

Give both ways out: request the field and match on names, or read the
GUID from `options[].choicesSettings.choices[].choiceId` and match
`variants[].choices[].optionChoiceIds.choiceId`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@danshw

danshw commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

Folded into #927. The two changes are coupled: the variant fixes here could not reach a clean path while the Search Products response envelope was still undocumented, so a single gate run over both is the only way to test them together. All commits from this branch were cherry-picked onto #927 unchanged.

@danshw danshw closed this Aug 8, 2026
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.

1 participant