From eabb4a9e26dbb6187f259175b2dc28b688aae9b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20S=C3=B8gaard?= <9662430+andershagbard@users.noreply.github.com> Date: Fri, 19 Jun 2026 17:47:32 +0200 Subject: [PATCH 1/6] fix(theme): ignore atomic-write tmp files in the theme dev file watcher Editor tools (including AI coding assistants) create temporary files like `blocks/foo.liquid.tmp.93809.4dbd82e0b95a` during atomic save operations, then delete them within milliseconds. Because the per-event debounce fires independently for `add` and `unlink`, Shopify's file watcher sees the add event, attempts to upload the invalid asset key, and crashes the dev server. Adding `**/*.tmp.*` to `DEFAULT_IGNORE_PATTERNS` prevents chokidar from emitting events for these files entirely. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- packages/theme/src/cli/constants.ts | 4 ++++ packages/theme/src/cli/utilities/theme-fs.test.ts | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/packages/theme/src/cli/constants.ts b/packages/theme/src/cli/constants.ts index b8a86bd926b..bdeb21b3ade 100644 --- a/packages/theme/src/cli/constants.ts +++ b/packages/theme/src/cli/constants.ts @@ -31,6 +31,10 @@ export const DEFAULT_IGNORE_PATTERNS = [ '**/config.yml', '**/node_modules/', '.prettierrc.json', + // Atomic-write temp files (e.g. blocks/foo.liquid.tmp.93809.4dbd82e0b95a): created and + // deleted within milliseconds, but Shopify's watcher sees the add before the delete and + // tries to upload an invalid key, crashing the dev server. + '**/*.tmp.*', ] export const MAX_GRAPHQL_THEME_FILES = 50 diff --git a/packages/theme/src/cli/utilities/theme-fs.test.ts b/packages/theme/src/cli/utilities/theme-fs.test.ts index ce4e50169c9..8eaf94ff29c 100644 --- a/packages/theme/src/cli/utilities/theme-fs.test.ts +++ b/packages/theme/src/cli/utilities/theme-fs.test.ts @@ -158,6 +158,21 @@ describe('theme-fs', () => { expect(watchedPaths.some((path) => path.includes('listings'))).toBe(false) }) + test('ignores editor atomic-write tmp files in the watcher', async () => { + // Given + const root = joinPath(locationOfThisFile, 'fixtures/theme') + const watchSpy = vi.spyOn(chokidar, 'watch') + + // When + const themeFileSystem = mountThemeFileSystem(root) + await themeFileSystem.ready() + await themeFileSystem.startWatcher('123', {token: 'token'} as any) + + // Then + const watchOptions = watchSpy.mock.calls[0]?.[1] as {ignored: string[]} + expect(watchOptions.ignored).toContain('**/*.tmp.*') + }) + test('"delete" removes the file from the local disk and updates the file map', async () => { // Given const root = joinPath(locationOfThisFile, 'fixtures/theme') From b98285a8e6099e4f778690910f4679589efc15ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20S=C3=B8gaard?= Date: Sat, 20 Jun 2026 10:51:43 +0200 Subject: [PATCH 2/6] Update comment --- packages/theme/src/cli/constants.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/theme/src/cli/constants.ts b/packages/theme/src/cli/constants.ts index bdeb21b3ade..9803a80a101 100644 --- a/packages/theme/src/cli/constants.ts +++ b/packages/theme/src/cli/constants.ts @@ -31,10 +31,7 @@ export const DEFAULT_IGNORE_PATTERNS = [ '**/config.yml', '**/node_modules/', '.prettierrc.json', - // Atomic-write temp files (e.g. blocks/foo.liquid.tmp.93809.4dbd82e0b95a): created and - // deleted within milliseconds, but Shopify's watcher sees the add before the delete and - // tries to upload an invalid key, crashing the dev server. - '**/*.tmp.*', + '**/*.tmp.*', // Atomic-write temp files (e.g. blocks/foo.liquid.tmp.93809.4dbd82e0b95a) ] export const MAX_GRAPHQL_THEME_FILES = 50 From 25d9adb40c1f559dc976d5cddef814f41d895ef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20S=C3=B8gaard?= Date: Sat, 20 Jun 2026 14:46:23 +0200 Subject: [PATCH 3/6] chore: add changeset for tmp file watcher fix --- .changeset/tiny-wolves-ignore-tmp.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tiny-wolves-ignore-tmp.md diff --git a/.changeset/tiny-wolves-ignore-tmp.md b/.changeset/tiny-wolves-ignore-tmp.md new file mode 100644 index 00000000000..62800874539 --- /dev/null +++ b/.changeset/tiny-wolves-ignore-tmp.md @@ -0,0 +1,5 @@ +--- +'@shopify/theme': patch +--- + +Ignore atomic-write temporary files (e.g. `blocks/foo.liquid.tmp.93809.4dbd82e0b95a`) in the theme dev watcher to prevent crashes when editors save files. From bbc49c5b649e9801b5c50f2e0f1065046a6dd496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20S=C3=B8gaard?= <9662430+andershagbard@users.noreply.github.com> Date: Wed, 24 Jun 2026 08:51:52 +0200 Subject: [PATCH 4/6] fix(theme): narrow atomic-write tmp file ignore patterns to theme dirs Replace the broad `**/*.tmp.*` glob with directory-specific patterns scoped to the theme file types that actually use atomic writes. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- packages/theme/src/cli/constants.ts | 5 ++++- packages/theme/src/cli/utilities/theme-fs.test.ts | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/theme/src/cli/constants.ts b/packages/theme/src/cli/constants.ts index 9803a80a101..bb5b3951216 100644 --- a/packages/theme/src/cli/constants.ts +++ b/packages/theme/src/cli/constants.ts @@ -31,7 +31,10 @@ export const DEFAULT_IGNORE_PATTERNS = [ '**/config.yml', '**/node_modules/', '.prettierrc.json', - '**/*.tmp.*', // Atomic-write temp files (e.g. blocks/foo.liquid.tmp.93809.4dbd82e0b95a) + // Atomic-write temp files (e.g. blocks/foo.liquid.tmp.bar.baz) + '**/{blocks,layouts,snippets}/**/*.liquid.tmp.*', + '**/{config,locales}/**/*.json.tmp.*', + '**/{sections,templates}/**/*.{liquid,json}.tmp.*', ] export const MAX_GRAPHQL_THEME_FILES = 50 diff --git a/packages/theme/src/cli/utilities/theme-fs.test.ts b/packages/theme/src/cli/utilities/theme-fs.test.ts index 8eaf94ff29c..94a444eac8d 100644 --- a/packages/theme/src/cli/utilities/theme-fs.test.ts +++ b/packages/theme/src/cli/utilities/theme-fs.test.ts @@ -170,7 +170,9 @@ describe('theme-fs', () => { // Then const watchOptions = watchSpy.mock.calls[0]?.[1] as {ignored: string[]} - expect(watchOptions.ignored).toContain('**/*.tmp.*') + expect(watchOptions.ignored).toContain('**/{blocks,layouts,snippets}/**/*.liquid.tmp.*') + expect(watchOptions.ignored).toContain('**/{config,locales}/**/*.json.tmp.*') + expect(watchOptions.ignored).toContain('**/{sections,templates}/**/*.{liquid,json}.tmp.*') }) test('"delete" removes the file from the local disk and updates the file map', async () => { From a8bcc8c48bc09e78572567b8cfc0bb9e50b45a5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20S=C3=B8gaard?= Date: Thu, 25 Jun 2026 07:58:13 +0200 Subject: [PATCH 5/6] Update packages/theme/src/cli/constants.ts Co-authored-by: Gray Gilmore --- packages/theme/src/cli/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/theme/src/cli/constants.ts b/packages/theme/src/cli/constants.ts index bb5b3951216..2ac503b0a9c 100644 --- a/packages/theme/src/cli/constants.ts +++ b/packages/theme/src/cli/constants.ts @@ -32,7 +32,7 @@ export const DEFAULT_IGNORE_PATTERNS = [ '**/node_modules/', '.prettierrc.json', // Atomic-write temp files (e.g. blocks/foo.liquid.tmp.bar.baz) - '**/{blocks,layouts,snippets}/**/*.liquid.tmp.*', + '**/{blocks,layout,snippets}/**/*.liquid.tmp.*', '**/{config,locales}/**/*.json.tmp.*', '**/{sections,templates}/**/*.{liquid,json}.tmp.*', ] From deb45d6925d670b49b777723117f0d198bfcb840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20S=C3=B8gaard?= Date: Thu, 25 Jun 2026 07:58:43 +0200 Subject: [PATCH 6/6] Update theme-fs.test.ts --- .../theme/src/cli/utilities/theme-fs.test.ts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/packages/theme/src/cli/utilities/theme-fs.test.ts b/packages/theme/src/cli/utilities/theme-fs.test.ts index 94a444eac8d..ce4e50169c9 100644 --- a/packages/theme/src/cli/utilities/theme-fs.test.ts +++ b/packages/theme/src/cli/utilities/theme-fs.test.ts @@ -158,23 +158,6 @@ describe('theme-fs', () => { expect(watchedPaths.some((path) => path.includes('listings'))).toBe(false) }) - test('ignores editor atomic-write tmp files in the watcher', async () => { - // Given - const root = joinPath(locationOfThisFile, 'fixtures/theme') - const watchSpy = vi.spyOn(chokidar, 'watch') - - // When - const themeFileSystem = mountThemeFileSystem(root) - await themeFileSystem.ready() - await themeFileSystem.startWatcher('123', {token: 'token'} as any) - - // Then - const watchOptions = watchSpy.mock.calls[0]?.[1] as {ignored: string[]} - expect(watchOptions.ignored).toContain('**/{blocks,layouts,snippets}/**/*.liquid.tmp.*') - expect(watchOptions.ignored).toContain('**/{config,locales}/**/*.json.tmp.*') - expect(watchOptions.ignored).toContain('**/{sections,templates}/**/*.{liquid,json}.tmp.*') - }) - test('"delete" removes the file from the local disk and updates the file map', async () => { // Given const root = joinPath(locationOfThisFile, 'fixtures/theme')