Cart Transform linesMerge accepted with no errors, but merge never applies (confirmed even in a completed order)

Summary

A cart_transform Shopify Function correctly computes and returns a linesMerge operation (confirmed via Shopify’s own function-run logs), the cart transform is activated for the shop, but the resulting cart lines are never actually merged — not in the cart page, not in /cart.js, not at checkout, and not in the final completed order.

Environment

Shop: restockpulse-dev.myshopify.com — confirmed a genuine Shopify Partner development store, not just a store with “-dev” in the name: shop.plan.partnerDevelopment: true, shop.plan.displayName: “Basic App Development”, shop.plan.shopifyPlus: false (queried directly via the Admin GraphQL API). Ruling this out matters because Shopify Plus/development-store exemptions apply to lineUpdate specifically, not the linesMerge operation this report is about.

App version: deployed and released via shopify app deploy. Extension: cart_transform type, target cart.transform.run. Cart transform: created via cartTransformCreate, userErrors: . Function API version: 2026-04. Access scopes include write_cart_transforms.

Function code

src/cart_transform_run.graphql:

query CartTransformRunInput {
cart {
lines {
id
quantity
groupAttribute: attribute(key: “_sheafly_bundle_group”) {
value
}
}
}
}

src/cart_transform_run.ts:

import type {
CartTransformRunInput,
CartTransformRunResult,
} from “../generated/api”;

const NO_CHANGES: CartTransformRunResult = {
operations: ,
};

const TEST_PARENT_VARIANT_ID = “gid://shopify/ProductVariant/52570730725508”;
// requiresComponents: true, one placeholder component

export function cartTransformRun(
input: CartTransformRunInput,
): CartTransformRunResult {
const groups = new Map();

for (const line of input.cart.lines) {
const groupId = line.groupAttribute?.value;
if (!groupId) continue;
const existing = groups.get(groupId) ?? ;
existing.push({ id: line.id, quantity: line.quantity });
groups.set(groupId, existing);
}

const operations = ;
for (const [groupId, lines] of groups) {
if (lines.length < 2) continue;
operations.push({
linesMerge: {
cartLines: lines.map((l) => ({
cartLineId: l.id,
quantity: l.quantity,
})),
parentVariantId: TEST_PARENT_VARIANT_ID,
title: Mix and match spike (${groupId}),
attributes: [{ key: “_sheafly_bundle_group”, value: groupId }],
},
});
}

return operations.length > 0 ? { operations } : NO_CHANGES;
}

Steps to reproduce

Add two different product variants to the cart, each with the same _sheafly_bundle_group line item property (e.g. via /cart/add.js with properties). Then view /cart.js, the cart page, or checkout.

Expected

One merged bundle line (per linesMerge semantics — title/parent variant from the operation, two components).

Actual

Two separate, unmerged line items — every time, at every stage, including the completed order.

Evidence the function itself is working correctly

Captured Shopify function-run log (every invocation during testing looked like this):

{
“payload”: {
“export”: “cart-transform-run”,
“input”: {
“cart”: {
“lines”: [
{ “id”: “gid://shopify/CartLine/…”, “quantity”: 1, “groupAttribute”: { “value”: “test-group-3” } },
{ “id”: “gid://shopify/CartLine/…”, “quantity”: 1, “groupAttribute”: { “value”: “test-group-3” } }
]
}
},
“output”: {
“operations”: [
{
“linesMerge”: {
“cartLines”: [
{ “cartLineId”: “gid://shopify/CartLine/…”, “quantity”: 1 },
{ “cartLineId”: “gid://shopify/CartLine/…”, “quantity”: 1 }
],
“parentVariantId”: “gid://shopify/ProductVariant/52570730725508”,
“title”: “Mix and match spike (test-group-3)”,
“attributes”: [{ “key”: “_sheafly_bundle_group”, “value”: “test-group-3” }]
}
}
]
},
“functionId”: “019fa87f-22c8-7b9d-a065-62743a328b95”,
“target”: “cart.transform.run”
},
“status”: “success”
}

The input and output are exactly correct for the merge to happen. It’s the actual merge that never occurs.

What was ruled out before filing this

Conflicting bundle-shell variant: first attempt reused an existing bundle already wired to different real components (productVariantComponents); switched to a dedicated, freshly-created shell variant with a single placeholder component. No change.

Local dev-preview artifact: ran shopify app deploy to create and release a real app version, stopped shopify app dev entirely so the storefront ran purely on the deployed release, re-activated the transform against the deployed function ID. No change.

Stale cart/checkout session: repeatedly cleared cookies and cart state, confirmed genuinely new checkout tokens each attempt. No change.

Only visible at order completion, not mid-checkout: completed a full real test order (Bogus Gateway test card 1, no real charge). Order still shows two separate line items (“The Collection Snowboard: Hydrogen” $600 x 1, “The Collection Snowboard: Oxygen” $1,025 x 1), each independently carrying its own _sheafly_bundle_group property, not one merged bundle line.

Question

linesMerge is accepted with no errors, and the function computes the exact operation Shopify’s own docs describe every single time (confirmed by the captured function-run logs above) — but the merge never actually happens, all the way through to a completed order. This isn’t a Plus/development-store plan issue (confirmed the shop is a genuine Partner development store, and that restriction applies to lineUpdate, not linesMerge, in any case).

Is there a step missing here that isn’t apparent from the docs — or is this a genuine bug in how linesMerge is applied?


Title (separate field, not part of the body): Cart Transform linesMerge accepted with no errors, but merge never applies (confirmed even in a completed order)

post the problem in the dev forum not here. It is for merchant’s problems

One concrete thing to verify before treating this as a Function-output issue: confirm that parent variant 52570730725508 is published to the Online Store, not only Active. Shopify’s own linesMerge example says parentVariantId should point to an existing published variant.

There is a very close June 2026 Developer Community report where linesMerge returned successfully but was never applied. Shopify staff said it appeared related to a recent change in how parent-variant publication is resolved on the merge path:

If the exact parent variant is already published, I would reference that thread in a developer-forum report and include a fresh cart token, the parent variant ID, both component variant IDs, and the Function run ID. Those are the identifiers Shopify staff requested for tracing the same symptom.

Your successful Function log confirms that the operation was returned, but it does not confirm that Shopify applied it during the downstream merge step.

can add a datapoint from production, since theres a bit of “is linesMerge just broken?” energy in here: we run linesMerge on live stores daily (bundle merges in the storefront cart) and the ops are applying fine as of this week — so its not a blanket outage. which actually supports the publication-resolution theory: when this fails silently, its almost always about which publication/market the cart resolves against, or about what the op asks the platform to do with the source lines.

two checks that bit us in production and that i dont see mentioned yet:

  1. partial quantity consumption. if your merge op consumes only part of a source line (customer has qty 3 of component A, your op merges 2 into the set), the platform has to split that line mid-merge — and that path is where we saw ops get droped silently even with perfect function output. the rule we ended up with: a merge op must always consume its source lines fully. we enforce it by keeping every add-to-cart as its own line (unique line property per add) instead of letting shopify coalesce quantities, so a set always maps 1:1 onto whole lines. worth checking what your op does when quantities dont divide evenly.
  2. markets/catalog publication, not just “Online Store”. published to the online store channel isnt always enough — if the storefront session resolves to a market the shell product isnt published for (dev stores love defaulting to odd countries), the parent lookup fails and the op gets dropped with no error anywhere. check the shell product against the actual market catalog your test cart runs under, and test once in a clean incognito session pinned to your main market.

if you’re blocked meanwhile: the pragmatic fallback is a native fixed bundle (productBundleCreate) with expand instead of merge — customer adds the parent directly, components come off stock, no merge path involved. not equivalent if you specifically need “merge separately added items”, but it unblocks checkout while the platform side gets sorted out.