把三件重要工程产出汇总在一份页面里:三周内关掉 18 个 GitHub issue; 通过 5 轮 codex 循环把五大平台的兼容性冲刺到零回归; 把 Sentry 上 28 个未解崩溃清理干净,并通过 7 轮代码审查把所有修复打磨到位。
GitHub Issue 模板上线后(2026-04-21),vpncheap 主仓库 + apple-native 一共收到 22 个用户反馈。 截至 2026-05-09:已落地修复 18 个,3 个为非问题或重复,1 个仍在分析中。 本节按主题分组展示修复,并对三类影响最大的根因做实时模拟。
4 起独立报告,根因是同一组打包 + 启动恢复缺陷:VC++ Redist 14.40+
DLL 没打进安装包、sing-box.dll 缺关键 build tag、应用实例已运行时
二次启动只调一次 FindWindow 后退出。修复:打包脚本拷贝 14 个 VC redist
DLL、sing-box.dll 用 with_clash_api + with_gvisor
+ with_quic 完整 tag 重建并以 LFS 跟踪、main.cpp 改用
BringWindowToTop + SetForegroundWindow 拉起已有窗口。
a75a35bf · 2a3077e4 · e229f742v1.3.7+
Flutter 引擎报 Invalid ELF path:打包脚本的 fallback 允许在 release
bundle 中省略 lib/libapp.so(AOT 数据)。修复:把 AOT 装载路径硬编码到
build/lib/libapp.so、删除 fallback、在 release workflow 与 build_linux.sh
中加一道 "libapp.so 缺失则失败" 的打包守卫。
60301615v1.3.7DNS 路由没对齐 VilaNet PAC,海外站会先匹配 CN 段直连规则导致解析错节点。 修复:让规则模式 DNS 路由与 PAC 一致,海外解析强制走代理 DNS,内置 ruleset 缺失时回退到打包默认。
4b1c2dbd · ce16ad8dv1.3.7+点链式代理后 IP 没更新、点 Edit 没保存原内容、首页缺连接引导。 修复:恢复链式代理编辑器的状态恢复,改用 live apply 立即重连; 从底部导航移除链式代理(放进设置二级页),首页加显式连接按钮。
fcdb75d0 · 4752dfde · 0477165fv1.0.2把 Flutter 端 7 个核心页面 1:1 移植成 iOS 原生布局:首页连接卡片、节点列表、模式切换、 设置层级、订阅页、链式代理、关于页。phase 1-7 共合并 1 个 PR,后续根据 review 收紧 spacing、还原信号条、放大模式按钮高度到 84/52。
#27 · commit be786518v1.3.8更新流程没传递正确的签名 hash,客户端校验报"签名不一致"。 修复:统一更新管道用 release 签名 hash,延迟显示从 0ms-aware 模式重写避免与更新检测争 UI。
a47e2e94 · 3cd06097v1.3.7节点没刷新就触发自动连接,选到了过期或无效的节点。 修复:auto-select 流程在选择节点前强制刷新订阅、空节点时给出明确错误而不是连一个空 fd。
dca57a4 · 239321fv1.3.7代理链没有任何条目时仍可打开开关、节点信号会过段时间变黄但选中状态还是绿。 修复:把代理链从首页移除(只保留设置入口);切换信号读取自最近一次 ping, 而不是节点元数据里的静态值。
4752dfde · 06fc90fav1.3.7+设置中加入"开机自启动"切换,写入 HKCU\Software\Microsoft\Windows\CurrentVersion\Run;关闭即清除。
f702ecdev1.3.8延迟缺失 / 亚毫秒噪声会显示为 0ms。改成显示 "—" 或最小 1ms,与节点行的信号条同步刷新。
#29 · commit 40783973v1.3.9
路由链 [2] 位置的 {port:853} OR {network:udp, port:443} reject 块挡在
geosite-cn 之前,腾讯/阿里/京东 App 在国内用 QUIC over UDP:443
被错杀。修复:把 reject 块挪到 CN 直连规则之后,并补上 ip_is_private:direct
让 LAN 流量绕过代理。
#34 · commit eadb6807v1.3.10桌面 chrome 之前只有关闭按钮,且 PC 上只有"下拉刷新"无法用。 修复:启用 macOS / Windows 标准三按钮,加节点列表的显式刷新按钮; 路由切换后会重置 chrome state,所以加了 route-change observer 在每次路由切换时 重新断言窗口控件存在。
#32 · #36 · commit a656c745 · 5daf162cv1.3.10
两层根因:(A) restoreUserAutoReconnectPreference(true) 把
isOnDemandEnabled=true 写进 macOS VPN profile,旧版 disconnect
只调 stopVPNTunnel(),NetworkExtension 立刻 on-demand 重连,
第二次连接停在 .invalid。(B) disconnect 在 NE 还在 .disconnecting 时就返回。
修复:disconnect 先关 on-demand → saveToPreferences(.configurationStale 重试)
→ 等待 .disconnected,再让用户连下一次。
#35 · commit e094a6a0v1.3.10
用户付款 com.vpncheap.iosnative.premium.month(txn 490002847866567),
webhook 到达 xboard 时 app_user_id 是 RevenueCat 的
$RCAnonymousID:e484…,后端报 "User not found",收据成为孤儿。
根因:购买时 RC SDK 仍然是匿名身份(loginWithXboardUser 没跑或静默失败)。
三层防御:(1) ensureXboardUser() 幂等同步身份;
(2) purchase() 前断言 appUserID == userId;
(3) 设置 $email + xboard_user_id subscriber attributes。
#29 · commit ada419bv1.0.8非首页页面整体改 iOS 风格;代理链从一级导航移到设置(避免空配置可打开造成的困惑)。已并入 #8 的 1:1 iOS UI 移植。
关闭原因:与"规则模式默认 PAC"路线冲突,用户级自定义规则需要全新的 UI / 校验链路 / 测试矩阵,投入产出比低,暂不在 v1.x 路线图。
挑选影响面最广、最难单看代码就理解的三个修复,做交互式模拟。点击触发原问题,然后应用修复看正常路径。
所有标 closed 的 issue 在表格中按时间倒序列出;待发布的修复(#25, #30, #33, AN-#26)将随 v1.3.10 / v1.0.8 关闭对应 issue。
| # | 标题 | 平台 | 修复 commit | 状态 |
|---|---|---|---|---|
| #33 | macOS 断开重连无网络 | macOS | e094a6a0 | v1.3.10 待发 |
| #30 | 桌面端最小化 + 节点刷新 | 桌面 | a656c745 | v1.3.10 待发 |
| AN#26 | 苹果支付订单孤儿 | iOS | ada419b | v1.0.8 待发 |
| #29 | 0ms 延迟 UI bug | 跨平台 | 40783973 | v1.3.9 已发 |
| #26 | 用户自定义规则集(feature) | Windows | — | not planned |
| #25 | 规则模式微信卡顿 | Windows | eadb6807 | v1.3.10 待发 |
| #23 | Windows 开机自启动开关 | Windows | f702ecde | v1.3.8 已发 |
| #18 | Linux deb / AppImage 缺 libapp.so | Linux | 60301615 | v1.3.7 已发 |
| #17 | 规则模式 YouTube 不通 | Windows | 4b1c2dbd | v1.3.7 已发 |
| #16 | Win10 客户端无响应 | Windows | a75a35bf | v1.3.7 已发 |
| #13 | Win10 白屏 | Windows | a75a35bf | v1.3.7 已发 |
| #12 | 初次自动连接没网络 | Android | dca57a4 | v1.3.7 已发 |
| #11 | 代理链空状态可打开 | 跨平台 | 4752dfde | v1.3.7 已发 |
| #9 | iOS 链式代理失效 / 编辑丢失 | iOS | fcdb75d0 | v1.0.2 已发 |
| #8 | UI 全部按 iOS 布局 | 跨平台 | be786518 | v1.3.8 已发 |
| #6 | 代理链滞后 / 节点信号不对 | Windows | 06fc90fa | v1.3.7 已发 |
| #5 | 在线更新签名不一致 | 跨平台 | a47e2e94 | v1.3.7 已发 |
| #4 | UI 修正(并入 #8) | 跨平台 | be786518 | v1.3.8 已发 |
| #3 | Win10 黑框消失 | Windows | 2a3077e4 | v1.3.7 已发 |
| #2 | Windows 双击吞掉 | Windows | 2a3077e4 | v1.3.7 已发 |
| #1 | 连接慢 + CPU 100% | Windows | a75a35bf | v1.3.7 已发 |
一次多智能体审查→修复冲刺:降低 Apple 部署目标、修复 Android 安全漏洞、重新生成 CocoaPods 状态、扩大 Linux 发行版覆盖、修正 Plasma 6 检测、让 Windows 清单说真话、 清理 17 个未使用的 Flutter 依赖 —— 每轮都被 codex 反复审查,直到没有发现为止。
UNNotificationContent.interruptionLevel 上加了一处
#available(iOS 15.0, *) 包裹;部署目标在
Podfile、Runner.xcodeproj(9 处)、AppFrameworkInfo.plist 以及 25 个
Pod 中统一下调。修复前主工程降到 14.0,Pods 还在以 15.6 编译。
+ 多覆盖约 3-4% 的活跃 iPhone 用户
VPNManager.swift 中两处
Logger(subsystem:category:)(仅 macOS 11+)改写为已存在的
os_log 模式。Info-SystemExt.plist 中的
LSMinimumSystemVersion 现在绑定到
$(MACOSX_DEPLOYMENT_TARGET)。补上了 DeveloperID 配置缺失的
Hardened Runtime,否则后续 notarize 会静默失败。
+ 覆盖 Catalina(macOS 10.15)用户和最后一代纯 x86 Intel Mac
linux/Dockerfile.compat,基于 ubuntu:20.04
(glibc 2.31)产出公开发布产物。之前的构建容器是 Ubuntu 22.04
(glibc 2.35),静默地产出了在 Ubuntu 20.04、Debian 11、RHEL 8、AL2 上无法
装载的二进制文件。同时修了 docker-compose.yml 指向不存在的
Dockerfile 的引用。
+ 覆盖 Debian 11 Bullseye、Ubuntu 20.04 LTS
runner.exe.manifest 中删掉了 Win 7/8/8.1 的
supportedOS GUID —— 这是个谎言。Go 1.24.7(用于构建
sing-box.dll)早在 Go 1.21 就放弃了 Win7-8.1。捆绑的 VC redist
14.40+ 也只在 Win 10 上能运行。清单现在和现实一致;同时增加
PerMonitorV2,PerMonitor 以及兼容 Win10 1607 的
dpiAware true/pm 后备。
+ 终于给用户一个诚实的安装矩阵(不再让 Win7 用户失望)
"stop_vpn" 广播被以
RECEIVER_EXPORTED 全局导出且没有 setPackage,
任何已安装的 App 都能停掉 VPN。现已切换到
RECEIVER_NOT_EXPORTED + 签名级权限保护 + 包级作用域发送方;
同时加上 Android 13+ 的 POST_NOTIFICATIONS 运行时申请、移除了
Lollipop 死分支、删掉了 multidex 依赖、清理了被 Android NSC 静默忽略的
CIDR <domain> 条目。
+ 第三方 App 再也无法停掉 VPN;Android 13+ 上能看到状态通知
每一轮 codex 审查 diff 并标出兼容性问题,我应用同意的修复,然后再跑一轮。当 codex 返回 clean 时退出 —— 不是凑够计数。
实施团队加上所有 codexloop 轮次的全部变更,按平台分组。
pod install。修复前主工程已降到 iOS 14 / macOS 10.15,但 540 个被跟踪的 Pod 文件仍然以 15.6 / 11.0 为目标。现在已统一对齐;同时移除了 17 个被裁剪的 Flutter 插件对应的 Pod 项。if #available(iOS 15.0, *) 包裹 content.interruptionLevel = .active —— 这是范围内唯一的 iOS-15-only API。同时更新 Podfile 平台行、post_install 钩子、9 处 pbxproj 条目、AppFrameworkInfo.plist 的 MinimumOSVersion。Logger(subsystem:category:) 改写成 os_log(Logger 需 macOS 11+;os_log 一直可用)。LSMinimumSystemVersion 绑到 $(MACOSX_DEPLOYMENT_TARGET);Podfile、post_install、8 处 pbxproj 同步更新。ENABLE_HARDENED_RUNTIME = YES,其它 release 配置已有。否则直接跑 xcodebuild -configuration DeveloperID 后续 notarize 会静默失败。com.novamindllc.vpncheap.action.STOP_VPN,通过 ContextCompat 切到 RECEIVER_NOT_EXPORTED、发送方加上 setPackage(packageName),并声明 AndroidX 在 API 24-32 上要求的 DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION(signature 保护级别)—— 否则 ContextCompat 会抛异常。ActivityCompat.requestPermissions。否则全新安装的 Android 13/14/15 上,VPN 状态通知会被静默压制。已先确认 Dart 层没在做同样的事再加。network_security_config.xml 移除 CIDR <domain> 条目 —— Android NSC 静默忽略 CIDR 段,所以"LAN cleartext"规则其实啥也没干。同时清理不可达的 Lollipop_MR1 分支,并去掉 androidx.multidex + multiDexEnabled true(minSdk 24 后已是死代码)。lib/ 与 test/ 中没有 import,再批量移除:app_links、screen_retriever、geocoding、sensors_plus、wakelock_plus、battery_plus、network_info_plus、upgrader、flutter_login、flutter_downloader、local_auth、camera、image_picker、flutter_local_notifications、permission_handler、cronet_http、cupertino_http(后两者由 native_dio_adapter 传递引入,本地多余)。pubspec.yaml SDK 约束从虚假的 >=3.0.0 改成真实的 >=3.9.0。linux/Dockerfile.compat(ubuntu:20.04,glibc 2.31)用于公开发布产物。把 Dockerfile.modern、Dockerfile.amd64 的 Flutter 钉版从 3.29.2 升到 3.41.2 以匹配 CI;修复 docker-compose.yml 错误的 Dockerfile 引用;在 proxy_manager.cc 中实现 Plasma 6 检测并使用绝对路径探测,适配 PATH 被裁剪的环境。runner.exe.manifest 删掉 Win 7/8/8.1 的 supportedOS GUID(Go 1.21+ 已放弃 Win7-8.1)。补上 PerMonitorV2,PerMonitor 以及 dpiAware true/pm 后备;CLAUDE.md 中 gomobile 引用从 v0.1.4 修正为 v0.1.12(对齐 sing-box 1.13.6 官方钉版及 CI)。两项最高影响修复的可视化:切换"修复前 / 修复后"可看到目标 OS 上的运行时行为变化。
每个关卡都在第 5 轮之后的最终状态上跑过。
xcodebuild —— codex 撞到了签名/沙箱限制;静态分析 + Pods 重生成的一致性顶替DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION 路径docker build 一次 Dockerfile.compat —— 本地 Docker daemon 没启动ios/Runner/Info-Profile.plist 是个 23 字节的非法 plist(只有 $(FLUTTER_BUILD_NUMBER)),已提交于 2025-09。Profile 构建从那时起一直静默失败。建议另开一个修复:把 Info-Release.plist 复制过去。
一次多智能体调查,把 Sentry 上每个 issue 重新审了一遍:发现一个之前漏掉的
27-event WebView bug、修正了一处跨平台错误归类、并把所有修复经过 7 轮 codex
审查直至 clean。所有 issue 标记为 resolved-in-next-release · Vpncheap@1.3.9+79。
每个卡片是一个 Sentry issue 的重新调查结果。条形显示置信度从旧值到新值(1–100)。80 线是"可执行修复计划"的门槛。
Task 闭包跨 actor 边界捕获了非 Sendable 的 FlutterEventSink。关闭过程中,引擎释放了闭包,但 actor task 仍持有 → RefCounts::doDecrementSlow → abort。
与 FLUTTER-1R/1Y 同一根因 —— 主 isolate 上调用 Process.runSync('uname')。已在 update_service.dart:673 中解决。
原审查把它当成 Windows DLL race。重新分析栈帧后发现这其实是同一个 Android Process.runSync 家族的第三种失败模式。
5 个 event 中 4 个确认是连接建立后短窗口内的时序问题。静态审查已排除证书 pinning 误配。在 lib/main.dart beforeSend 里过滤是正确选择。
原审查没覆盖。dart-network-investigator 子智能体找到的:onPageStarted / onPageFinished 直接 setState,没有 mounted 守卫。
符号化受阻,只到 74/65 confidence,但"FreeLibrary 时 Go 运行时线程仍存活"这条机械路径明显。退出时跳过 FreeLibrary;OS 会在进程退出时回收。
45 帧栈 100% 在 FlutterMacOS 框架内。便宜的缓解方式:在 AppDelegate 启动时第一时间 signal(SIGPIPE, SIG_IGN)。
原审查归咎于我们的 runBlocking semaphore。重新看栈:Thread 0 全是 CoreLocation / Pasteboard / 字体 XPC —— 系统框架调用,不是我们的代码。还需上传 dSYM 才能进一步定位。
按 ASLR 模式看是两个不同的崩溃点。1.3.x 上事件数为 0,可能 libbox 升级已修;次要修复:把 attemptReconnect() 的双重关闭做了同步。
2 帧 in-app 100% 混淆。上传 Source map 到 Sentry 即可解锁。静态审查最大嫌疑是 api_path_service.dart:103,但没有栈帧无法确认。
追踪到的四个核心 bug 的实时动画。点击"触发竞态"看出问题的过程,然后"应用修复"看锁结构是怎么挡住它的。
Android 锁所有权修复在 4 轮 codex 之间逐步演进,每一轮都揪出一个更微妙的残留竞态。
val serverToClose = synchronized(vpnStartStopLock) { ... } // vpnFd 在锁外关闭 —— 与 stopVPN 竞态! if (vpnFd != -1) { ParcelFileDescriptor.adoptFd(vpnFd)?.close() vpnFd = -1 }
// 锁内原子快照两个字段 val snapshot = synchronized(vpnStartStopLock) { val server = commandServer val fd = vpnFd commandServer = null vpnFd = -1 StopVpnSnapshot(server, fd, ...) }
commandServerCloseThread = thread // 锁在这里释放 } snapshot.closeThread?.start() // 竞态:thread 已发布但 !isAlive → // waitForCommandServerClose 提前清空标志
}.also { thread -> isCommandServerClosing = true commandServerCloseThread = thread thread.start() // 锁内 }
} catch (e: Exception) { // 不区分身份的 null —— 可能盖掉 // 一个并发 start 刚发布的 server! commandServer = null throw e }
} catch (e: Exception) { createdServer?.let { ours -> val shouldClose = synchronized(vpnStartStopLock) { if (commandServer === ours) { commandServer = null; true } else false } if (shouldClose) { ours.close() } } throw e }
审查中的置信度变化,以及修复落在哪些代码区域。
每个 issue 一行。越过 80 虚线 = 越过"可执行修复计划"门槛。
14 个文件,跨所有平台。
两阶段共 7 轮审查。每轮都揪出上一轮漏掉的真实残留 bug。
找到 1 个高严重度问题:vpnFd 在 stopVPN() 与 attemptReconnect() 都在锁外关闭 —— 同一个 fd 可能被收养两次。
又找到 2 个:(a) isCommandServerClosing 在锁内置位但 commandServerCloseThread 在锁外赋值;(b) attemptReconnect() 外层守卫在锁外读取字段。
找到 1 个高:thread.start() 在锁释放后才调用,存在 isAlive 返回 false 的窗口,会让 waitForCommandServerClose() 提前清空标志。
✓ Clean。"FLUTTER-8 范围内已干净,无残留竞态。"
codex 列出 4 处:vpnFd 在 584、1182、2078,commandServer 在 758。3 处归为真实 bug,1 处只是理论问题。
找到 1 处我引入的回归:startCommandServer 外层 catch 没做身份检查,可能盖掉并发 start 刚发布的 server。
✓ Clean。"对该范围已确认干净。"
所有 issue 已标记为 resolved-in-next-release(Vpncheap@1.3.9+79)。如果有更高版本仍上报同样事件,Sentry 会自动重新打开。
| Short ID | 标题 | 事件数 | 状态 |
|---|---|---|---|
| FLUTTER-7 | _loadOrders 中的 Null check | 4 | resolved-in-next-release |
| FLUTTER-1R | Process.runSync ANR(Android 16) | 3 | resolved-in-next-release |
| FLUTTER-1Y | Process.runSync ANR(Android 16) | 3 | resolved-in-next-release |
| FLUTTER-1P | VPNConnectionException 'No product selected' | 1 | resolved-in-next-release |
| FLUTTER-P | VPN 连上后 HandshakeException | 60 | resolved-in-next-release |
| FLUTTER-16 | iOS PacketTunnel EXC_BAD_ACCESS | 1 | resolved-in-next-release |
| FLUTTER-1K | macOS NSAlert.runModal 在 async Task 内 | 1 | resolved-in-next-release |
| FLUTTER-15 | RangeError 0..8: 100 | 1 | resolved-in-next-release |
| FLUTTER-1N | bionic abort/syscall(原误归类) | 1 | resolved-in-next-release |