Skip to content

Attempt to fix ENAMETOOLONG error by filtering and deduping open_basedir - #4432

Merged
fredrikekelund merged 7 commits into
trunkfrom
stu-2038-fix-enametoolong
Aug 6, 2026
Merged

Attempt to fix ENAMETOOLONG error by filtering and deduping open_basedir#4432
fredrikekelund merged 7 commits into
trunkfrom
stu-2038-fix-enametoolong

Conversation

@fredrikekelund

Copy link
Copy Markdown
Contributor

Related issues

How AI was used in this PR

I used Claude to iterate in relatively small chunks towards a result I liked. I used Codex to review the result.

Proposed Changes

In #4173, @Tropicalista reported an issue where native PHP sites with "site directory" file access would fail to start and report an ENAMETOOLONG error.

ENAMETOOLONG errors indicate that the CLI command was too long for the OS to handle (there is such a limit). We don't know what exactly made the command so long, but our best theory is that it's the open_basedir directive, which is passed as a CLI option. It could have happened if there was a pnpm-style node_modules directory anywhere in the site directory, for example (since this means there can be hundreds of symlink paths in the open_basedir directive).

This PR fixes the problem by applying the following changes:

  1. Ignore node_modules, .git, and .DS_Store paths in findSymlinksInDir(). Previously, we only ignored those paths when watching for new symlinks. If a pnpm-style node_modules dir is the source of the problem, then this is the key fix.
  2. If the open_basedir list contains /lorem/ipsum and /lorem/ipsum/dolor, /lorem/ipsum/dolor is now dropped before passing the list to PHP (because it is redundant). This change also ensures that newly added symlinks pointing to an already allowed directory don't trigger a process restart.

The next logical step would be to put the open_basedir directive in a site-specific php.ini file. I'll leave that for another PR, though.

Testing Instructions

  1. Download a WordPress plugin like performance-lab and unzip it
  2. Have a Studio site
  3. Start that site
  4. cd into wp-content/plugins for that site
  5. ln -s PATH_TO_DOWNLOADED_EXTRACTED_PLUGIN_DIR
  6. Open wp-admin
  7. Click Plugins in the sidebar
  8. Ensure that the plugin (Performance Lab in my example) is available to activate

This routine ensures that basic symlink watcher functionality and open_basedir compilation still work as expected. It does not test the ENAMETOOLONG error specifically, as we don't know exactly how to reproduce that.

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?

@fredrikekelund
fredrikekelund requested review from a team and bcotrim August 4, 2026 13:43
@fredrikekelund fredrikekelund self-assigned this Aug 4, 2026
@wpmobilebot

wpmobilebot commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

📊 Performance Test Results

Comparing dc8b98c vs trunk

app-size

Metric trunk dc8b98c Diff Change
App Size (Mac) 1402.88 MB 1402.78 MB 0.10 MB ⚪ 0.0%

site-editor

Metric trunk dc8b98c Diff Change
load 1062 ms 1036 ms 26 ms ⚪ 0.0%

site-startup

Metric trunk dc8b98c Diff Change
siteCreation 7532 ms 7536 ms +4 ms ⚪ 0.0%
siteStartup 2872 ms 2865 ms 7 ms ⚪ 0.0%

Results are median values from multiple test runs.

Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff)

@bcotrim bcotrim left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic makes sense, and the testing instructions work as described.

I added 2 issues flagged by Claude that I couldn't quite verify, take it as non-blockers.

Comment thread apps/cli/lib/symlinks.ts Outdated
Comment on lines +30 to +32
return entryPath
.split( path.sep )
.some( ( segment ) => IGNORED_SCAN_DIRECTORY_NAMES.has( segment ) );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

split( path.sep ) never matches on Windows — chokidar normalizes to forward slashes before calling ignored (chokidar/index.js:44-48). Verified on Win11: it receives node_modules and returns false, where trunk's regex returned true. So the watcher descends into the link farms it means to skip.

Suggested change
return entryPath
.split( path.sep )
.some( ( segment ) => IGNORED_SCAN_DIRECTORY_NAMES.has( segment ) );
return entryPath
.split( /[\\/]/ )
.some( ( segment ) => IGNORED_SCAN_DIRECTORY_NAMES.has( segment ) );

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch 👍

Comment thread apps/cli/php-server-child.ts Outdated
}

function isCoveredByOpenBasedirAllowlist( target: string ): boolean {
return getEffectiveOpenBasedirAllowlist().some( ( entry ) => containsPath( entry, target ) );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Runs the full O(N²) dropCoveredPaths() per symlink event, and arePathsEqual does 2 statSync per comparison — 50 entries ≈ 19ms / 22k sync stat calls, on the same loop as the proxy (:460). Trunk was an O(1) Set.has.

The dedup is redundant here: if a dropped entry covers the target, whatever displaced it does too.

Suggested change
return getEffectiveOpenBasedirAllowlist().some( ( entry ) => containsPath( entry, target ) );
return [ ...staticOpenBasedirAllowlist, ...symlinkOpenBasedirAllowlist ].some( ( entry ) =>
containsPath( entry, target )
);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good feedback 👍

@fredrikekelund
fredrikekelund enabled auto-merge (squash) August 6, 2026 05:29
@fredrikekelund
fredrikekelund merged commit 64569b9 into trunk Aug 6, 2026
13 checks passed
@fredrikekelund
fredrikekelund deleted the stu-2038-fix-enametoolong branch August 6, 2026 06:18
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.

3 participants