默认端口 8765 与 WorkBuddy Copilot 冲突:改为系统自动分配端口 待报告者确认
t-20260713-6317d1 · Bug · 创建于 2026-07-13 07:29:38 · 版本 main@95f7581 · macOS 15 (Darwin 25.5.0)
问题描述
背景:本机另一项目 WorkBuddy Copilot 的 Hook、学员端、导师端固定依赖 127.0.0.1:8765,而本工具 src/cutpoint_lab/studio/server.py 的 --port 默认值恰为 8765。两者同机运行时,WorkBuddy 的导师台地址会命中本工具的服务,导致页面/API 版本不一致等故障。双方已协商:8765 永久保留给 WorkBuddy,本工具让出。期望改动:① --port 默认值从 8765 改为 0(由操作系统自动分配空闲端口);② 启动时打印、以及 --open 自动打开的地址必须使用实际绑定的端口(server 绑定后可从 socket 取真实端口);③ 保留 --port 显式指定的能力且行为不变(需要固定端口的用户仍可指定);④ scripts/studio_web.py 的 usage 文案与相关文档同步更新。验收标准:不带 --port 连续启动两个实例可同时运行互不冲突;--open 打开的地址真实可用;显式 --port 8765 时行为与现在完全一致;python3 scripts/run_tests.py 全绿。备注:这是创始人已拍板的产品决策(让出 8765),属于启动配置默认值变更,不涉及 HTTP API 的行为语义——不应被约束第 5 条误伤。
用户日志
lsof -nP -iTCP:8765 -sTCP:LISTEN → python3.1 11039 michael TCP 127.0.0.1:8765 (LISTEN)(本工具进程);WorkBuddy 侧 AI 确认导师台因此命中错误服务
分诊结论
结论:accept
- 问题描述清晰:端口冲突症状明确(lsof 日志证实本工具占用 8765),根因可理解
- 期望改动具体可执行:四点需求均有明确行为定义(默认改 0、打印真实端口、--open 使用真实端口、保留显式指定能力)
- 验收标准完整:可独立验证(双实例并存、--open 可用、显式 8765 行为不变、测试全绿)
- 改动范围有限且不违反约束:仅改启动配置默认值,不涉及 HTTP API 行为语义,不引入新依赖
修复摘要
将 server.py 的 --port 默认值从 8765 改为 0(OS 自动分配),并在测试中添加正确的回归断言:通过 patch bind_server 捕获 main([]) 实际传入的 port 参数断言为 0,不再创建独立 ArgumentParser。
改动文件:
src/cutpoint_lab/studio/server.pytests/test_studio_app.py
合并提交 29440fd1de52f96f907fc2ca0355fec457ee9188
事件时间线
- 2026-07-13 07:29:38created
{"type":"bug","hasLogs":true} - 2026-07-13 07:30:06status
{"from":"received","to":"queued","detail":{"via":"triage"}} - 2026-07-13 07:30:06status
{"from":"queued","to":"fixing"} - 2026-07-13 07:31:53verify
{"ok":true,"steps":[{"name":"verify","ok":true,"required":true}]} - 2026-07-13 07:31:53status
{"from":"fixing","to":"awaiting_judge","detail":{"files":2}} - 2026-07-13 07:31:53status
{"from":"awaiting_judge","to":"judging"} - 2026-07-13 07:32:59policy
{"action":"human","tier":"medium","reasons":["judge 无法判定:Bug 真实存在:server.py 原 default=8765 与 WorkBuddy 固定端口冲突,日志可证实。;需求 ① 已满足:argparse default 从 8765 改为 0,help 文本同步更新。;需求 ② 无法从 diff 验证:工单要求 '启动时打印、以及 --open 自动打开的地址必须使用实际绑定的端口'。diff 未展示 main() 的 URL 打印逻辑和 --open 的 webbrowser.open 调用。若这两处仍拼 args.port(=0),用户会看到 http://127.0.0.1:0 或空白浏览器,修复实际无效。;需求 ④ 修复者自认未完成:scripts/studio_web.py 的 docstring/usage 未更新,以 '保护路径' 为由搁置,工单验收标准明确要求文档同步。;回归测试第一部分是冗余测试:test_default_port_is_zero_not_8765 中创建了独立的 ArgumentParser 并断言其 default=0,这个断言测的是测试自己写的 parser 而非 server_main 的 parser,即使 server.py 没改也能通过,属于无效断言。第二部分(--help 不含 8765)是有效的,但整体测试未覆盖 --open 实际打开 URL 这一关键路径。;pipeline 报错(源媒体不存在)系测试设计内预期错误,与本次改动无关,已确认 86/86 全绿。"]} - 2026-07-13 07:32:59status
{"from":"judging","to":"awaiting_human","detail":{"cardId":"d-20260713-4ff3d6","reasons":["judge 无法判定:Bug 真实存在:server.py 原 default=8765 与 WorkBuddy 固定端口冲突,日志可证实。;需求 ① 已满足:argparse default 从 8765 改为 0,help 文本同步更新。;需求 ② 无法从 diff 验证:工单要求 '启动时打印、以及 --open 自动打开的地址必须使用实际绑定的端口'。diff 未展示 main() 的 URL 打印逻辑和 --open 的 webbrowser.open 调用。若这两处仍拼 args.port(=0),用户会看到 http://127.0.0.1:0 或空白浏览器,修复实际无效。;需求 ④ 修复者自认未完成:scripts/studio_web.py 的 docstring/usage 未更新,以 '保护路径' 为由搁置,工单验收标准明确要求文档同步。;回归测试第一部分是冗余测试:test_default_port_is_zero_not_8765 中创建了独立的 ArgumentParser 并断言其 default=0,这个断言测的是测试自己写的 parser 而非 server_main 的 parser,即使 server.py 没改也能通过,属于无效断言。第二部分(--help 不含 8765)是有效的,但整体测试未覆盖 --open 实际打开 URL 这一关键路径。;pipeline 报错(源媒体不存在)系测试设计内预期错误,与本次改动无关,已确认 86/86 全绿。"]}} - 2026-07-13 07:35:45humanReview
{"cardId":"d-20260713-4ff3d6","choiceId":"requeue","note":"保留现有改动(default=0 正确;--open/打印已实证使用真实绑定端口 server.py:611-615,无需改动)。只需修一处:test_studio_app.py 里 test_default_port_is_zero_not_8765 的第一部分断言无效——它新建了一个独立 ArgumentParser 再断言 default=0,测的是测试自己而非 server 的 parser,server.py 回退到 8765 它也能通过。请改为对 server 真实入口的断言(例如导入 server 模块解析空参数列表后断言 args.port==0,或等价方式);--help 不含 8765 的第二部分断言保留。不要碰 scripts/**(usage 文档由运维另行处理)。"} - 2026-07-13 07:35:45status
{"from":"awaiting_human","to":"queued","detail":{"via":"human","note":"保留现有改动(default=0 正确;--open/打印已实证使用真实绑定端口 server.py:611-615,无需改动)。只需修一处:test_studio_app.py 里 test_default_port_is_zero_not_8765 的第一部分断言无效——它新建了一个独立 ArgumentParser 再断言 default=0,测的是测试自己而非 server 的 parser,server.py 回退到 8765 它也能通过。请改为对 server 真实入口的断言(例如导入 server 模块解析空参数列表后断言 args.port==0,或等价方式);--help 不含 8765 的第二部分断言保留。不要碰 scripts/**(usage 文档由运维另行处理)。"}} - 2026-07-13 07:35:45status
{"from":"queued","to":"fixing"} - 2026-07-13 07:38:02verify
{"ok":true,"steps":[{"name":"verify","ok":true,"required":true}]} - 2026-07-13 07:38:03status
{"from":"fixing","to":"awaiting_judge","detail":{"files":2}} - 2026-07-13 07:38:03status
{"from":"awaiting_judge","to":"judging"} - 2026-07-13 07:38:39policy
{"action":"human","tier":"medium","reasons":["风险等级 medium 的门槛配置为人工审批"]} - 2026-07-13 07:38:39status
{"from":"judging","to":"awaiting_human","detail":{"cardId":"d-20260713-0d427e","reasons":["风险等级 medium 的门槛配置为人工审批"]}} - 2026-07-13 07:50:11humanReview
{"cardId":"d-20260713-0d427e","choiceId":"approve","note":""} - 2026-07-13 07:50:11status
{"from":"awaiting_human","to":"approved","detail":{"via":"human"}} - 2026-07-13 07:50:11status
{"from":"approved","to":"merging"} - 2026-07-13 07:50:13merged
{"sha":"29440fd1de52f96f907fc2ca0355fec457ee9188"} - 2026-07-13 07:50:13status
{"from":"merging","to":"merged","detail":{"sha":"29440fd1de52f96f907fc2ca0355fec457ee9188"}} - 2026-07-13 07:50:13notified
{"mode":"log","confirmUrl":"http://localhost:8123/confirm/t-20260713-6317d1?token=***"} - 2026-07-13 07:50:13status
{"from":"merged","to":"awaiting_confirmation"}