fix: resolve symlinked argv1 for Control UI asset detection#14637
Closed
aynorica wants to merge 2 commits intoopenclaw:mainfrom
Closed
fix: resolve symlinked argv1 for Control UI asset detection#14637aynorica wants to merge 2 commits intoopenclaw:mainfrom
aynorica wants to merge 2 commits intoopenclaw:mainfrom
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes Control UI asset detection for global installs when process.argv[1] points at a symlinked shim (common with nvm/fnm/n/Homebrew setups), ensuring the resolver can still locate the openclaw package root and dist/control-ui/index.html.
Changes:
- Resolve symlinks in
candidateDirsFromArgv1()viarealpathSync()to add the real entrypoint directory as a package-root candidate. - Extend the async Control UI index resolver to accept/propagate
moduleUrl(parity with existing sync resolution patterns). - Add Vitest coverage for symlinked
argv1scenarios (package root, control-ui root, async index path).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/infra/openclaw-root.ts |
Adds realpathSync-based candidate directory to support symlinked argv1 resolution. |
src/infra/control-ui-assets.ts |
Threads optional moduleUrl through async path resolution to improve robustness. |
src/infra/control-ui-assets.test.ts |
Adds tests validating symlinked argv1 resolution for root/index detection. |
40c484a to
a25137b
Compare
Member
|
Landed on main. Thanks @aynorica! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #4855 — Control UI assets not found on
npm install -g openclawwhen using symlink-based Node version managers (nvm, fnm, n, Homebrew/Linuxbrew).The root cause is that
process.argv[1]preserves the symlink path (e.g.~/.nvm/versions/node/v22/bin/openclaw) rather than resolving to the real file insidenode_modules/openclaw/. The directory walk-up from the symlink directory never findspackage.jsonwithname: "openclaw", so asset resolution returnsnull.This PR applies
fs.realpathSync()toargv1incandidateDirsFromArgv1()and propagatesmoduleUrlto the async resolver, covering all resolution paths.lobster-biscuit
Repro Steps
npm install -g openclawopenclaw gatewayhttp://127.0.0.1:18789/→ "Missing Control UI assets"Root Cause
candidateDirsFromArgv1()insrc/infra/openclaw-root.tscallspath.resolve(argv1)which does not follow symlinks. For symlink-based version managers:argv1=~/.nvm/versions/node/v22/bin/openclaw(symlink)path.dirname(argv1)= thebin/directory, not the package rootpackage.jsonwithname: "openclaw"Node.js does not resolve symlinks for
argv[1](empirically verified), butfs.realpathSync()does.Behavior Changes
candidateDirsFromArgv1()now also triesfs.realpathSync(argv1)and adds the resolved directory as a candidate alongside the original. IfrealpathSyncthrows (path doesn't exist), the original candidates are preserved — no change in behavior for non-symlink installs.resolveControlUiDistIndexPath()now accepts an optionalmoduleUrlparameter (backward-compatible — still accepts a bare string) and forwards it toresolveOpenClawPackageRoot(), matching the sync resolver's existing pattern.resolveControlUiDistIndexHealth()forwardsmoduleUrlwhen available.No behavior change for standard npm global installs, local installs, or packaged app installs — these paths are unaffected since
realpathSyncreturns the same path when no symlinks exist.Codebase and GitHub Search
realpathSyncusage — found 8 call sites in the codebase, including one already inresolveControlUiRootSync()forexecPathresolution (same pattern)import.meta.urlalways resolves through symlinks (Node.js behavior)Tests
3 new test cases added to
src/infra/control-ui-assets.test.ts:resolves package root when argv1 is a symlinkresolveOpenClawPackageRoot()follows symlinks viarealpathSyncresolves control-ui root when argv1 is a symlinkresolveControlUiRootSync()findsdist/control-ui/through symlinked argv1resolves dist index path when argv1 is a symlink (async)resolveControlUiDistIndexPath()returns correctindex.htmlthrough symlinked argv1All 17 tests pass (14 existing + 3 new). Zero regressions.
Symlink tests use
fs.symlinkSyncwith relative targets, matching real nvm/fnm behavior. Tests skip gracefully if symlink creation fails (Windows CI without elevated privileges).Manual Testing
Prerequisites
Steps
bin/openclaw→ symlink →lib/node_modules/openclaw/openclaw.mjsprocess.argv[1]returns the symlink path (not resolved)fs.realpathSync(argv1)resolves to the real package pathnpx vitest run src/infra/control-ui-assets.test.ts— 17/17 passnpx tsgo --noEmit— cleannpx oxlint+npx oxfmt --checkon changed files — cleanEvidence
Files changed (3):
src/infra/openclaw-root.tsrealpathSyncfallback incandidateDirsFromArgv1()(+12 lines)src/infra/control-ui-assets.tsmoduleUrlin async resolver, forward toresolveOpenClawPackageRoot(+10 lines, -3 lines)src/infra/control-ui-assets.test.tsSign-Off
Greptile Overview
Greptile Summary
This PR fixes Control UI asset discovery for global installs where the CLI entrypoint (
process.argv[1]) is a symlink (common with nvm/fnm/n/Homebrew). The core change is insrc/infra/openclaw-root.ts, wherecandidateDirsFromArgv1()now adds afs.realpathSync()-resolved directory as an additional candidate so the package-root walk can reach the realnode_modules/openclawlocation.To ensure the async Control UI resolver benefits from the same root-discovery inputs as the sync path,
resolveControlUiDistIndexPath()now accepts either a string argv1 or an{ argv1, moduleUrl }options object and forwardsmoduleUrlintoresolveOpenClawPackageRoot(). Tests insrc/infra/control-ui-assets.test.tsadd coverage for resolving through a symlinked argv1, matching the reported global-install scenario.Confidence Score: 5/5
(2/5) Greptile learns from your feedback when you react with thumbs up/down!