Skip to content

OrphanedSnippet: contextual templates (*.context.*.json) crash theme graph, flagging every snippet as orphaned #1279

Description

@bberg11

Description

A theme containing a contextual template (templates/*.context.*.json) causes buildThemeGraph to throw. themeCheckRun swallows that error in a bare catch, leaving themeGraph undefined, so getReferences() returns [] for every file and OrphanedSnippet reports every snippet in the theme as orphaned.

On a real theme this meant 152 of 153 snippets were flagged (the only exception being one snippet excluded via a top-level ignore), including snippets rendered directly by sections.

The failure is silent — nothing in the CLI output indicates the graph failed to build.

Root cause

Contextual templates use parent plus partial section overrides, so their section entries legitimately have no type (it is inherited from the parent template):

{
  "context": { "market": "ca" },
  "parent": "index.json",
  "sections": { "hero": { "settings": {} } }
}

traverseJsonModule assumes every section in a JSON template has a type and dereferences it with a non-null assertion:

https://github.com/Shopify/theme-tools/blob/main/packages/theme-graph/src/graph/traverse.ts

const typeProperty = node.children.find((child) => child.key.value === 'type')!;
const start = typeProperty.loc.start.offset;

This is inconsistent with the schema-based traversal functions in the same file, which all guard correctly:

const typeProperty = node.children.find((child) => child.key.value === 'type');
if (!typeProperty) continue;

Four sites use the unguarded non-null assertion:

  • traverseJsonModuleJsonModuleKind.Template
  • traverseJsonModuleJsonModuleKind.SectionGroup
  • traverseSectionReferences
  • traverseBlockReferences

Contextual section groups (sections/*.context.*.json) have the same shape and hit the latter sites, including nested blocks that carry only settings.

Reproduction

Minimal theme:

templates/index.json

{ "sections": { "hero": { "type": "hero" } }, "order": ["hero"] }

templates/index.context.ca.json

{ "context": { "market": "ca" }, "parent": "index.json", "sections": { "hero": { "settings": {} } } }

sections/hero.liquid

{% render 'greeting' %}
{% schema %}
{ "name": "Hero", "settings": [] }
{% endschema %}

snippets/greeting.liquid

<p>Hello</p>

Result with @shopify/theme-check-node@3.28.1:

=== WITH contextual template present ===
total offenses: 1
OrphanedSnippet: 1 [ 'greeting.liquid' ]

=== WITHOUT contextual template ===
total offenses: 0
OrphanedSnippet: 0 []

snippets/greeting.liquid is rendered by sections/hero.liquid, so it should never be reported.

Calling buildThemeGraph directly, outside the try/catch, shows the underlying error:

TypeError: Cannot read properties of undefined (reading 'loc')
    at traverseJsonModule (node_modules/@shopify/theme-graph/dist/graph/traverse.js:330:44)
    at async Promise.all (index 1)
    at async buildThemeGraph (node_modules/@shopify/theme-graph/dist/graph/build.js:33:5)

Impact

  • OrphanedSnippet is unusable on any theme using contextual templates or contextual section groups, which is common for themes serving multiple markets.
  • Every other cross-file check silently degrades, since getReferences/getDependencies return empty arrays whenever graph construction fails.
  • Adding the contextual templates to ignore in .theme-check.yml is not a workaround — graph traversal walks the templates and sections directories via deps.fs and never consults the ignore config. Verified: offense count stayed at 153 after ignoring the files.

Suggested fix

Guard the four JSON traversal sites the same way the schema traversal functions already do (skip the reference when there is no type property), so contextual overrides contribute no edge rather than throwing.

Separately, it may be worth logging when buildThemeGraph fails in themeCheckRun rather than discarding the error, since "graceful degradation" currently turns into confidently wrong results for every reference-based check.

Versions

  • @shopify/cli 4.6.1
  • @shopify/theme-check-node 3.28.1
  • @shopify/theme-graph 0.3.1
  • Also present on main at time of writing
  • Node 24.16.0, macOS

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions