fix(windows): resolve daemon startup failures and Git Bash compatibility (#582)
* fix(windows): resolve daemon startup failures and Git Bash compatibility Three root causes behind 27 open Windows issues: 1. Path::canonicalize() returns \\?\ prefixed paths on Windows that Node.js cannot parse, preventing daemon startup. Strip the prefix before passing to Node. (fixes #522, #390, #56, #25, #37, #89) 2. Git Bash/MSYS2 translates Unix-style paths and resolves node to a shell wrapper script. Use node.exe explicitly and set MSYS_NO_PATHCONV/MSYS2_ARG_CONV_EXCL to prevent argument mangling. (fixes #148, #108, #171) 3. postinstall fixWindowsShims() hardcoded x64 arch and did not verify the native binary exists before rewriting shims. Now detects arch dynamically and validates the binary path. (fixes #262) Also: - Error messages now show TCP port on Windows instead of Unix socket path - Windows CI expanded to test full daemon lifecycle (open, snapshot, close) * fix(windows): strip \\?\ prefix in auth-cli path (fixes #579) Same canonicalize() issue as the daemon spawn path, but in run_auth_cli() which passes the script path to Node.js.
This commit is contained in:
@@ -173,6 +173,23 @@ jobs:
|
||||
}
|
||||
shell: pwsh
|
||||
|
||||
- name: Test daemon lifecycle (open, snapshot, close)
|
||||
run: |
|
||||
$env:PATH = "$pwd\bin;$env:PATH"
|
||||
Write-Host "--- Opening page ---"
|
||||
bin/agent-browser-win32-x64.exe open https://example.com
|
||||
if ($LASTEXITCODE -ne 0) { Write-Error "open failed"; exit 1 }
|
||||
Write-Host "--- Taking snapshot ---"
|
||||
$snapshot = bin/agent-browser-win32-x64.exe snapshot
|
||||
if ($LASTEXITCODE -ne 0) { Write-Error "snapshot failed"; exit 1 }
|
||||
Write-Host $snapshot
|
||||
Write-Host "--- Closing browser ---"
|
||||
bin/agent-browser-win32-x64.exe close
|
||||
if ($LASTEXITCODE -ne 0) { Write-Error "close failed"; exit 1 }
|
||||
Write-Host "--- Windows daemon lifecycle test passed ---"
|
||||
shell: pwsh
|
||||
timeout-minutes: 5
|
||||
|
||||
serverless-chromium:
|
||||
name: Serverless Chromium (@sparticuz/chromium)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
+30
-8
@@ -355,6 +355,17 @@ pub fn ensure_daemon(
|
||||
let exe_path = env::current_exe().map_err(|e| e.to_string())?;
|
||||
// Canonicalize to resolve symlinks (e.g., npm global bin symlink -> actual binary)
|
||||
let exe_path = exe_path.canonicalize().unwrap_or(exe_path);
|
||||
// On Windows, canonicalize() returns \\?\ prefixed extended-length paths.
|
||||
// Node.js cannot handle these, so strip the prefix.
|
||||
#[cfg(windows)]
|
||||
let exe_path = {
|
||||
let p = exe_path.to_string_lossy();
|
||||
if let Some(stripped) = p.strip_prefix(r"\\?\") {
|
||||
PathBuf::from(stripped)
|
||||
} else {
|
||||
exe_path
|
||||
}
|
||||
};
|
||||
let exe_dir = exe_path.parent().unwrap();
|
||||
|
||||
let mut daemon_paths = vec![
|
||||
@@ -404,10 +415,11 @@ pub fn ensure_daemon(
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
|
||||
// On Windows, call node directly. Command::new handles PATH resolution (node.exe or node.cmd)
|
||||
// and automatically quotes arguments containing spaces.
|
||||
let mut cmd = Command::new("node");
|
||||
cmd.arg(daemon_path);
|
||||
// Use node.exe explicitly to avoid Git Bash/MSYS2 shell wrapper resolution
|
||||
let mut cmd = Command::new("node.exe");
|
||||
cmd.arg(daemon_path)
|
||||
.env("MSYS_NO_PATHCONV", "1")
|
||||
.env("MSYS2_ARG_CONV_EXCL", "*");
|
||||
apply_daemon_env(&mut cmd, session, opts);
|
||||
|
||||
// CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS
|
||||
@@ -431,10 +443,20 @@ pub fn ensure_daemon(
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
|
||||
Err(format!(
|
||||
"Daemon failed to start (socket: {})",
|
||||
get_socket_dir().join(format!("{}.sock", session)).display()
|
||||
))
|
||||
#[cfg(unix)]
|
||||
let endpoint_info = format!(
|
||||
"socket: {}",
|
||||
get_socket_dir()
|
||||
.join(format!("{}.sock", session))
|
||||
.display()
|
||||
);
|
||||
#[cfg(windows)]
|
||||
let endpoint_info = format!(
|
||||
"port: 127.0.0.1:{}",
|
||||
get_port_for_session(session)
|
||||
);
|
||||
|
||||
Err(format!("Daemon failed to start ({})", endpoint_info))
|
||||
}
|
||||
|
||||
fn connect(session: &str) -> Result<Connection, String> {
|
||||
|
||||
@@ -31,6 +31,15 @@ use std::process::Command as ProcessCommand;
|
||||
fn run_auth_cli(cmd: &serde_json::Value, json_mode: bool) -> ! {
|
||||
let exe_path = env::current_exe().unwrap_or_default();
|
||||
let exe_path = exe_path.canonicalize().unwrap_or(exe_path);
|
||||
#[cfg(windows)]
|
||||
let exe_path = {
|
||||
let p = exe_path.to_string_lossy();
|
||||
if let Some(stripped) = p.strip_prefix(r"\\?\") {
|
||||
PathBuf::from(stripped)
|
||||
} else {
|
||||
exe_path
|
||||
}
|
||||
};
|
||||
let exe_dir = exe_path.parent().unwrap_or(std::path::Path::new("."));
|
||||
|
||||
let mut script_paths = vec![
|
||||
@@ -233,6 +242,13 @@ fn main() {
|
||||
libc::signal(libc::SIGPIPE, libc::SIG_DFL);
|
||||
}
|
||||
|
||||
// Prevent MSYS/Git Bash path translation from mangling arguments
|
||||
#[cfg(windows)]
|
||||
{
|
||||
env::set_var("MSYS_NO_PATHCONV", "1");
|
||||
env::set_var("MSYS2_ARG_CONV_EXCL", "*");
|
||||
}
|
||||
|
||||
let args: Vec<String> = env::args().skip(1).collect();
|
||||
let flags = parse_flags(&args);
|
||||
let clean = clean_args(&args);
|
||||
|
||||
+14
-18
@@ -187,46 +187,42 @@ async function fixUnixSymlink() {
|
||||
* We overwrite them to invoke the native .exe directly.
|
||||
*/
|
||||
async function fixWindowsShims() {
|
||||
// Check if this is a global install by looking for npm's global prefix
|
||||
let npmBinDir;
|
||||
try {
|
||||
npmBinDir = execSync('npm prefix -g', { encoding: 'utf8' }).trim();
|
||||
} catch {
|
||||
return; // Not a global install or npm not available
|
||||
return;
|
||||
}
|
||||
|
||||
// The shims are in the npm prefix directory (not prefix/bin on Windows)
|
||||
const cmdShim = join(npmBinDir, 'agent-browser.cmd');
|
||||
const ps1Shim = join(npmBinDir, 'agent-browser.ps1');
|
||||
|
||||
// Only fix if shims exist (indicates global install)
|
||||
// Shims may not exist yet during postinstall (npm creates them after
|
||||
// lifecycle scripts). If missing, fall back: the JS wrapper at
|
||||
// bin/agent-browser.js handles Windows correctly via child_process.spawn.
|
||||
if (!existsSync(cmdShim)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Path to native binary relative to npm prefix
|
||||
const relativeBinaryPath = 'node_modules\\agent-browser\\bin\\agent-browser-win32-x64.exe';
|
||||
// Detect architecture so ARM64 Windows is handled correctly
|
||||
const cpuArch = arch() === 'arm64' ? 'arm64' : 'x64';
|
||||
const relativeBinaryPath = `node_modules\\agent-browser\\bin\\agent-browser-win32-${cpuArch}.exe`;
|
||||
const absoluteBinaryPath = join(npmBinDir, relativeBinaryPath);
|
||||
|
||||
// Only rewrite shims if the native binary actually exists
|
||||
if (!existsSync(absoluteBinaryPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Overwrite .cmd shim
|
||||
const cmdContent = `@ECHO off\r\n"%~dp0${relativeBinaryPath}" %*\r\n`;
|
||||
writeFileSync(cmdShim, cmdContent);
|
||||
|
||||
// Overwrite .ps1 shim
|
||||
const ps1Content = `#!/usr/bin/env pwsh
|
||||
$basedir = Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||
$exe = ""
|
||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||
$exe = ".exe"
|
||||
}
|
||||
& "$basedir/${relativeBinaryPath.replace(/\\/g, '/')}" $args
|
||||
exit $LASTEXITCODE
|
||||
`;
|
||||
const ps1Content = `#!/usr/bin/env pwsh\r\n$basedir = Split-Path $MyInvocation.MyCommand.Definition -Parent\r\n& "$basedir\\${relativeBinaryPath}" $args\r\nexit $LASTEXITCODE\r\n`;
|
||||
writeFileSync(ps1Shim, ps1Content);
|
||||
|
||||
console.log('✓ Optimized: shims point to native binary (zero overhead)');
|
||||
} catch (err) {
|
||||
// Permission error or other issue - not critical, JS wrapper still works
|
||||
console.log(`⚠ Could not optimize shims: ${err.message}`);
|
||||
console.log(' CLI will work via Node.js wrapper (slightly slower startup)');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user