Skip to content

fix: make copy deploy symlink-aware and surface deploy errors - #39

Open
ssx wants to merge 2 commits into
magento:masterfrom
ssx:feature/symlink-aware-deploy
Open

fix: make copy deploy symlink-aware and surface deploy errors#39
ssx wants to merge 2 commits into
magento:masterfrom
ssx:feature/symlink-aware-deploy

Conversation

@ssx

@ssx ssx commented Jun 9, 2026

Copy link
Copy Markdown

Description

The copy deploy strategy is not symlink-aware, and DeployManager silently swallows deploy failures outside debug mode. Together these cause Magento installs to end up with a partially or completely unpopulated setup/ directory (and other mapped dirs) with no error output.

Root cause

Copy::createDelegate() (dir-to-dir branch):

if (file_exists($destPath)) {
    $destPath .= DIRECTORY_SEPARATOR . basename($sourcePath);
}
mkdir($destPath, 0755, true);   // "mkdir(): File exists" for a dangling symlink

When a mapped destination is a symlink whose target does not currently exist (common with shared persistent storage, e.g. pub/media/custom_options -> /shared/pub/media/custom_options), file_exists() follows the link and reports the path as absent. mkdir() then runs against an existing link node and fails with mkdir(): File exists.

DeployManager::doDeploy() caught the resulting \ErrorException but only wrote it in debug mode, so the package's remaining mappings (including setup/) were skipped silently.

Fix

  1. Copy::createDelegate() is symlink-aware — a pre-existing symlink at the destination (e.g. a shared-storage mount) is preserved instead of being clobbered or mkdir-ed over. A destination that exists but is not a directory (and is not a symlink) now throws an \ErrorException naming the exact path, rather than letting mkdir() emit a path-less warning.
  2. DeployManager::doDeploy() hard-fails — a deploy error is no longer swallowed. It is re-thrown as a \RuntimeException naming the package, the underlying message, and the originating file:line, aborting the install with a non-zero exit. A broken/incomplete deploy means the application cannot run, so failing loudly is correct.

Legitimate shared-storage symlinks are preserved (not a failure); only genuine deploy errors abort.

Manual testing

On a clean magento/project-community-edition=2.4.8-p5 install (default copy strategy):

  1. find setup -type f | wc -l566 (baseline).
  2. ln -s /shared/pub/media/custom_options pub/media/custom_options (dangling target), then composer reinstall magento/magento2-base.
    • Before: setup/ empty/missing, no error printed; -vvv reveals mkdir(): File exists at Copy.php.
    • After: redeploy completes with no error, setup/ back to 566, and the symlink is preserved.

Coding standard

Verified clean under the Magento2 PHPCS standard (no silenced errors). The remaining DiscouragedFunction warnings on mkdir/is_dir/is_link/file_exists are pre-existing throughout this file and unavoidable here — Magento\Framework\Filesystem\DriverInterface is not available at Composer-plugin runtime.

Notes

Related upstream report filed at magento/magento2 issues.

The copy deploy strategy was not symlink-aware. When a mapped destination
is a symlink whose target does not currently exist (common with shared
persistent storage, e.g. pub/media/custom_options -> /shared/...), file_exists()
follows the link and reports the path as absent, so mkdir() runs against an
existing link node and fails with "mkdir(): File exists". The resulting
ErrorException aborts the rest of that package's deploy.

DeployManager::doDeploy() only logged that exception in debug mode, so the
failure was silent at normal verbosity, leaving directories such as setup/
partially or completely unpopulated with no diagnostic output.

- Copy::createDelegate(): preserve a pre-existing symlink at the destination
  instead of trying to mkdir over it.
- DeployManager::doDeploy(): write deploy failures as a warning at normal
  verbosity instead of only in debug mode.
@ssx

ssx commented Jun 9, 2026

Copy link
Copy Markdown
Author

This is a fix for the issue reported here: magento/magento2#40864

@ssx

ssx commented Jun 9, 2026

Copy link
Copy Markdown
Author

@magento I have signed the Magento Contributor Agreement

Follow-up to the symlink-aware deploy change. A failed file deploy leaves
Magento unable to run, so it should abort the install loudly rather than
continue.

- DeployManager::doDeploy(): re-throw deploy failures (previously swallowed
  outside debug mode) as a RuntimeException that names the package, the
  underlying message, and the originating file:line.
- Copy::createDelegate(): when the destination exists but is not a directory
  (and is not a symlink to preserve), throw an ErrorException naming the exact
  path instead of letting mkdir() emit a path-less "File exists" warning.

Complies with the Magento2 coding standard (no silenced errors; the remaining
discouraged-filesystem-function warnings are pre-existing and unavoidable in a
Composer plugin, where Magento\Framework\Filesystem is not available).
@ssx

ssx commented Jul 3, 2026

Copy link
Copy Markdown
Author

@magento-engcom-team is anyone alive that checks this repo?

@ssx

ssx commented Jul 3, 2026

Copy link
Copy Markdown
Author

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