@@ -56,15 +56,10 @@ param(
5656 [Parameter (Mandatory = $true )]
5757 [string ] $OpenClawInstallDir ,
5858 # Must be a DIRECT CHILD of a drive root (e.g. C:\openshell-openclaw, not
59- # C:\work\openshell-openclaw). Node's CommonJS module resolver calls
60- # fs.realpathSync while resolving the entry script, which lstat()s every
61- # parent directory up the chain -- including ones OUTSIDE share_dir. The
62- # AppContainer only grants share_dir itself, so an intermediate parent like
63- # C:\work fails with EPERM (confirmed empirically: this exact test failed
64- # with "EPERM: operation not permitted, lstat 'C:\work'" until the share
65- # dir was moved to the drive root). The drive root itself (C:\) apparently
66- # doesn't need an explicit grant to lstat successfully, so a one-level path
67- # sidesteps the problem entirely.
59+ # C:\work\openshell-openclaw). The staged Node invocation below uses
60+ # --preserve-symlinks-main so Node does not realpath the main module before
61+ # our capture script starts; keeping a one-level path also avoids exposing
62+ # or depending on unrelated intermediate directories.
6863 [string ] $ShareDir = " C:\openshell-openclaw" ,
6964 [int ] $TargetPort = 18889 ,
7065 [int ] $ForwardLocalPort = 28889 ,
@@ -217,6 +212,7 @@ $fwdProc = $null
217212$fwdLog = Join-Path $resultDir " forward.log"
218213$fwdErrLog = Join-Path $resultDir " forward.err.log"
219214$passed = $false
215+ $failureMessage = " "
220216$healthJson = $null
221217$selfProbeOutcome = " not-recorded"
222218$selfProbeResponseBytes = 0
@@ -426,6 +422,7 @@ try {
426422 mxc = @ {
427423 command = @ (
428424 " $shareDirToml /node.exe" ,
425+ " --preserve-symlinks-main" ,
429426 " $shareDirToml /openclaw-capture.mjs" ,
430427 " gateway" , " run" , " --dev" , " --allow-unconfigured" ,
431428 " --auth" , " token" , " --bind" , " loopback" , " --port" , " $TargetPort "
@@ -456,43 +453,23 @@ try {
456453 " --env" , " NEMOCLAW_MXC_EGRESS_LOOPBACK_PORT=29999" ,
457454 " --no-tty" , " --" , " exit"
458455 )
456+ # Windows PowerShell 5.1 wraps native stderr as ErrorRecord objects. Keep
457+ # warnings in the captured diagnostic without letting them terminate the
458+ # command before its real exit code and output are collected.
459+ $createPrevEAP = $ErrorActionPreference
460+ $ErrorActionPreference = " Continue"
459461 try { $createOut = & $cli @createArgs 2>&1 ; $createCode = $LASTEXITCODE }
460462 catch { $createOut = $_.Exception.Message ; $createCode = 1 }
463+ finally { $ErrorActionPreference = $createPrevEAP }
461464 $createBenign = Show-SandboxCreate $createOut $SandboxName
462465 if ($createCode -ne 0 -and -not $createBenign ) {
463466 throw " sandbox create '$SandboxName ' failed (exit $createCode ): $ ( $createOut | Out-String ) "
464467 }
465468
466- # 9. Wait for OpenClaw's gateway to report ready, by tailing the gateway's
467- # own log for the line it prints on successful startup (forwarded from
468- # the sandbox's stdout via "wxc-exec stdout:"). Generous timeout: Node
469- # startup + AppContainer/UAC elevation + plugin warmup can take a while
470- # on a cold run.
471- Step " Wait for OpenClaw gateway readiness"
472- $readyDeadline = (Get-Date ).AddSeconds(90 )
473- $openclawReady = $false
474- while ((Get-Date ) -lt $readyDeadline ) {
475- if (Test-Path $gwLog ) {
476- # `.*` (not `\s+`) between "[gateway]" and "ready": OpenClaw wraps its
477- # log lines in ANSI color codes whenever it inherits enough of the host
478- # env to detect a color-capable terminal -- which happens with
479- # mxc-openclaw-localnet.toml (-UseLocalNetwork), since that config
480- # doesn't set pc_minimal_env and so inherits the full host env, unlike
481- # mxc-openclaw-gateway.toml's curated minimal set. A strict \s+ match
482- # missed this entirely and timed out waiting for a line that had
483- # already printed. Those codes render in this log as LITERAL backslash-
484- # escaped text (e.g. "...\x1b[36mready..."), not real ESC bytes -- so
485- # "m" from "36m" directly abuts "ready" with no word boundary, which is
486- # why a \bready\b tightening (tried once) also failed to match; a bare
487- # substring check is what actually works here. The resulting collision
488- # risk with "already" is theoretical -- no such line has been observed
489- # on this "[gateway]"-tagged forwarded-stdout path in practice.
490- if (Select-String - Path $gwLog - Pattern ' \[gateway\].*ready' - Quiet - ErrorAction SilentlyContinue) { $openclawReady = $true ; break }
491- }
492- Start-Sleep - Seconds 2
493- }
494- if (-not $openclawReady ) { throw " OpenClaw did not report ready within 90s (see gateway.log in the results bundle)" }
495- Ok " OpenClaw gateway ready"
469+ # `sandbox create` does not return success until the MXC driver receives the
470+ # relay's target_ready event. Trust that lifecycle result directly instead
471+ # of racing a second, text-based poll against gateway.log.
472+ Ok " OpenClaw gateway ready (sandbox target_ready received)"
496473
497474 # 10. openshell forward service: opens a fresh, on-demand relay for this
498475 # one call and bridges TargetPort (inside the sandbox) to
@@ -610,26 +587,55 @@ try {
610587 }
611588}
612589catch {
613- Bad $_.Exception.Message
590+ $failureMessage = $_.Exception.Message
591+ Bad $failureMessage
614592}
615593finally {
616594 # Stop the forward before the sandbox so its relay tears down cleanly.
617595 if ($fwdProc -and -not $fwdProc.HasExited ) {
618596 try { Stop-Process - Id $fwdProc.Id - Force - ErrorAction SilentlyContinue } catch {}
619597 }
598+ if ($fwdProc ) {
599+ try {
600+ if (-not $fwdProc.WaitForExit (5000 )) {
601+ throw " forward process did not exit within 5s"
602+ }
603+ } catch {
604+ Info " forward teardown: $ ( $_.Exception.Message ) "
605+ if ($passed ) { $passed = $false ; $failureMessage = $_.Exception.Message }
606+ }
607+ }
620608
621609 # Tear down the sandbox while the gateway is still up (delete needs it).
622610 if ($cli -and $SandboxName ) {
623- try { & $cli sandbox delete $SandboxName 2>&1 | Out-Null }
624- catch { Info " sandbox teardown '$SandboxName ': $ ( $_.Exception.Message ) (continuing)" }
611+ $deleteCode = 1
612+ $deleteOut = @ ()
613+ $deletePrevEAP = $ErrorActionPreference
614+ $ErrorActionPreference = " Continue"
615+ try { $deleteOut = & $cli sandbox delete $SandboxName 2>&1 ; $deleteCode = $LASTEXITCODE }
616+ catch { $deleteOut = $_.Exception.Message }
617+ finally { $ErrorActionPreference = $deletePrevEAP }
618+ if ($deleteCode -ne 0 ) {
619+ $cleanupFailure = " sandbox teardown '$SandboxName ' failed (exit $deleteCode ): $ ( $deleteOut | Out-String ) "
620+ Info $cleanupFailure
621+ if ($passed ) { $passed = $false ; $failureMessage = $cleanupFailure }
622+ }
625623 }
626624
627625 if ($KeepRunning -and $gw -and -not $gw.HasExited ) {
628626 Info " leaving gateway pid $ ( $gw.Id ) running (-KeepRunning); stop with: Stop-Process -Id $ ( $gw.Id ) -Force"
629627 } elseif ($gw -and -not $gw.HasExited ) {
630628 Step " Cleanup" ; Stop-Process - Id $gw.Id - Force - ErrorAction SilentlyContinue
631- try { $gw.WaitForExit (5000 ) | Out-Null } catch {}
632- Info " stopped gateway pid $ ( $gw.Id ) "
629+ try {
630+ if (-not $gw.WaitForExit (5000 )) {
631+ throw " gateway process did not exit within 5s"
632+ }
633+ Info " stopped gateway pid $ ( $gw.Id ) "
634+ } catch {
635+ $cleanupFailure = " gateway teardown failed: $ ( $_.Exception.Message ) "
636+ Info $cleanupFailure
637+ if ($passed ) { $passed = $false ; $failureMessage = $cleanupFailure }
638+ }
633639 }
634640
635641 Step " Gateway log (tail)"
@@ -663,12 +669,17 @@ finally {
663669
664670 Step " RESULT"
665671 $verdict = if ($passed ) { " PASS" } else { " FAIL" }
672+ if (-not $passed -and [string ]::IsNullOrWhiteSpace($failureMessage )) {
673+ $failureMessage = " one or more qualification assertions failed"
674+ }
675+ $failureSummary = ($failureMessage -replace ' \s+' , ' ' ).Trim()
666676 $summary = @"
667677OpenShell MXC OpenClaw + dynamic forward test
668678=====================================================================
669679timestamp : $stamp
670680machine : $env: COMPUTERNAME
671681verdict : $verdict
682+ failure : $failureSummary
672683sandbox : $SandboxName
673684backend : $Backend
674685config : $tomlName
0 commit comments