Skip to content

fix: prevent theme dev from overwriting local files after throttled uploads#7041

Draft
graygilmore wants to merge 2 commits intomainfrom
gg-fix-theme-dev-upload-overwrite
Draft

fix: prevent theme dev from overwriting local files after throttled uploads#7041
graygilmore wants to merge 2 commits intomainfrom
gg-fix-theme-dev-upload-overwrite

Conversation

@graygilmore
Copy link
Contributor

Summary

  • Fix bug where failed uploads (e.g. THROTTLED) were prematurely removed from unsyncedFileKeys, allowing the polling loop to overwrite local files with remote versions — defeating the "keep local" reconciliation choice
  • Only remove files from unsyncedFileKeys when uploadResults confirms success: true
  • Fix progress counter to only increment for successful uploads

Fixes https://github.com/Shopify/developer-tools-team/issues/1134

Details

In buildUploadJob, the .then() callback after uploadBatch unconditionally removed all files from unsyncedFileKeys and incremented progress by batch.length. Since uploadBatch never throws (it returns results with success: true/false), the .then() fired even when uploads failed. This left failed files unprotected from the polling loop in theme-polling.ts, which would detect a checksum mismatch and download the remote version over the local file.

The fix checks uploadResults.get(file.key)?.success === true per-file before removal. Files that fail all retries remain in unsyncedFileKeys, preventing polling overwrites.

Test plan

  • New test: "should not remove failed uploads from unsyncedFileKeys" — mocks a THROTTLED failure, verifies the file stays in unsyncedFileKeys
  • New test: "should remove file from unsyncedFileKeys when retry succeeds" — mocks a fail-then-succeed scenario, verifies correct removal
  • All 23 uploader tests pass
  • All 543 theme package tests pass
  • All 13 polling tests pass (no changes to polling logic)

🎩 Tophatting

  1. Create a theme with 170+ JSON template files
  2. Run shopify theme dev and select "Keep the local version" at reconciliation
  3. Observe that local files are no longer overwritten when some uploads are throttled

🤖 Generated with Claude Code

graygilmore and others added 2 commits March 16, 2026 15:47
chokidar's _handleFile listener compares mtimeMs and suppresses change events when mtime hasn't changed. Build tools that preserve timestamps (e.g. via touch --reference) cause silent misses on Linux/Windows.

chokidar emits a raw event unconditionally, even when the mtime check suppresses the change event. This adds a raw event listener as a fallback: when a raw event fires, we start a short 100ms timeout. If chokidar's normal change event fires within that window, we cancel the timeout (normal path handled it). If the timeout expires, we trigger the handler ourselves.

The existing handleFileUpdate already computes MD5 checksums and only syncs when content actually changes, so even if both paths fire, the checksum comparison prevents redundant uploads.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Failed uploads (e.g. THROTTLED) were unconditionally removed from unsyncedFileKeys in the .then() callback of uploadBatch, regardless of whether the upload actually succeeded. This allowed the polling loop in theme-polling.ts to see these unprotected files, detect a checksum mismatch, and overwrite local files with remote versions -- defeating the 'keep local' reconciliation choice.

The fix checks uploadResults.get(file.key)?.success === true before removing from unsyncedFileKeys and incrementing the progress counter. Files that fail all retries remain in unsyncedFileKeys, preventing the polling loop from overwriting them.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Contributor

Coverage report

St.
Category Percentage Covered / Total
🟢 Statements 82.8% 14621/17659
🟡 Branches 75.15% 7253/9651
🟢 Functions 82.14% 3712/4519
🟢 Lines 83.26% 13824/16603

Test suite run success

3816 tests passing in 1474 suites.

Report generated by 🧪jest coverage report action from 9034f51

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant