Repository navigation
Description
真实失败与范围
PR #96 的 push candidate run 40449286764,Windows job 122007956956,HEAD 5c073e49ecef6722f997cdc9a7d3b8dd97e594ee,candidate_shell_tui 中 test_real_scroll_region 失败。该配置为 candidate termforge.5 + system ConPTY/flags0;该提交只改上游隔离构建与证据,没有修改此产品路径。不能因此断定环境抖动或根因。
6个该组测试中另5个通过,本组总计24.853s;失败调用是原 execute(...,"candidate-scroll",timeout=20)。同HEAD PR candidate成功不能覆盖该push失败;没有重跑取绿。
已核对的证据
artifact 12709588341 ZIP SHA d6d0a533acdef574b60de38dc65ce052b3dce1137ef1679ecc5eedb9259cb942 已下载核对;原件保存于私有 local/r56-conpty-build/ci-fifth-candidate-failure.zip。
sample.jsonSHA458e49164de09028d86f1f42a9e8476e92e8ebd284378f7d0afe1ac08c2d0027:timed_out=true;期限时Job active5/total7/terminated0;外层返回99,stdout/stderr为空;清理后Job active0/total7/terminated0,desktop_gone=true。清理结果不能冒充自然退出。- before/region/final 原生capture、源与实际运输、candidate末帧均已产生。末帧80×24,cursor[5,0],alternate=false,frame_matches_full=true;可见前五行是TOP_FIXED/REGION_C/REGION_D/BOTTOM_FIXED/TF_SCROLL_DONE,与本场景预期一致。
- 原107B源 SHA
f27448bdc7f3811e68ae5e6f9779d14409d179df55761b7b1355997d69afaff1;实际运输737B SHAe8553b2ba03e4e51a7189104f340cf5a9c11f8ce55256419568f9f144c51d144。 - 最后已完成阶段、阻塞线程/进程、child自然退出及关窗/回收的具体边界未知,不能仅凭画面正确宣布通过。它不是#56的175B主/备用margin输入,也没有证明与[R1回归] 持续 cmd 输出时 ETX 未中断导致 normal/resize 超时 #36/#44同因。
有界后续与验收
- 固定原源码/二进制、107B源、20秒总期限与已有清理预算;核对末帧之后的child退出、reader EOF、会话关闭、UI退出和launcher等待边界。
- 执行前列明可证伪假设、最小阶段观测、样本数与停止条件;优先离线检查现有capture及代码,再进行限定自有Job/desktop复现。不改变测试成功预期、不延长期限、不无限刷绿。
- 若取得同类失败,给出同合同的因果red→green及必要反例;若未复现,保留原失败和未知。
- 最终同源candidate/system回归和自然退出/Job/desktop回收通过;单独末帧正确不替代生命周期。
父项 #38,关联 #27/#28;本项是独立可靠性诊断,不以其他配置或上游adapter412/412来结项。
离线复核与有界接续
用既有项目虚拟环境执行 validate_scroll_output 对上述原 artifact 复算,原生 before/region/final、107B源、737B运输的独立 pyte 0.8.2 重放、候选末帧/默认属性/光标/有限历史均通过。该函数不检查进程自然退出;因此这只是确定已有输出证据的边界,不能把原20秒超时改为通过。首次全局 Python 缺少 pyte 元数据,随后使用已有项目环境复核,无安装或源码修改。
已按上述预定批次固定失败 HEAD 5c073e49ecef6722f997cdc9a7d3b8dd97e594ee,在独立工作树/全新candidate/system构建执行原 CandidateTuiTests.test_real_scroll_region:一个单测内原两次场景均通过,合计1.204秒,每次原20秒外部期限和原清理预算不变。两次均exit37、cleanup_complete、内外Job active0、desktop已删除;没有追加循环。
本机为Windows11 build26200/MSVC19.42,CI为Windows Server环境,结果分列。本机未复现原CI超时,不构成因果修复,原阻塞阶段仍unknown。源码/二进制/运行日志与两份完整sample保存在独立工作树的 local/r97/;脱敏清单SHA dffb1487c9597b563d91da7d2cf1c2b994db4f48625cb446662c168debed167f。本次没有修改实现或断言。
后续须在能区分末帧完成之后各退出阶段的最小观测下获得同类失败:child返回、cmd接收退出输入、Session wait/reader EOF、窗口退出与launcher等待分别留证。先固定探针/样本/停止条件,保持原期限;目前只记录待定位阶段,不宣布任一环节为原因。
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsDone
Activity
JavaZeroo commentedon Oct 30, 2026
失败配置在现树上可达、断言未变;缺的是「末帧之后收尾状态机」的一条快照
1. 可达性(逐行引用,均可复核)
option(TERMFORGE_BUILD_CANDIDATE_ADAPTER ... OFF);:170-172if(TERMFORGE_BUILD_CANDIDATE_ADAPTER) termforge_candidate_shell(termforge_shell);:54-63 加TERMFORGE_CANDIDATE_CORE=1并链 Rust 静态库TERMFORGE_CONPTY_SIDEBYSIDE时configured()返回system();:25、:129-130creation_flags_ = 0TERMFORGE_BUILD_QT_SHELL内 include)与 :417 用于 Qt 入口termforge_shell(:166-172)从未调用 → 该目标永远 system/flags0,没有第二种后端可选candidate_shell_tui需 option 且 WIN32;test_candidate_tui.py:93-96execute(...,"candidate-scroll",...,timeout=20)结论:现树上
cmake -S . -B <dir> -DTERMFORGE_BUILD_CANDIDATE_ADAPTER=ON+ctest -R candidate_shell_tui就是 issue 记录的配置,可达。2. 与失败 HEAD
5c073e4相比,断言与路径未变test_real_scroll_region逐字未变(git diff 5c073e4..HEAD -- experiments/shell/test_candidate_tui.py只改动 margins 用例);scroll_checks.py、backend.hpp无改动。run.py的期限分支(:467-477)未改;normal 退出仍是exit 37(shell.cpp:561-563、:1478-1485)。TERMFORGE_OUTPUT_TRACE/TERMFORGE_CAPTURE_OUTPUT。原失败 artifact 早于该改动,所以它不可能带 output-trace.jsonl;同配置再红一次会自带读写时间线。3. local/repeat* 里没有 candidate-scroll 样本
local/repeat2…repeat16的场景目录全是默认构建的ui-*(无candidate-*);local下*candidate*文件为空;文本里 grepcandidate-scroll为空。D:\Code\termforge-scroll-exit-r97\local\r97\original-test\candidate-shell\run-13cpoqeb\candidate-scroll-0与…\candidate-scroll-1(before/region/final 的 console+cells、source.bin 107 B、transport.bin 801/813 B、candidate-scroll.json、sample.json、frame.bmp)。同目录 manifest.json 记 source5c073e4、configurationcandidate termforge.5/system ConPTY/flags0、original_outer_timeout_seconds 20、retry_allowed false;original-test.log 为Ran 1 test in 1.204s ... OK。4. 三条假设与一条可分离观测
末帧内容的最后写入点在 shell.cpp:557(证据 JSON);之后收尾顺序是:gate 信号(:558-560)→
type(L"exit 37\r")(:562)→stage_=6→begin_close()(:401-422 起 closer 线程:input_.stop()、resize_.stop()、session_.cancel() / session_.close()→closed_=true)→ tick 里if (closing_ && closed_)且drained才DestroyWindow(:903-909)。session_.wait_exit(0)(:912)/ candidate 的 terminal event 6(:825)→ 从未begin_close()closing_=falseinput_.stop()/session_.close()(:413-421),或drained永不为真(:905)closing_=true但closed_=false或drained=falseDestroyWindow之后到进程返回(:907-909、:1474-1506)closed_=true、窗口已销毁,但进程 20 s 内未返回单一观测:一行「收尾状态机快照」——同一 schema、同一文件,从末帧写完开始每 tick 一行,字段为
stage_、closing_、closed_、drained(当拍输入/输出已取空)、session_.child_signaled / exit_code / read_error。取值组合即可三分:child 未 signaled→H1;signaled 但closed_假→H2;都真而进程不返回→H3。两个必须注意的放置约束:① 失败时 stdout/stderr 为空(正文 sample 事实),而 shell 只在成功路径打印 JSON(shell.cpp:1487-1505)、异常路径
return 2(:1507-1510),所以快照必须旁路写出;② 若 UI 线程卡住,tick 内的行也写不出来 → 同一 schema 还要由 closer 线程(:413-421)和 reader EOF 处(session.hpp:96-97)各写一行。为什么现有 artifact 必然缺这一观测:run.py:469-473 在期限处
job.terminate(),而OwnedJob.terminate是TerminateJobObject(handle, 99)(experiments/conpty/run.py:83-84)——正文记的「外层返回99」就是我们自己的杀进程码,不是 child 的自然退出码。正文「清理结果不能冒充自然退出」在此有了代码位置。5. 关闭判据(原文)
因此本项仍应开放;下一步是一次限定运行(同 HEAD 或同配置、20 s 不变、不扩样),带上面那一行快照,取到同类 red 即为三分;取不到就继续保留未知,不以「再绿一次」结项。
边界
git diff 5c073e4..HEAD、本树 local 目录枚举,以及另一工作树local/r97的 manifest.json 与 original-test.log。未改代码/断言/期限,未重跑、未 dispatch、未下载任何 CI artifact。JavaZeroo commentedon Oct 31, 2026
R1 #97 复核:失败配置在当前树可达、CI 每轮都在跑;单一观测是收尾心跳与 frame.bmp
1. 可达性(当前树逐行核对)
CMakeLists.txt:4option(TERMFORGE_BUILD_CANDIDATE_ADAPTER ... OFF)CMakeLists.txt:54-58termforge_candidate_shell():加TERMFORGE_CANDIDATE_CORE=1并链 Rust 静态库CMakeLists.txt:170add_executable(termforge_shell ...);:174-175if(...) termforge_candidate_shell(termforge_shell)experiments/conpty/backend.hpp:112-122configured():未定义TERMFORGE_CONPTY_SIDEBYSIDE时返回system()(:79-82),creation_flags_默认 0(:25)cmake/ConptyBackend.cmake:19仅在termforge_use_bundled_conpty()内;该函数只被CMakeLists.txt:432的termforge_qt_entry()(:427-447)调用,Qt 目标在 :415 的if(TERMFORGE_BUILD_QT_SHELL)内termforge_shell永远 system/flags0,无第二种后端CMakeLists.txt:348-350(candidate_shell_tui,TIMEOUT 120);experiments/shell/test_candidate_tui.py:95-96execute(..., "candidate-scroll", ..., timeout=20).github/workflows/core-candidate.yml:78(-DTERMFORGE_BUILD_CANDIDATE_ADAPTER=ON)、:88-90(windows 上ctest -R candidate_shell_tui)结论:
cmake -DTERMFORGE_BUILD_CANDIDATE_ADAPTER=ON+ctest -R candidate_shell_tui就是 issue 记录的配置,可达且被 CI 覆盖。2. 最近 CI 里它确实在跑(但没有复现原超时)
2026-10-31T08:20Z 两次 core-candidate 失败(
d2864711run 41184212920、cdc00debrun 41184198882)step 17Build and test candidate shell on Windows失败:test_real_scroll_region ... ok([R2][回归] 定位 candidate-scroll 末帧生成后的原20秒退出超时 #97 的原失败用例通过),失败的是test_real_fullscreen_editor_and_cursor_reply:unsupported terminal event kind;stage=50 bytes=949 input_high_water=1 input_rejected=0 output_bytes=2783 read_error=109(日志原文),说明扩展 dump 已在 CI 生效。即:配置可达、用例在跑、原 20 s 退出超时未复现;同时看到同一用例组在 CI 确实会红,不能把"没复现"当成修复。
3. 原失败 artifact 里的一条新事实:没有 frame.bmp
原件(issue 正文所指)保留在
D:\Code\termforge-r51-resize\local\r56-conpty-build\ci-fifth-candidate-failure.zip,我直接读了它的目录与样本:candidate-shell/run-jrd4hlz1/candidate-scroll-0/candidate-scroll.json(3673 B)、candidate-scroll.source.bin(107 B)、candidate-scroll.transport.bin(737 B)、before/region/final 的 console+cellsframe.bmp(同 zip 内通过样本如candidate-margins-0/frame.bmp有 2,396,214 B)sample.json(524 B)timed_out=true、exit_code=99、stdout/stderr 空、job_at_timeout active=5/total=7/terminated=0、清理后 active=0、desktop_gone=true代码对应:
frame.bmp只在closing_ && closed_ && drained分支内写(experiments/shell/shell.cpp:917-923,save 在 :921、DestroyWindow在 :922)。而candidate-scroll.json写在场景末尾(:557),type(L"exit 37\r")在其后(:562)。因此原失败样本确实执行到"末帧证据写完 + exit 37 已排入"之后,且没有进入收尾分支。这直接排除了"窗口已销毁、只是进程/launcher 未返回"这一类(我在前一条评论里列的 H3),把范围压缩为:
closing_未置位(child 未退出 /wait_exit(0)一直 false,:926)或closed_未置位(closer 线程卡住)或drained一直为假。4. 单一后续观测
原 zip 来自
5c073e4,早于 output/input trace(5dd3a9d/1f0ce20都不在该 HEAD),所以它没有output-trace.jsonl;现在同一配置再跑会自动带(experiments/shell/run.py:453默认设置TERMFORGE_OUTPUT_TRACE)。一次同合同 red 即可三分,只需读该样本
output-trace.jsonl的最后一条 heartbeat(termforge-output-heartbeat-v1,每 500 ms 一条,字段closing/complete/pid/child_signaled/exit_code/read_error,shell.cpp:889-909),外加同目录frame.bmp是否存在:closing=false且child_signaled=falsebegin_close未发生:child 未退出,exit 37未被处理(下一步转到输入侧:现在 input trace 已能证明exit 37\r是否写入)closing=true且心跳持续到超时input_.stop()/session_.close(),shell.cpp:413-421)closing=true后停止frame.bmp却仍超时DestroyWindow之后(本条已被原 artifact 排除,仅作后续判据)不改 20 s 期限、不扩样、不改断言;这一步只是把已有字段读全。
5. issue 原文句子与可关闭性
(正文"有界后续与验收"第 3、4 条)
当前:配置可达但原超时未复现(本机与 CI 都没有),收尾状态仍未观测,不可关闭;只有当同源 candidate/system 回归 + 自然退出/Job/desktop 回收在同一源码通过,或用上面的心跳取得同类 red 并给出因果 red→green,才谈得上关闭。
边界
CMakeLists.txt、backend.hpp、cmake/ConptyBackend.cmake、test_candidate_tui.py、run.py、shell.cpp相关行;两段 CI 日志;原件 zip 内目录与sample.json。未重跑、未构建 candidate、未改断言/期限、未 dispatch、未下载新 artifact。D:\Code\termforge-scroll-exit-r97\local\r97内复现样本内容(只确认目录存在)。unsupported terminal event kind失败与 [R2][回归] 定位 candidate-scroll 末帧生成后的原20秒退出超时 #97 同因,也未把 [R2][回归] 定位 candidate-scroll 末帧生成后的原20秒退出超时 #97 与 [R1回归] 持续 cmd 输出时 ETX 未中断导致 normal/resize 超时 #36/[R2][Regression] controller B graceful 的 cmd 正常退出观测缺口 #44 判为同因。Merge pull request #283 from JavaZeroo/diag/candidate-frame-at-timeout
Merge pull request #285 from JavaZeroo/diag/lifecycle-flags
JavaZeroo commentedon Nov 1, 2026
按「不死磕」原则关闭:candidate-scroll 末帧生成后的 20 秒退出超时
关闭理由
用户明确「可以接受不完美、不需要一直死磕一个问题」。本项属于批次内不可复现、诊断已就位的一类,因此按此原则关闭。
本项已有的证据
已加入
frame_at_timeout与标签页生命周期字段:frame.bmp只在closing_ && closed_ && drained三者成立后写出,因此其在期限瞬间的存在性可区分"收尾未完成"与"帧已出但进程未返回"。已就位的诊断(再次命中时可立即判读)
sample.json的frame_at_timeoutclosing/closed/closer_joinedoutput_bytes/consumed/child_signaled/exit_code关闭时的真实状态
本项不是根因修复。原始失败记录仍保留,未被新批次覆盖。
重开条件
若同类失败再次出现,凭上述诊断字段可直接判读,恢复本项即可,无需重做本轮探索。