@@ -744,9 +744,9 @@ function normalizeWslHomePath(home: string): string | null {
744744 return normalized ;
745745}
746746
747- function toWslUncPath ( prefix : '\\\\wsl.localhost' | '\\\\wsl$' , distro : string , posixPath : string ) : string {
747+ function toWslUncPath ( distro : string , posixPath : string ) : string {
748748 const uncSuffix = posixPath . replace ( / \/ / g, '\\' ) ;
749- return `${ prefix } \\${ distro } ${ uncSuffix } ` ;
749+ return `\\\\wsl.localhost \\${ distro } ${ uncSuffix } ` ;
750750}
751751
752752function getWslExecutableCandidates ( ) : string [ ] {
@@ -762,16 +762,54 @@ function getWslExecutableCandidates(): string[] {
762762 return Array . from ( candidates ) ;
763763}
764764
765+ function looksLikeUtf16Le ( buffer : Buffer ) : boolean {
766+ const sampleSize = Math . min ( buffer . length , 512 ) ;
767+ if ( sampleSize < 2 ) {
768+ return false ;
769+ }
770+
771+ let pairs = 0 ;
772+ let nullsAtOddIndex = 0 ;
773+ for ( let i = 0 ; i + 1 < sampleSize ; i += 2 ) {
774+ pairs += 1 ;
775+ if ( buffer [ i + 1 ] === 0 ) {
776+ nullsAtOddIndex += 1 ;
777+ }
778+ }
779+
780+ return pairs > 0 && nullsAtOddIndex / pairs >= 0.3 ;
781+ }
782+
783+ function decodeWslOutput ( output : string | Buffer | undefined ) : string {
784+ if ( typeof output === 'string' ) {
785+ return output . replace ( / \0 / g, '' ) ;
786+ }
787+ if ( ! output || output . length === 0 ) {
788+ return '' ;
789+ }
790+
791+ const hasUtf16LeBom = output . length >= 2 && output [ 0 ] === 0xff && output [ 1 ] === 0xfe ;
792+ const decoded = hasUtf16LeBom || looksLikeUtf16Le ( output )
793+ ? output . toString ( 'utf16le' )
794+ : output . toString ( 'utf8' ) ;
795+ return decoded . replace ( / \0 / g, '' ) ;
796+ }
797+
765798async function runWsl ( args : string [ ] , timeout = 5000 ) : Promise < { stdout : string ; stderr : string } > {
766799 const candidates = getWslExecutableCandidates ( ) ;
767800 let lastError : unknown = null ;
768801
769802 for ( const executable of candidates ) {
770803 try {
771- const result = await execFileAsync ( executable , args , { timeout } ) ;
804+ const result = await execFileAsync ( executable , args , {
805+ timeout,
806+ windowsHide : true ,
807+ maxBuffer : 1024 * 1024 ,
808+ encoding : 'buffer' ,
809+ } ) ;
772810 return {
773- stdout : result . stdout ?? '' ,
774- stderr : result . stderr ?? '' ,
811+ stdout : decodeWslOutput ( result . stdout ) ,
812+ stderr : decodeWslOutput ( result . stderr ) ,
775813 } ;
776814 } catch ( error ) {
777815 lastError = error ;
@@ -781,24 +819,59 @@ async function runWsl(args: string[], timeout = 5000): Promise<{ stdout: string;
781819 throw lastError instanceof Error ? lastError : new Error ( 'Unable to execute wsl.exe' ) ;
782820}
783821
822+ function parseWslDistros ( stdout : string ) : string [ ] {
823+ const distros : string [ ] = [ ] ;
824+ const seen = new Set < string > ( ) ;
825+ const lines = stdout . split ( / \r ? \n / ) ;
826+
827+ for ( const rawLine of lines ) {
828+ let line = rawLine . replace ( / \0 / g, '' ) . trim ( ) ;
829+ if ( ! line ) {
830+ continue ;
831+ }
832+
833+ line = line . replace ( / ^ \* \s * / , '' ) . trim ( ) ;
834+ line = stripDefaultSuffix ( line ) ;
835+
836+ const lower = line . toLowerCase ( ) ;
837+ if (
838+ lower . startsWith ( 'windows subsystem for linux' ) ||
839+ lower . includes ( 'default version' ) ||
840+ lower . startsWith ( 'the following is a list' )
841+ ) {
842+ continue ;
843+ }
844+
845+ const key = line . toLowerCase ( ) ;
846+ if ( ! seen . has ( key ) ) {
847+ seen . add ( key ) ;
848+ distros . push ( line ) ;
849+ }
850+ }
851+
852+ return distros ;
853+ }
854+
784855async function listWslDistros ( ) : Promise < string [ ] > {
785- try {
786- const { stdout } = await runWsl ( [ '-l ' , '-q ' ] , 4000 ) ;
787- return stdout
788- . split ( / \r ? \n / )
789- . map ( ( line ) => line . trim ( ) )
790- . filter ( ( line ) => line . length > 0 ) ;
791- } catch {
792- // Fallback for environments where -q behavior is inconsistent.
793- const { stdout } = await runWsl ( [ '-l' ] , 4000 ) ;
794- return stdout
795- . split ( / \r ? \n / )
796- . map ( ( line ) => line . trim ( ) )
797- . filter ( ( line ) => line . length > 0 )
798- . filter ( ( line ) => ! line . toLowerCase ( ) . startsWith ( 'windows subsystem for linux' ) )
799- . filter ( ( line ) => ! line . toLowerCase ( ) . includes ( 'default version' ) )
800- . map ( stripDefaultSuffix ) ;
856+ const commands : string [ ] [ ] = [
857+ [ '--list ' , '--quiet ' ] ,
858+ [ '-l' , '-q' ] ,
859+ [ '-l' ] ,
860+ ] ;
861+
862+ for ( const command of commands ) {
863+ try {
864+ const { stdout } = await runWsl ( command , 4000 ) ;
865+ const parsed = parseWslDistros ( stdout ) ;
866+ if ( parsed . length > 0 ) {
867+ return parsed ;
868+ }
869+ } catch {
870+ // Try the next command variant.
871+ }
801872 }
873+
874+ return [ ] ;
802875}
803876
804877function stripDefaultSuffix ( input : string ) : string {
@@ -841,50 +914,38 @@ async function handleFindWslClaudeRoots(
841914 const candidates : WslClaudeRootCandidate [ ] = [ ] ;
842915 const seen = new Set < string > ( ) ;
843916 for ( const distro of distros ) {
844- const homePath = await resolveWslHome ( distro ) ;
845- const homeCandidates = new Set < string > ( ) ;
846- if ( homePath ) {
847- homeCandidates . add ( homePath ) ;
848- }
849- if ( process . env . USERNAME ) {
850- homeCandidates . add ( `/home/${ process . env . USERNAME } ` ) ;
917+ const resolvedHomePath = await resolveWslHome ( distro ) ;
918+ const fallbackHomePath = process . env . USERNAME ? `/home/${ process . env . USERNAME } ` : null ;
919+ const normalizedHome =
920+ normalizeWslHomePath ( resolvedHomePath ?? '' ) ??
921+ ( fallbackHomePath ? normalizeWslHomePath ( fallbackHomePath ) : null ) ;
922+
923+ if ( ! normalizedHome ) {
924+ continue ;
851925 }
852- homeCandidates . add ( '/root' ) ;
853926
854- for ( const homeCandidate of homeCandidates ) {
855- const normalizedHome = normalizeWslHomePath ( homeCandidate ) ;
856- if ( ! normalizedHome ) {
857- continue ;
858- }
859- const claudePosixPath = path . posix . join ( normalizedHome , '.claude' ) ;
860-
861- const uncPaths = [
862- toWslUncPath ( '\\\\wsl.localhost' , distro , claudePosixPath ) ,
863- toWslUncPath ( '\\\\wsl$' , distro , claudePosixPath ) ,
864- ] ;
865-
866- for ( const claudeUncPath of uncPaths ) {
867- if ( seen . has ( claudeUncPath . toLowerCase ( ) ) ) {
868- continue ;
869- }
870- seen . add ( claudeUncPath . toLowerCase ( ) ) ;
871-
872- const projectsPath = path . join ( claudeUncPath , 'projects' ) ;
873- const hasProjectsDir = ( ( ) => {
874- try {
875- return fs . existsSync ( projectsPath ) && fs . statSync ( projectsPath ) . isDirectory ( ) ;
876- } catch {
877- return false ;
878- }
879- } ) ( ) ;
880-
881- candidates . push ( {
882- distro,
883- path : claudeUncPath ,
884- hasProjectsDir,
885- } ) ;
886- }
927+ const claudePosixPath = path . posix . join ( normalizedHome , '.claude' ) ;
928+ const claudeUncPath = toWslUncPath ( distro , claudePosixPath ) ;
929+ const key = claudeUncPath . toLowerCase ( ) ;
930+ if ( seen . has ( key ) ) {
931+ continue ;
887932 }
933+ seen . add ( key ) ;
934+
935+ const projectsPath = path . join ( claudeUncPath , 'projects' ) ;
936+ const hasProjectsDir = ( ( ) => {
937+ try {
938+ return fs . existsSync ( projectsPath ) && fs . statSync ( projectsPath ) . isDirectory ( ) ;
939+ } catch {
940+ return false ;
941+ }
942+ } ) ( ) ;
943+
944+ candidates . push ( {
945+ distro,
946+ path : claudeUncPath ,
947+ hasProjectsDir,
948+ } ) ;
888949 }
889950
890951 return { success : true , data : candidates } ;
0 commit comments