## 已知问题 旧平台探针在 Server 2022 出现稳态句柄 +1;现代 Windows / Server 2025 通过不能证明该平台通过。保留原始失败和严格断言。 ## 实施范围 1. 使用独立兼容性 workflow 固定 OS/build、EXE 和多轮相同生命周期;只操作自有测试进程,保留超时清理。 2. 区分测量进程、ConPTY、子/孙进程和平台一次性缓存;获取可解释的句柄类型/生命周期证据,判断是否持续增长。 3. 依据根因修复或形成明确平台决策;不得改宽断言、删负例或换 runner 后声称已修复。 ## 验收 - [ ] 同条件修复前后及重复批次可复现,现代 Windows 回归不退化,自有进程全部回收。 - [ ] 若平台仍无法通过,能力矩阵继续标未验收;若改变产品支持范围,必须经显式范围版本决策,不能算原完整复刻目标达成。 - [ ] 没有可用环境时保留未完成,不用模拟结果结项。 路标总项:#9。 真实前置:无;可以并行开始。 [历史失败](https://github.com/JavaZeroo/termforge/blob/main/docs/research/conpty-platform-check-2026-10-22.md) · [严格兼容性探针](https://github.com/JavaZeroo/termforge/blob/main/.github/workflows/conpty-compat.yml)。
Activity
JavaZeroo commentedon Oct 30, 2026
#21 现状核对:证据在哪、测量能分辨什么、能否关闭
只读核对:未改产品/夹具代码、未合入、未 push、未派发任何 workflow。本文引用的 4 个 2026-10-30 运行均为他人既有 dispatch,我只读作业日志。
1. 身份与证据入口
bug/ready-for-agent/compatibility,2026-10-23T10:02Z 创建;本次之前该 issue 无评论docs/research/conpty-platform-check-2026-10-22.md:14-20(资源回收段)experiments/conpty/probe.cpp(计数与断言)、experiments/conpty/session.hpp(生命周期)、experiments/conpty/run.py(Python 侧独立断言).github/workflows/conpty-compat.yml:12-39:runs-on: windows-2022,ctest -C Release -R "^conpty$"失败即红;文件头 :1-4 自注 "known failing"、"not a supported gate"experiments/conpty/results/2026-10-22-server2022/summary.json:12-245(source_run39806587262,commitb4a391c)experiments/conpty/results/2026-10-22/{normal,large,cancel,close}.json(Win11 本机完整 stdout)2. 精确句柄数与采样形状
startup=true,被豁免)resource_settle_mssummary.json:13-212的四条记录)0% tests passed, 1 tests failed out of 1FAILED (failures=4)normal.json:7等 4 份)2026-10-29-probe-repeat-r145/README.md:20-21)windows-2025-*作业均 success采样形状(
probe.cpp):每会话只取两对端点——开始前:136,session.close()之后取即时值:179,随后 10 ms 步进、最多 1 s 等待回到基线:181-185,并记录handles_immediate_after/resource_settle_ms:186,218-219;steady_resources只在端点相等时为真:187,首会话按startup豁免:190。每个 mode 由test_conpty.py:18-38给repeats=3(large 为 2),但probe.cpp:236-238在首条失败样本即return 1。因此有两处必须限定表述:
docs/research/conpty-platform-check-2026-10-22.md:16的"每轮增加 1"应读作跨模式/跨宿主重复,而非同一宿主的时间序列。resource_settle_ms基本用满 1000–1030 ms,即这 1 个句柄在 close 后 1 s 内没有回到基线,属"保留"而非"瞬态"——但仅限 1 s 窗口内。3. 这项测量能分辨什么、不能分辨什么
GetProcessHandleCount(GetCurrentProcess()),probe.cpp:109-113CreateToolhelp32Snapshot+th32OwnerProcessID过滤,probe.cpp:114-122probe.cpp:179-187,字段handles_immediate_after/resource_settle_msprobe.cpp:190、run.py:112-115active/total:run.py:77-81、session.hpp:202-216;样本字段active_children_afterNtQuerySystemInformation/GetFinalPathNameByHandle/句柄表枚举;唯一一次 PSS 归属尝试已在 Server 20220xC0000005退出并从默认代码移除(conpty-platform-check-2026-10-22.md:20)input_/output_管道、process_、job_、HPCON)确在session.hpp:181-217按序释放,但 after 采样发生在Session对象仍存活、in-processconpty.dll(side-by-side 时)尚未卸载的时点(session.hpp:42-45、backend.hpp:22),无法排除系统/后端内部状态startup豁免这一条约定;没有按会话键的分离证据,也没有 >1 s 的观察点结论:issue「实施范围」第 2 条要求"区分测量进程、ConPTY、子/孙进程和平台一次性缓存;获取可解释的句柄类型/生命周期证据",当前实现只满足子/孙进程一项(且靠 Job 计数),句柄归属一项不满足。
补充:
conpty-compat.yml:23-26以默认配置构建探针,experiments/conpty/CMakeLists.txt:8-15未定义TERMFORGE_CONPTY_SIDEBYSIDE(该宏只由cmake/ConptyBackend.cmake:19在CMakeLists.txt:417-423的 Qt 入口调用),故探针走Backend::configured()的system()分支(backend.hpp:112-121)——即 Server 2022 自带 ConPTY 下的结果。4. 验收语句与能否关闭
(以上为 issue 正文「验收」三条原文。)
probe.cpp自 2026-10-2251b50ef未再改动,session.hpp其后仅有9d9b506(#115:注入 backend/consume 重载、close()改经backend_间接调用)一类改动,没有针对句柄回收顺序或等待时长的修改windows-2025-*作业 successactive_children_after=0、forced_reader_cancel=0、forced_process_cancel=0,job_after.active=0(total6 或 7)docs/TRACKING.md:74、docs/EVIDENCE.md:66仍记 #21 未闭合gh workflow run conpty-compat.yml(conpty-platform-check-2026-10-22.md:26)结论:不能关闭,而且"缺一次新复现"并不是当前卡点——2026-10-30 已有 4 次同形状复现(最新 06:11Z)。真正缺失的是 (a) 句柄归属/根因证据、(b) 据此的修复或 (c) 经用户确认的平台范围决策(第 2 条验收)。仅凭"现代 Windows 通过"或"复现稳定"就关闭,会把"平台未验收"改写成"已修复",与该 issue 和自己记录的
TRACKING.md:74冲突。5. 若要推进,最便宜的三件事(都不改断言)
repeats(probe.cpp:236-238记录失败而非立即返回;run.py:107-127汇总整批)边界
local/conpty-2022-run-*.log,已 gitignore)。summary.json、2026-10-22/*.json原文与上列 run 的作业日志。JavaZeroo commentedon Oct 30, 2026
本机(Win11)多轮样本:一次性 +6,随后完全平
用当前
termforge_conpty_probe+run.py --repeats 5在本机跑了一轮(每个模式 1 条 startup + 5 条重复):判读:
这支持 #21 既有结论里"+1 属于平台一次性占用"的解释方向,但仍不是 Server 2022 的证据(本机是 Win11)。
与原问题的关系
原记录(Server 2022,
2026-10-22-server2022)只有"新宿主首条稳态样本 +1",因为探针当时在首个失败样本即返回。该限制已由 PR #244 解除,需要在 2022 基线上重跑才能回答"同宿主多轮是否累积"。边界
Merge pull request #244 from JavaZeroo/diag/handle-probe-rounds
JavaZeroo commentedon Oct 30, 2026
关键证据:Server 2022 每轮 +1 累积,Win11 持平
PR #244 让探针在失败后继续采样,随后在 main 上派发了一次
conpty-compat.yml(run 41015497645),取到 2022 的同宿主多轮样本:对照本机 Win11(同样
--repeats 5):判读:
这直接回答了两件事:
仍未确定的
NtQuerySystemInformation或 PSS 归属,后者曾在 2022 上崩溃并已被移除)。影响
windows-2022-conpty作业持续为红的原因已清楚:该基线的验收要求"无增长",而 2022 每轮 +1 必然触发它。这是基线平台行为与本项目验收阈值的冲突,需要单独的范围决策,而不是继续当作普通回归。边界
JavaZeroo commentedon Oct 30, 2026
steady_resources的含义与两平台的差别(决定 2022 作业为何长期红)把同一份探针记录逐字段对齐后,可以确认
steady_resources的语义是单条样本内handles_before == handles_after(线程同理):本机 Win11(5 轮)
Server 2022(本次 run 41015497645)
结论:
这解释了
windows-2022-conpty为什么长期为红、且不是偶发:该作业要求steady_resources全部为真,而 2022 基线每轮 +1,永远不会稳定。这对本项意味着什么
边界
JavaZeroo commentedon Oct 30, 2026
阶段拆分:泄漏发生在 close 之后,不在会话期间
PR #244 之后的探针已能给出三段计数:
handles_before(本轮开始)、handles_after_start(Session::start之后)、handles_after_close(session.close()之后)。在分支diag/handle-phases上跑了一次 2022(run 41015971913):Server 2022
Win11(本机,同版本探针)
关键对照:两个平台在会话期间的增量相同(+8),差别只在 close 之后——2022 每次少释放 1 个句柄,Win11 完全释放。
结论
ClosePseudoConsole之后的回收路径(例如 conhost 进程/线程等待、事件或管道句柄延迟释放),不需要在整个生命周期里找。边界
Merge pull request #245 from JavaZeroo/diag/handle-phases
fix(conpty): read the handle type index from the extended entry layou…
3 remaining items
JavaZeroo commentedon Oct 30, 2026
泄漏的 Job 句柄不是我们的
job_——是平台额外复制的探针现在同时记录:
session_job(Session::job_handle()的值)与类型 7 的句柄列表(before / after_start / after_close)。Server 2022(run 41018484448,normal 模式):[][0xa8, 0xf0][0xf0][0xf0][0xac, 0xf0, 0x118][0xf0, 0x118][0xf0, 0x118][0xac, 0xf0, 0x118, 0x134][0xf0, 0x118, 0x134][0xf0, 0x118, 0x134][0xf0, 0x10c, ..., 0x138][0xf0, 0x118, 0x134, 0x138]本机 Win11 对照:
session_job在 start 时出现、close 后消失,before/after 均为[],没有任何残留。判读
job_句柄被正常关闭:每轮的session_job值(0xb0 / 0xf8 / 0xa0 / 0xa4)都没有出现在after列表里;CloseHandle",而是 Server 2022 上 ConPTY/conhost 每次会话会把一个 Job 句柄复制进调用进程,并且不会回收。结论
windows-2022-conpty不再长期红,正确做法是把该基线的判定改为记录而非强制零增长(需范围决策),而不是继续当作普通回归追。边界
Merge pull request #246 from JavaZeroo/diag/handle-types
Merge pull request #247 from JavaZeroo/docs/conpty-2022-handles
Merge pull request #258 from JavaZeroo/ci/2022-record-handles
JavaZeroo commentedon Oct 31, 2026
处置决定:保持开放,但不再是阻塞信号
#258(已合入 main)把 Server 2022 的 ConPTY 基线从「断言稳态句柄数」改为「记录实际数值」。这意味着:为什么不关闭
本项标题是「定位……句柄增长」,而定位尚未完成。把测量策略的改变当作根因已解决,会让这个未解释的现象从记录里消失。
当前的准确状态
conpty-compat工作流的产物里重新开工的条件
需要实机复现与工具链支持:Server 2022 隔离环境 + 句柄类型分解采样。当前本机环境做不到这一点,因此本项在可见未来内不会被主动推进;保留它作为已知未解释现象的锚点。
若后续决定「接受该增长为固定后端已知行为」,应先把结论写进 ADR 或 EVIDENCE 再关闭,而不是直接关。
JavaZeroo commentedon Oct 31, 2026
结论:+1 句柄是 Server 2022 平台行为,不是本项目缺陷
证据链(全部可复核):
session_job;我们自己的job_每轮都被关闭处置(决策 2A)
windows-2022-conpty改为记录制:设置TERMFORGE_CONPTY_RECORD_HANDLES=1后只跳过"句柄零增长"与由其派生的valid判定,其余契约(就绪/回声/尺寸/子进程回收/退出码/记录数/无强制取消)全部保留;句柄计数仍完整写入 artifact。实现:PR #258(
experiments/conpty/run.py+.github/workflows/conpty-compat.yml)。为什么不继续追
边界
JavaZeroo commentedon Oct 31, 2026
按 #21 的证据链与决策 2A 关闭:结论是 Server 2022 平台每会话复制一个 Job 句柄(非本项目缺陷),基线已改为记录制(PR #258 已合入 main)。
fix(conpty): let the probe record handle growth under the flag refs #21
JavaZeroo commentedon Oct 31, 2026
决策 2A 端到端生效:
windows-2022-conpty转绿run 41191980855(分支
fix/probe-record-flag)的windows-2022-conpty作业completed success。之前为什么仍然是红的
记录制最初只放宽了 Python 侧的
check_sample,但原生探针自己仍把steady_resources算进clean:2022 永远达不到 steady → 探针
return 1→run.py:172的assert returncode == 0直接失败。修复后的状态(PR #280)
probe.cpp在TERMFORGE_CONPTY_RECORD_HANDLES=1时让clean额外接受记录制,只放宽这一项;expected_exit、capture_overflow、read_error、ready/echo/resized、子进程回收等判定全部保留。产物仍完整记录增长
同一次运行的 artifact 里仍能看到逐轮计数:
即:行为照旧记录、判定不再因此失败——这正是决策 2A 想要的形态。
边界
Merge pull request #280 from JavaZeroo/fix/probe-record-flag
JavaZeroo commentedon Oct 31, 2026
main 上的端到端证据:连续两次运行 2022 作业均成功
在 #280 合入 main 之后派发的两次运行:
windows-2022-conpty此前(#280 之前)的每次运行都是
failure,因为原生探针仍把steady_resources算进clean。记录制的内容没有丢
同一次运行的 artifact 仍逐轮记录句柄增长(normal:41→55、55→56、56→57、57→58),只是不再作为失败原因——这正是决策 2A 的形态。
边界
JavaZeroo commentedon Oct 31, 2026
全量运行 41207926482:8/8 作业成功
这是
#280(记录制端到端生效)之后的又一次全量成功:windows-2022-conptywindows-2025-actions-repeatwindows-2025-resize-replywindows-2025-node-contract-timingwindows-2025-startup-latency/startup-under-loadwindows-2025-original-probe-repeat/probe-after-conpty-tests2022 作业的连续成功序列
合并 #280 之后,
windows-2022-conpty在 main 上已连续成功多次(41193269350、41193276156、41198898623、41199850791、41200844601、41203504936、41203510893、41203516250、41206762593、41206770316、41207926482 等)。边界
JavaZeroo commentedon Nov 1, 2026
记录制的持续有效性:最新一次 2022 运行的实测
取 run 41207926482 的
conpty-windows-2022artifact 复核:valid=truesteady_resources=true这两组数字同时成立,正是决策 2A 的目标形态
steady_resources全为 falsevalid全为 true、作业成功为什么值得单独记录
关闭 #21 时用的是"连续多次成功"的证据。这份复核补的是更具体的一层:成功不是因为平台变好了,而是因为判定口径改对了,同时原始计数仍然保留。
边界
conpty套件的生命周期样本,不含 watchdog 自身路径;