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:
traverseJsonModule — JsonModuleKind.Template
traverseJsonModule — JsonModuleKind.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
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
Description
A theme containing a contextual template (
templates/*.context.*.json) causesbuildThemeGraphto throw.themeCheckRunswallows that error in a barecatch, leavingthemeGraphundefined, sogetReferences()returns[]for every file andOrphanedSnippetreports 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
parentplus partial section overrides, so their section entries legitimately have notype(it is inherited from the parent template):{ "context": { "market": "ca" }, "parent": "index.json", "sections": { "hero": { "settings": {} } } }traverseJsonModuleassumes every section in a JSON template has atypeand dereferences it with a non-null assertion:https://github.com/Shopify/theme-tools/blob/main/packages/theme-graph/src/graph/traverse.ts
This is inconsistent with the schema-based traversal functions in the same file, which all guard correctly:
Four sites use the unguarded non-null assertion:
traverseJsonModule—JsonModuleKind.TemplatetraverseJsonModule—JsonModuleKind.SectionGrouptraverseSectionReferencestraverseBlockReferencesContextual section groups (
sections/*.context.*.json) have the same shape and hit the latter sites, including nested blocks that carry onlysettings.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.liquidResult with
@shopify/theme-check-node@3.28.1:snippets/greeting.liquidis rendered bysections/hero.liquid, so it should never be reported.Calling
buildThemeGraphdirectly, outside thetry/catch, shows the underlying error:Impact
OrphanedSnippetis unusable on any theme using contextual templates or contextual section groups, which is common for themes serving multiple markets.getReferences/getDependenciesreturn empty arrays whenever graph construction fails.ignorein.theme-check.ymlis not a workaround — graph traversal walks the templates and sections directories viadeps.fsand 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
typeproperty), so contextual overrides contribute no edge rather than throwing.Separately, it may be worth logging when
buildThemeGraphfails inthemeCheckRunrather than discarding the error, since "graceful degradation" currently turns into confidently wrong results for every reference-based check.Versions
@shopify/cli4.6.1@shopify/theme-check-node3.28.1@shopify/theme-graph0.3.1mainat time of writing