fix: improve WSL user home path resolution and enhance process renaming on Windows
- Updated fallback home path logic to correctly handle 'root' user case in WSL. - Refactored process renaming in TeamAgentToolsInstaller to include retry logic on Windows for better error handling during concurrent operations. - Enhanced user information retrieval in TeamProvisioningService to handle potential errors gracefully. - Clarified documentation in processKill utility regarding platform-specific behavior.
This commit is contained in:
parent
91933c2ee3
commit
0099be4f23
5 changed files with 37 additions and 7 deletions
|
|
@ -981,7 +981,11 @@ async function handleFindWslClaudeRoots(
|
||||||
// Fallback: query the default WSL user, then try Windows USERNAME
|
// Fallback: query the default WSL user, then try Windows USERNAME
|
||||||
const wslUser = await resolveWslDefaultUser(distro);
|
const wslUser = await resolveWslDefaultUser(distro);
|
||||||
const fallbackUser = wslUser || process.env.USERNAME;
|
const fallbackUser = wslUser || process.env.USERNAME;
|
||||||
const fallbackHomePath = fallbackUser ? `/home/${fallbackUser}` : null;
|
const fallbackHomePath = fallbackUser
|
||||||
|
? fallbackUser === 'root'
|
||||||
|
? '/root'
|
||||||
|
: `/home/${fallbackUser}`
|
||||||
|
: null;
|
||||||
const normalizedHome =
|
const normalizedHome =
|
||||||
normalizeWslHomePath(resolvedHomePath ?? '') ??
|
normalizeWslHomePath(resolvedHomePath ?? '') ??
|
||||||
(fallbackHomePath ? normalizeWslHomePath(fallbackHomePath) : null);
|
(fallbackHomePath ? normalizeWslHomePath(fallbackHomePath) : null);
|
||||||
|
|
|
||||||
|
|
@ -97,9 +97,8 @@ async function collectNvmWindowsCandidates(): Promise<string[]> {
|
||||||
|
|
||||||
const exts = getWindowsExecutableExtensions();
|
const exts = getWindowsExecutableExtensions();
|
||||||
return versions
|
return versions
|
||||||
.flatMap((version) => exts.map((ext) => path.join(nvmRoot, version, `claude${ext}`)))
|
.sort((a, b) => b.localeCompare(a, undefined, { numeric: true, sensitivity: 'base' }))
|
||||||
.sort((a, b) => a.localeCompare(b))
|
.flatMap((version) => exts.map((ext) => path.join(nvmRoot, version, `claude${ext}`)));
|
||||||
.reverse();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveFromPathEnv(binaryName: string): Promise<string | null> {
|
async function resolveFromPathEnv(binaryName: string): Promise<string | null> {
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,26 @@ function atomicWrite(filePath, data) {
|
||||||
'.' +
|
'.' +
|
||||||
String(Math.random().toString(16).slice(2));
|
String(Math.random().toString(16).slice(2));
|
||||||
fs.writeFileSync(tmp, data, 'utf8');
|
fs.writeFileSync(tmp, data, 'utf8');
|
||||||
fs.renameSync(tmp, filePath);
|
// On Windows, fs.renameSync can throw EPERM/EACCES when another process
|
||||||
|
// is concurrently renaming to the same target. Retry with backoff.
|
||||||
|
const maxRetries = process.platform === 'win32' ? 5 : 1;
|
||||||
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
||||||
|
try {
|
||||||
|
fs.renameSync(tmp, filePath);
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
if (attempt < maxRetries - 1 && e && (e.code === 'EPERM' || e.code === 'EACCES')) {
|
||||||
|
// Busy wait — small random delay to reduce contention
|
||||||
|
const ms = Math.floor(Math.random() * 50) + 10;
|
||||||
|
const end = Date.now() + ms;
|
||||||
|
while (Date.now() < end) { /* spin */ }
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Clean up temp file on final failure
|
||||||
|
try { fs.unlinkSync(tmp); } catch { /* ignore */ }
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeStatus(value) {
|
function normalizeStatus(value) {
|
||||||
|
|
|
||||||
|
|
@ -2702,11 +2702,18 @@ export class TeamProvisioningService {
|
||||||
const electronHome = getHomeDir();
|
const electronHome = getHomeDir();
|
||||||
const isWindows = process.platform === 'win32';
|
const isWindows = process.platform === 'win32';
|
||||||
const home = shellEnv.HOME?.trim() || electronHome;
|
const home = shellEnv.HOME?.trim() || electronHome;
|
||||||
|
let osUsername = '';
|
||||||
|
try {
|
||||||
|
osUsername = os.userInfo().username;
|
||||||
|
} catch {
|
||||||
|
// os.userInfo() can throw SystemError in restricted environments (no passwd entry, Docker, etc.)
|
||||||
|
}
|
||||||
const user =
|
const user =
|
||||||
shellEnv.USER?.trim() ||
|
shellEnv.USER?.trim() ||
|
||||||
process.env.USER?.trim() ||
|
process.env.USER?.trim() ||
|
||||||
process.env.USERNAME?.trim() ||
|
process.env.USERNAME?.trim() ||
|
||||||
os.userInfo().username;
|
osUsername ||
|
||||||
|
'unknown';
|
||||||
|
|
||||||
// Shell: on Windows there is no SHELL env var; use COMSPEC (cmd.exe / powershell).
|
// Shell: on Windows there is no SHELL env var; use COMSPEC (cmd.exe / powershell).
|
||||||
// On Unix, prefer the user's login shell from env or fall back to /bin/zsh.
|
// On Unix, prefer the user's login shell from env or fall back to /bin/zsh.
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ import path from 'path';
|
||||||
* it calls TerminateProcess() which is equivalent to SIGKILL (immediate, ungraceful).
|
* it calls TerminateProcess() which is equivalent to SIGKILL (immediate, ungraceful).
|
||||||
* - `taskkill /T` also kills child processes, preventing orphaned process trees.
|
* - `taskkill /T` also kills child processes, preventing orphaned process trees.
|
||||||
*
|
*
|
||||||
* Throws if the process cannot be killed (except ESRCH — process already dead).
|
* On Unix, throws if the process cannot be killed (except ESRCH — process already dead).
|
||||||
|
* On Windows, taskkill is best-effort (async fire-and-forget) to match killProcessTree() semantics.
|
||||||
*/
|
*/
|
||||||
export function killProcessByPid(pid: number): void {
|
export function killProcessByPid(pid: number): void {
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue