medusajs/medusa

[Bug]: Stale cache in getDataForComputation causes empty inventory data after enabling inventory management on a variant

Open

#15178 opened on Apr 23, 2026

View on GitHub
 (9 comments) (0 reactions) (0 assignees)TypeScript (22,539 stars) (2,090 forks)batch import
Stalegood first issuetype: bugversion: 2.0

Description

Package.json file

{
    "name": "medusa-starter-default",
    "version": "0.0.1",
    "description": "A starter for Medusa projects.",
    "author": "Medusa (https://medusajs.com)",
    "license": "MIT",
    "keywords": [
        "sqlite",
        "postgres",
        "typescript",
        "ecommerce",
        "headless",
        "medusa"
    ],
    "dependencies": {
        "@medusajs/admin-sdk": "2.12.5",
        "@medusajs/cli": "2.12.5",
        "@medusajs/file-local": "2.12.5",
        "@medusajs/framework": "2.12.5",
        "@medusajs/index": "2.12.5",
        "@medusajs/medusa": "2.12.5",
        "@opentelemetry/api": "^1.9.0",
        "@opentelemetry/core": "1.x",
        "@opentelemetry/exporter-trace-otlp-grpc": "^0.208.0",
        "@opentelemetry/instrumentation-pg": "^0.61.2",
        "@opentelemetry/resources": "^2.2.0",
        "@opentelemetry/sdk-node": "^0.208.0",
        "@opentelemetry/sdk-trace-base": "1.x",
        "@opentelemetry/sdk-trace-node": "^2.2.0",
        "@opentelemetry/semantic-conventions": "1.x",
        "@radix-ui/react-dialog": "1.1.4",
        "@radix-ui/react-popover": "1.1.4",
        "@radix-ui/react-separator": "1.1.4",
        "@toast-ui/editor": "^3.2.2",
        "@toast-ui/react-editor": "^3.2.3",
        "body-parser": "^1.20.3",
        "cmdk": "^1.1.1",
        "cors": "^2.8.5",
        "dotenv": "^16.4.7",
        "lodash": "^4.17.21",
        "lucide-react": "^0.487.0",
        "multer": "^1.4.5-lts.2",
        "sharp": "^0.34.1",
        "typeorm": "^0.3.20"
    },
    "devDependencies": {
        "@medusajs/test-utils": "2.12.5",
        "@swc/core": "1.5.7",
        "@swc/jest": "^0.2.36",
        "@types/body-parser": "^1",
        "@types/cors": "^2",
        "@types/jest": "^29.5.13",
        "@types/lodash": "^4.17.16",
        "@types/multer": "^1",
        "@types/node": "^20.0.0",
        "@types/pdfmake": "^0",
        "@types/react": "^18.3.2",
        "@types/react-dom": "^18.2.25",
        "@typescript-eslint/eslint-plugin": "^8.35.1",
        "@typescript-eslint/parser": "^8.35.1",
        "eslint": "8.57.0",
        "eslint-config-prettier": "^10.1.5",
        "eslint-plugin-prettier": "^5.5.1",
        "eslint-plugin-react": "^7.37.5",
        "eslint-plugin-react-hooks": "^5.2.0",
        "jest": "^29.7.0",
        "prettier": "^3.6.2",
        "prop-types": "^15.8.1",
        "react": "^18.2.0",
        "react-dom": "^18.2.0",
        "rimraf": "^6.0.1",
        "ts-node": "^10.9.2",
        "tsc-alias": "^1.8.11",
        "typescript": "^5.6.2",
        "vite": "^5.2.11"
    },
    "engines": {
        "node": ">=20"
    }
}

Node.js version

v24.14.0

Database and its version

PostgreSQL 16.3

Operating system name and version

Macos 26/Ubuntu 24LTS

Browser name

No response

What happended?

In packages/core/utils/src/product/get-variant-availability.ts when the caching module is enabled, the getDataForComputation function caches the results of the product_variant_inventory_items query with no cache tags. This causes the cache entry to become permanently stale if a product variant is initially created with inventory management disabled (or before any inventory items are linked), and then inventory management is later enabled and an inventory item is assigned to that variant.

Steps to reproduce (with the caching module enabled):

  1. Create a product via the admin UI, adding at least one variant. During creation, ensure the variant has "Inventory Management" toggled off — do not link any inventory items.
  2. Navigate to the product's detail page. Confirm that inventory management is disabled and no inventory items are listed. This initial state triggers the first product_variant_inventory_items query, which returns an empty array that is now cached for this variant ID.
  3. Edit the variant and toggle "Inventory Management" on. Save the variant.
  4. Create an inventory item and link it to the variant.
  5. Navigate back to the product's detail page and notice that the inventory quantity on the variant is showing incorrectly. The stale value from the product_variant_inventory_items query is being returned and the variant.inventory_quantity is null instead of 0.
  6. Add a stock location for the inventory item and give it some stock.
  7. Navigate back to the product's detail page and the inventory for the variant is still incorrect. This value is also incorrect in any storefront queries that include the variant.inventory_quantity.

Root Cause

The offending code is in getDataForComputation.

Compare this with the sales_channel_locations query immediately below it (lines 187–201), which correctly supplies cache tags (SalesChannel:${data.sales_channel_id} and StockLocation:list:*), ensuring it is invalidated when relevant entities change.

The product_variant_inventory_items cache entry is stored without any tags, so no invalidation event (e.g., creating or updating an InventoryItem or a ProductVariantInventoryItem link) can ever clear it. The stale empty result persists for the lifetime of the cache TTL.

Expected Fix

The product_variant_inventory_items query should be cached with appropriate tags tied to the queried variant IDs and the inventory item, for example:

{
  cache: {
    enable: true,
    tags: [
      ...data.variant_ids.map((id) => `ProductVariant:${id}`),
      "InventoryItem:list:*",
    ],
  },
}

Alternatively, caching could be disabled for this query, as this product variant availability is likely to change regularly enough when users are purchasing products from a store.

Expected behavior

The variant's availability reflects the newly linked inventory item and shows the correct stock quantity.

Actual behavior

The variant continues to show null availability, as if no inventory item is linked, because the cache is still serving the empty array stored in step 2. The stale cache entry has no tags, so linking the inventory item does not trigger any cache invalidation, and the empty result persists until the cache TTL naturally expires.

Link to reproduction repo

See steps above

Contributor guide