继续收敛受控命令进程错误

保留 leader 身份读取和进程组终止具体原因

保留主进程 kill、wait 与兜底终止正文
This commit is contained in:
kdletters
2026-10-06 01:48:04 +08:00
parent b3b568385c
commit dcf5dad4b1
2 changed files with 26 additions and 12 deletions
@@ -155,9 +155,11 @@ impl ProjectCommandTree {
let pid = child.id().ok_or("受控命令缺少进程身份")?;
let start_identity =
crate::process_identity::external_agent_runner_process_start_identity(pid)
.ok()
.flatten()
.map_err(|error| format!("受控命令 leader 启动身份读取失败:{error}"))?
.filter(|identity| !identity.is_empty());
if start_identity.is_none() {
return Err("受控命令 leader 启动身份为空,拒绝接管进程组".into());
}
Ok(Self::Group {
pid,
start_identity,
@@ -186,7 +188,7 @@ impl ProjectCommandTree {
}
let observed =
crate::process_identity::external_agent_runner_process_start_identity(*pid)
.map_err(|_| "受控进程组 leader 身份未确认,拒绝发送终止信号")?;
.map_err(|error| format!("受控进程组 leader 身份未确认:{error}"))?;
if !owned_project_command_group_identity_matches(
start_identity.as_deref(),
observed.as_deref(),
@@ -207,15 +209,23 @@ impl ProjectCommandTree {
{
let Self::Job(job) = self;
let requested = job.terminate();
if requested.is_err() {
let _ = child.start_kill();
}
let fallback_error = requested
.as_ref()
.err()
.and_then(|_| child.start_kill().err());
let deadline = tokio::time::Instant::now() + Duration::from_secs(5);
let waited = tokio::time::timeout_at(deadline, child.wait()).await;
requested?;
if let Err(error) = requested {
return Err(match fallback_error {
Some(fallback_error) => {
format!("{error};主进程兜底终止失败:{fallback_error}")
}
None => error,
});
}
waited
.map_err(|_| "等待受控命令主进程退出超时")?
.map_err(|_| "受控命令主进程退出未确认")?;
.map_err(|_| "等待受控命令主进程退出超时:超过 5 秒".to_string())?
.map_err(|error| format!("受控命令主进程退出未确认:{error}"))?;
while !job.is_empty()? {
if tokio::time::Instant::now() >= deadline {
return Err("受控命令 Windows Job 子树退出未确认".into());
@@ -228,11 +238,14 @@ impl ProjectCommandTree {
{
let deadline = tokio::time::Instant::now() + Duration::from_secs(5);
let requested = self.request_owned_group_termination();
let _ = child.start_kill();
let child_kill_error = child.start_kill().err();
let waited = tokio::time::timeout_at(deadline, child.wait()).await;
let child_kill_detail = child_kill_error
.map(|error| format!(";主进程兜底终止失败:{error}"))
.unwrap_or_default();
waited
.map_err(|_| "等待受控命令主进程退出超时")?
.map_err(|_| "受控命令主进程退出未确认")?;
.map_err(|_| "等待受控命令主进程退出超时:超过 5 秒".to_string())?
.map_err(|error| format!("受控命令主进程退出未确认:{error}{child_kill_detail}"))?;
#[cfg(target_os = "linux")]
{
let Self::Group { pid, .. } = self;
@@ -93,3 +93,4 @@ DirectProject 回合失败在确认不是客户端内部不可归类故障时,
- 策划工作区事件订阅失败仍有一处直接静默结束,导致文件刷新失联却没有提示;现把订阅 IPC 正文写入工作区错误状态,继续保留手动刷新与已有清单读取错误出口。
- 新一轮原生扫出 app-server 进程与上下文漏损:Codex 执行器核验/OAuth 保存/回合身份的 JoinError、图片 base64 解码、Direct 上下文 canonicalize,以及进程树归属、Job、等待、终止和 Drop 清理错误原先只剩固定码;现保留 JoinError、解码器、OS/Job、等待和终止阶段正文,并为进程树 Drop 清理失败写入诊断日志。
- 继续扫出浏览器收割的 Job/root kill/wait 错误原先压成 `bool`、External Editor 本机凭据 JSON/目录创建/保存错误丢底层正文,以及生成任务中断收口把项目清单读取失败当成“状态未知”;现分别保留浏览器清理阶段正文、凭据解析/存储正文,并让非“项目尚未初始化”的清单读取失败直接返回具体错误。
- 受控 command 执行路径仍会把 leader 启动身份读取、进程组终止、root kill、Job/wait 失败折叠为“身份未确认/退出未确认”;现拒绝无身份接管并保留身份读取、终止兜底、等待和 OS 错误正文。