A PDF preview loaded on a WordPress site I was working on, but the original PDF returned “access denied”. I had already worked on an upload-permission repair, and the tests had passed. The next uploads showed what my earlier tests had missed.
The first repair addressed permissions in the upload folders. Existing files became readable, and checks that created new files and nested folders passed. Those were useful results, but I had implicitly accepted them as evidence about a step they didn’t exercise: moving an uploaded file out of temporary storage.
The test used the WordPress function wp_upload_bits, which creates a new file in the upload folder from content supplied to it. WordPress documentation explicitly distinguishes that from moving an uploaded file. The name makes it an understandable choice for an upload test; the behavior makes it an incomplete one.
A browser upload takes a different route. PHP first stores the incoming file in a temporary location, then WordPress’s upload handler moves it into the destination folder. On this system, the file retained access-control settings from its temporary location. The destination folder’s rules for newly created files didn’t supply the missing read access.
That also explained the preview, which was a new file created in the destination. It inherited the access the original lacked. PHP read the original to generate the preview, but the web server serving visitors couldn’t read that same original. Two files associated with one upload had had their permissions set by different rules. The working preview was a clue to that difference (and a fairly convincing distraction).

The next diagnostic test sent actual multipart upload data through the site’s PHP worker. It reproduced the unreadable original and readable preview, giving me a test that could expose the failure before I used it to assess the correction.
The correction addressed inherited read access at the temporary-file boundary. It preserved the temporary directory’s isolation and kept incoming files private until publication, while allowing the web server to read the published originals. Making the whole upload tree broadly writable would have been inappropriate for a missing read permission.
Targeted multipart checks passed through both staging and production PHP workers. Originals and generated previews were readable, and the originals’ content hashes matched the input. The affected public PDFs were downloadable after repair; executable upload paths remained blocked and staging remained protected.
I didn’t verify every Media Library browser interaction in those checks. They were synthetic requests through the actual upload-processing path, and they established that the reproduced permission failure was corrected under the tested conditions. I also didn’t establish what had originally caused the stricter permissions. The immediate failure mechanism and its historical cause remain separate questions.
For this repair, the acceptance procedure now requires a fresh multipart upload through the actual PHP worker, checks on both the original and its preview, and verification that temporary files remain protected. Checking an existing repaired PDF wouldn’t answer whether the next upload will work.
When a defect comes back after a passing test, trace how the test reaches its result and how the user’s action reaches that same point. Where do the two paths first differ?
—jhunterj