Truncate the skipped-download list instead of deleting it#264
Conversation
apply-runtime mounts .import-download-list-skipped.jsonl into the generated runtime (the temporary remote-uploads proxy reads it to decide which paths are still remote-only). PHP-WASM re-resolves its mounts on every runtime rotation and on restart from a persisted config, so unlink-ing the file crashes the running site with ENOENT. Replace every deletion of the skipped-download list with a new ImportClient::clear_skipped_download_list() helper that truncates the file to empty in place. An empty file means "no remote-only files left", which is exactly what every has-skipped check wants (they all test for size > 0), so the mount stays valid and no caller behavior changes. Updated the four call sites in import.php (abort, clearing before diff, no skipped to fetch, skipped fetch complete) and the one in Pull::prepare_repull().
Pull pipeline performance —
|
| Stage | PR | trunk | Δ | Status | Details |
|---|---|---|---|---|---|
playground-sqlite-db-pull |
8.88 s | 8.49 s | ⚪ +391 ms (+4.6%) | ✓ | condition=db-pull in PHP.wasm runtime=php.wasm 8.3 wp_mysql_parser=enabled mode=lexer native_lexer=verified native_token_stream=WP_MySQL_Native_Token_Stream native_token_count=18 native_parser=selected trunk: condition=db-pull in PHP.wasm runtime=php.wasm 8.3 wp_mysql_parser=enabled mode=lexer native_lexer=verified native_token_stream=WP_MySQL_Native_Token_Stream native_token_count=18 native_parser=selected |
playground-sqlite-db-apply |
3.44 s | 3.51 s | ⚪ -68 ms (-1.9%) | ✓ | condition=db-apply to SQLite in PHP.wasm runtime=php.wasm 8.3 wp_mysql_parser=enabled mode=parser native_lexer=verified native_token_stream=WP_MySQL_Native_Token_Stream native_token_count=18 native_parser=verified native_ast=WP_MySQL_Native_Parser_Node sqlite_driver_parser=verified trunk: condition=db-apply to SQLite in PHP.wasm runtime=php.wasm 8.3 wp_mysql_parser=enabled mode=parser native_lexer=verified native_token_stream=WP_MySQL_Native_Token_Stream native_token_count=18 native_parser=verified native_ast=WP_MySQL_Native_Parser_Node sqlite_driver_parser=verified |
| Total | 12.32 s | 12.00 s | ⚪ +323 ms (+2.7%) |
Numbers carry runner noise; treat single-run deltas as directional, not authoritative.
📈 Trunk performance history — commit-by-commit timeline.
There was a problem hiding this comment.
Pull request overview
This PR fixes a crash (ENOENT) that occurred after the deferred uploads download completed. The root cause was that the importer deleted the .import-download-list-skipped.jsonl file via unlink(), but apply-runtime mounts this file into the generated PHP-WASM runtime. When the file was gone, the mount broke on the next runtime rotation or restart, crashing both the server and WP-CLI.
Changes:
- Added a new
ImportClient::clear_skipped_download_list()method that truncates the file to empty instead of deleting it, keeping the mount valid while signalling "no remote-only files left." - Replaced all four
unlinkcall sites inimport.phpand one inPull::prepare_repull()with calls to the new method. - Added a unit test (
ClearSkippedDownloadListTest) and updated the e2e test to assert the file is truncated (exists with size 0) rather than deleted.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
packages/reprint-importer/src/import.php |
Adds clear_skipped_download_list() method; replaces four unlink call sites with it |
packages/reprint-importer/src/lib/pull/class-pull.php |
Replaces unlink of skipped list in prepare_repull() with the new truncation method |
tests/Import/ClearSkippedDownloadListTest.php |
New unit test covering truncation and no-op-when-absent behaviors |
tests/e2e/tests/import-40-defer-uploads.test.js |
Updates assertion from "file deleted" to "file exists and is empty" |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
This sounds like a bug in PHP-WASM where the runtime rotation should tolerate missing files:
|
|
I'm exploring a fix here WordPress/wordpress-playground#3811 |
Thanks! That makes sense to me, I agree this might look like an escape hatch for that scenario. |
What it does
A pulled site no longer crashes with
ENOENTafter the deferred uploads (--filter=skipped-earlier) tail finishes downloading. The importer now empties the skipped-download list by truncating it in place instead of deleting it.Rationale
apply-runtimemounts the skipped-download list (.import-download-list-skipped.jsonl) into the generated runtime — the temporary remote-uploads proxy reads it to decide whichwp-content/uploadspaths are still remote-only. Several cleanup paths in the importer deleted that file withunlink().PHP-WASM re-resolves its mounts on every runtime rotation (and on restart from a persisted config). Once the file was gone, the mount resolved to a missing path and the running site crashed with
ENOENT— both the server start and WP-CLI failed. The crash surfaced right after the uploads tail completed, which is where the list was being deleted.Implementation
Replaced every deletion of the list with a new
ImportClient::clear_skipped_download_list()that truncates the file to empty in place:An empty file means "no remote-only files left", which is exactly what every
has_skippedcheck already wants — they all testfilesize(...) > 0. So the runtime mount stays valid and no caller behavior changes. Updated the four call sites inimport.php(abort files-pull, clearing before diff stage, no skipped files to fetch, skipped files fetch complete) and the one inPull::prepare_repull().Testing instructions
The easiest way to reproduce the issue is to use Studio code:
Yto create a new Studio sitecomposer build:pharon this reporeprint.pharto the following folder on Studiocp reprint.phar <studio_repo>/wp-files/reprintnpm run cli:buildso thepharfile is copied to the dist folder