docs(console): 把 verbatim 路径的证据精确到「脚本参数」这一维
真机二维四格实验(ProcessStartInfo,精确对应 Command::new(prog).arg(script)): G1 plain-prog + plain-script -> exit 0,正常 G2 plain-prog + VERBATIM-script -> exit 1,EISDIR lstat 'C:' G3 VERBATIM-prog + plain-script -> exit 0,正常 G4 VERBATIM-prog + VERBATIM -> exit 1,EISDIR lstat 'C:' 结论:炸的是「脚本参数」带 verbatim,不是「可执行文件」带 verbatim。 旁证:真机上被控端与 sidecar 的 program 都带 \\?\ 却长期存活。 因此改准注释口径:净化 script 是关键修复,净化 node 属防御性 (守住「凡交给外部进程的路径都干净」这条单一纪律,不靠记位置)。 行为零变化(纯注释 + CHANGELOG)。
This commit is contained in:
1 parent
eec76ed91e
commit
56f64e1cc1
3 files changed
+49
-16
No files matched your search
+18
-6
@@ -419,13 +419,25 @@ macOS 上同样两条却是绿的 —— 典型的「只在 Windows 暴露」。
|
||||
改为**立刻**报「检查更新失败:**sidecar 已退出(stdout 关闭),请重试**」——
|
||||
说明被拉起的 sidecar **在请求进行中真的退出了**,只是这次被立刻发现。本节是它的真根因。
|
||||
|
||||
**真机 A/B(决定性的一条)**:Windows 11 ARM64;同一个 `node.exe`、同一份 `sidecar.mjs`,
|
||||
**只换脚本参数的写法**:
|
||||
**真机 A/B(决定性的一条)**:Windows 11 ARM64;同一个 `node.exe`、同一份脚本,
|
||||
用 `ProcessStartInfo` 下发(精确对应 Rust `Command::new(prog).arg(script)` 的形态),
|
||||
**二维四格**各跑一遍 —— 要回答的是「炸的是哪一个维度」:
|
||||
|
||||
| 脚本参数 | 结果 |
|
||||
|---|---|
|
||||
| `C:\…\sidecar.mjs` | 2.5 秒返回 `check_update` 结果,进程常驻、心跳持续 |
|
||||
| `\\?\C:\…\sidecar.mjs` | **0.47 秒即退出,exit 1**,**零 JS 执行** |
|
||||
| # | `program` | 脚本参数 | 结果 |
|
||||
|---|---|---|---|
|
||||
| G1 | `C:\…\node.exe` | `C:\…\mini.mjs` | exit 0,正常输出 ← **= 修复后的形态** |
|
||||
| G2 | `C:\…\node.exe` | `\\?\C:\…\mini.mjs` | **exit 1**,`EISDIR … lstat 'C:'` |
|
||||
| G3 | `\\?\C:\…\node.exe` | `C:\…\mini.mjs` | exit 0,正常输出 |
|
||||
| G4 | `\\?\C:\…\node.exe` | `\\?\C:\…\mini.mjs` | **exit 1**,`EISDIR … lstat 'C:'` |
|
||||
|
||||
**炸的是「脚本参数」带 verbatim,不是「可执行文件」带 verbatim** —— G3 证明 `program`
|
||||
位置带前缀 node 照常启动;真机上也有旁证:被控端与 sidecar 进程的 `program` 都带 `\\?\`
|
||||
却长期存活(`Get-CimInstance Win32_Process` 的 CommandLine 可直接看到)。
|
||||
修复方向由此确定:**净化脚本参数是关键**,净化 node 属防御性。
|
||||
|
||||
另有一次更贴近原症状的观测(同一台机器、直接用真 `sidecar.mjs`):
|
||||
普通路径 **2.5 秒**返回 `check_update` 结果、进程常驻且心跳持续;
|
||||
`\\?\` 路径 **0.47 秒**即退出、exit 1、**零 JS 执行** —— 连 sidecar 自带的启动日志一行都没写。
|
||||
|
||||
「零 JS 执行」是实测出来的:sidecar 自己的启动日志一行没写、审计日志在 `agent.start` 之后
|
||||
一片空白、判死探针文件 0 字节 —— 连第一行 JS 都没跑到。
|
||||
|
||||
@@ -435,9 +435,13 @@ impl AgentManager {
|
||||
);
|
||||
|
||||
// 进程边界:交给 node 的三条路径都先去掉 verbatim 前缀。
|
||||
// 被控端的脚本路径来自数据目录(本来就是普通路径),但 node 路径来自
|
||||
// `resource_dir()`(Windows 上是 `\\?\C:\…`)—— 与 2026-10-13「sidecar 秒退」
|
||||
// 同一类风险,故在此统一兜住,不依赖上游是否记得净化。
|
||||
// - `script` 来自数据目录(通常已是普通路径),但一旦带 `\\?\` 就**必炸** ——
|
||||
// 脚本参数位置是 2026-10-13「sidecar 秒退」的致命维度(真机四格实验 G2 / G4:
|
||||
// exit 1,`EISDIR ... lstat 'C:'`);
|
||||
// - `node` 来自 `resource_dir()`(Windows 上是 `\\?\C:\…`),该位置实测**无害**
|
||||
// (G3 照常启动),净化属防御性;
|
||||
// - 两条都净化是为了守住「凡交给外部进程的路径都干净」这条单一纪律 ——
|
||||
// 靠「记住哪个位置安全」省这一步,迟早会把安全的那个挪到致命的位置上。
|
||||
let node = crate::sidecar::unverbatim(node.to_path_buf());
|
||||
let script = crate::sidecar::unverbatim(script.to_path_buf());
|
||||
let data_dir = crate::sidecar::unverbatim(data_dir.to_path_buf());
|
||||
|
||||
@@ -43,12 +43,26 @@ use tokio::sync::oneshot;
|
||||
///
|
||||
/// → **exit 1,且一行 JS 都来不及执行**(连 sidecar 自己的启动日志都写不出来)。
|
||||
///
|
||||
/// 真机 A/B(Windows 11 ARM64;控制台 v2.0.3;同一个 `node.exe`、同一份 `sidecar.mjs`):
|
||||
/// ## 真机 A/B —— 二维四格,精确到是「哪一维」出问题
|
||||
/// Windows 11 ARM64;同一个 `node.exe`、同一份最小脚本 `mini.mjs`;用
|
||||
/// `ProcessStartInfo` 下发,**精确对应 Rust `Command::new(prog).arg(script)` 的形态**:
|
||||
///
|
||||
/// | 脚本参数 | 结果 |
|
||||
/// |---|---|
|
||||
/// | `C:\…\sidecar.mjs` | 2.5 秒返回 `check_update` 结果,进程常驻 |
|
||||
/// | `\\?\C:\…\sidecar.mjs` | **0.5 秒即退出,exit 1**,零 JS 执行 |
|
||||
/// | # | `program` | 脚本参数 | 结果 |
|
||||
/// |---|---|---|---|
|
||||
/// | G1 | `C:\…\node.exe` | `C:\…\mini.mjs` | exit 0,正常输出(**= 修复后的形态**) |
|
||||
/// | G2 | `C:\…\node.exe` | `\\?\C:\…\mini.mjs` | **exit 1**,`EISDIR … lstat 'C:'` |
|
||||
/// | G3 | `\\?\C:\…\node.exe` | `C:\…\mini.mjs` | exit 0,正常输出 |
|
||||
/// | G4 | `\\?\C:\…\node.exe` | `\\?\C:\…\mini.mjs` | **exit 1**,`EISDIR … lstat 'C:'` |
|
||||
///
|
||||
/// **结论**:炸的是「**脚本参数**带 verbatim」,**不是**「可执行文件带 verbatim」——
|
||||
/// G3 证明 `program` 位置带前缀 node 照常启动;真机上也有旁证:被控端与 sidecar 进程的
|
||||
/// `program` 都带 `\\?\`(见 `Get-CimInstance Win32_Process` 的 CommandLine)却长期存活。
|
||||
/// 因此 `sidecar_command` 里**净化 `script` 才是关键修复**,净化 `node` 属防御性
|
||||
/// (幂等、无副作用;且 `\\?\` 路径不适合出现在计划任务 `-Execute` 之类场景)。
|
||||
///
|
||||
/// 另有一次更贴近原症状的观测(同一台机器,直接用真 `sidecar.mjs`):
|
||||
/// 普通路径 **2.5 秒**返回 `check_update` 结果并常驻;
|
||||
/// `\\?\` 路径 **0.5 秒**即退出、exit 1、**零 JS 执行**(连自带启动日志都没写出来)。
|
||||
///
|
||||
/// 控制台侧前后两种表现都由这一条派生:
|
||||
/// - 修存活检测**之前**:请求写进已死进程的管道 → 干等 45 秒 → 「check_update 超时」;
|
||||
@@ -310,8 +324,11 @@ impl Sidecar {
|
||||
///
|
||||
/// 优先级:环境变量 `NODEAGENT_CONSOLE_NODE` → 随包 resource 的 `node` → 系统 `node`。
|
||||
///
|
||||
/// ⚠️ 出口一律经 `unverbatim`:`resource_dir()` 在 Windows 上给的是 verbatim 路径,
|
||||
/// 带 `\\?\` 的路径交给 node 会**启动即 EISDIR 秒退**(详见 `unverbatim` 的说明)。
|
||||
/// ⚠️ 出口一律经 `unverbatim`:`resource_dir()` 在 Windows 上给的是 verbatim 路径。
|
||||
/// 对本函数而言这是**防御性**净化 —— 真机四格实验(见 `unverbatim`)证明 `program`
|
||||
/// 位置带 `\\?\` 本身无害(G3 照常启动)。之所以仍然净化,是为了守住「凡从本模块
|
||||
/// 出去的路径都干净」这一条**单一纪律**:靠「记住哪些位置安全」来省这一步,
|
||||
/// 迟早会有人把它挪到参数位置而不自知(那才是 G2/G4 的必炸形态)。
|
||||
pub fn resolve_node(resource_dir: Option<&Path>) -> Result<PathBuf, String> {
|
||||
if let Ok(p) = std::env::var("NODEAGENT_CONSOLE_NODE") {
|
||||
if !p.is_empty() {
|
||||
|
||||
Reference in new issue
Block a user