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:
iliya 2026-03-02 23:10:44 +02:00
parent 91933c2ee3
commit 0099be4f23
5 changed files with 37 additions and 7 deletions

View file

@ -981,7 +981,11 @@ async function handleFindWslClaudeRoots(
// Fallback: query the default WSL user, then try Windows USERNAME
const wslUser = await resolveWslDefaultUser(distro);
const fallbackUser = wslUser || process.env.USERNAME;
const fallbackHomePath = fallbackUser ? `/home/${fallbackUser}` : null;
const fallbackHomePath = fallbackUser
? fallbackUser === 'root'
? '/root'
: `/home/${fallbackUser}`
: null;
const normalizedHome =
normalizeWslHomePath(resolvedHomePath ?? '') ??
(fallbackHomePath ? normalizeWslHomePath(fallbackHomePath) : null);

View file

@ -97,9 +97,8 @@ async function collectNvmWindowsCandidates(): Promise<string[]> {
const exts = getWindowsExecutableExtensions();
return versions
.flatMap((version) => exts.map((ext) => path.join(nvmRoot, version, `claude${ext}`)))
.sort((a, b) => a.localeCompare(b))
.reverse();
.sort((a, b) => b.localeCompare(a, undefined, { numeric: true, sensitivity: 'base' }))
.flatMap((version) => exts.map((ext) => path.join(nvmRoot, version, `claude${ext}`)));
}
async function resolveFromPathEnv(binaryName: string): Promise<string | null> {

View file

@ -153,7 +153,26 @@ function atomicWrite(filePath, data) {
'.' +
String(Math.random().toString(16).slice(2));
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) {

View file

@ -2702,11 +2702,18 @@ export class TeamProvisioningService {
const electronHome = getHomeDir();
const isWindows = process.platform === 'win32';
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 =
shellEnv.USER?.trim() ||
process.env.USER?.trim() ||
process.env.USERNAME?.trim() ||
os.userInfo().username;
osUsername ||
'unknown';
// 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.

View file

@ -10,7 +10,8 @@ import path from 'path';
* it calls TerminateProcess() which is equivalent to SIGKILL (immediate, ungraceful).
* - `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 {
if (process.platform === 'win32') {