Shopify / Shopify/theme-tools

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

Open
#1,279 1 comment 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
234
Forks
92
Avg merge
1d 3h
Merged PRs (30d)
6

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in packages/theme-graph/src/graph/traverse.ts and inspect the four JSON traversal sites that assume a section has a type, then review buildThemeGraph and themeCheckRun to understand the silent failure. Reproduce the contextual-template example and verify that graph construction succeeds and OrphanedSnippet no longer flags the rendered snippet.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.