新图标在 macOS 26 上终于显示正常了。 深色风格下,箭头和圆环不再糊成一整块,猫的头和耳朵边缘也不再有锯齿。
关闭工作台后,发布说明占用的内存会随之释放。 以前打开过的发布说明页面会一直在后台运行,直到退出 DuoUpdater。
所有版本,最新的在前——和应用自我更新时显示的说明是同一份。下载请到发布页面。较早的版本以英文显示。
新图标在 macOS 26 上终于显示正常了。 深色风格下,箭头和圆环不再糊成一整块,猫的头和耳朵边缘也不再有锯齿。
关闭工作台后,发布说明占用的内存会随之释放。 以前打开过的发布说明页面会一直在后台运行,直到退出 DuoUpdater。
全新的应用图标。 一只蜷起来的暹罗猫,尾巴就是刷新箭头。在 macOS 26 及以后,它会跟随你选择的浅色、深色或着色图标风格。
在低数据模式或按流量计费的网络(比如 iPhone 热点)下,后台检查会先等一等。 回到普通网络后立即补上。手动检查和安装更新不受影响。
因为网络抖动而失败的检查,会在两分钟后自动重试一次。 以前失败的行和警告会一直留到下一次定时检查,默认是六小时后。
内部改进。 发布说明里的图片占用的磁盘空间小了很多,请求 面板不再把一次重新验证的请求算成两次,检查 Muse 时也不再下载它的登录页。
通过自带安装脚本安装的 Homebrew 应用(比如夸克网盘)现在可以一键更新。 以前每次尝试都会报错,说下载的文件里没有安装包。
需要你亲自运行安装程序的 Homebrew 应用,现在会显示更新并附上链接,而不是更新失败。 以前 DuoUpdater 会把整个安装程序下载完,然后报错停下。
能识别的 Homebrew 应用更多了。 Homebrew 以不同名字安装的应用(比如 Visual Paradigm Community Edition)现在也会检查更新,名字相近的另一个应用也不会再被错认成它们。
Homebrew cask 已被下架的应用,不会再被推荐 Homebrew 拒绝安装的更新。 如果应用有自己的更新源,会改用它来检查。
VS Code 的发布说明又能显示了。
内部改进。 Antigravity 和 OpenLens 的更新通过其他途径找到之后,设置 › 诊断 里不会再一直留着它们的警告。
从 blender.org 下载的 Blender 现在也能检查更新,并且一键更新。 以前只有用 Homebrew 安装的 Blender 才会被检查。
Blender 的 alpha、beta 和候选版(RC)各走各的轨道。 DuoUpdater 会识别你装的是哪一种构建,同一种构建出了更新的版本时提醒你。alpha 不会被推荐正式版,反过来也一样。
Muse 的更新又能看到了。 Muse 的下载链接开始要求登录之后,它那一行显示的是错误,而不是新版本。
内部改进。 启动后第一次打开菜单栏弹出窗口和工作台会快一点;duo check 现在会列出检查失败的应用,而不是说一切都是最新的。
DuoUpdater 现在支持意大利语、繁体中文、葡萄牙语(巴西)和土耳其语。 一共十一种语言。德语、西班牙语和日语里译错或显示不全的文字也已修正。“设置 → 文件夹”现在会像访达一样,用你的语言显示文件夹名称。
用 Homebrew 安装的应用,现在通过 Homebrew 更新。 这针对的是由 brew 安装、同时又自带更新器的应用。以前 DuoUpdater 会直接原地更新它们,Homebrew 并不知道,于是下一次 brew upgrade 会把你已经装好的版本再下载、再装一遍。
窗口会在你当前所在的空间里打开。 以前关掉“设置”或其他 DuoUpdater 窗口后再打开,可能会把你切回它上次显示时所在的空间。
一次更新,只通知一次。 有些应用会通过两个来源检查,它们可能会把同一个新版本反复通知。
发布日志不再出现日期在未来的版本。 如果开发者的更新源给出的日期晚于 DuoUpdater 第一次看到这个版本的时间,日志会改用看到它的时间。
备份盘没插着时,工作台窗口又能放进屏幕了,“立即复制”也会告诉你为什么复制不了。 以前窗口可能比屏幕还高,列表滚不到底;“立即复制”点了也没有任何反应。
Xcode 测试版和候选发布版现在可以一键更新了。 在“设置 → Xcode”里登录一次 Apple Developer 账户,DuoUpdater 就会像其他更新一样下载并安装新的测试版和 RC。以前它只能告诉你有新版本出来了。
任意版本的 Xcode 都能和已有的并排安装。 “设置 → Xcode”列出了所有 Xcode 版本,按版本分组,你的 macOS 对应的那一组默认展开。“安装”会把你选的版本作为单独一份放进“应用程序”——比如 Xcode-26.6.app——不替换任何东西;也可以只保存归档(.xip)。登录后还会并入 Apple 自己的列表,新版本几分钟内就会出现。
Apple Developer 登录会自己续上。 Apple 大约八小时后会结束开发者会话;现在只要 Apple 还认得这台 Mac,DuoUpdater 就会在后台取得新的会话——不弹窗、不用输密码。可以在“设置 → Xcode”里关掉。
新 Xcode 版本出现得更快了。 工作日里 Apple 通常发布的时段,DuoUpdater 每五分钟找一次新的 Xcode。
几个应用在开发者改动后又能正常用了。 CapCut beta 和 Superconductor nightly 又能找到并安装了,Superconductor 也会保持现在的应用身份。VLC 3.0.24 和 JetBrains Air 又能显示更新说明了。
现在支持 Qoder CN IDE 了。 Qoder 国内版 IDE 能检查更新、一键安装,更新说明也会显示在窗口里。Qoder CN 桌面应用现在也会显示更新说明了。
Qoder IDE 的最新版本不会再来回跳了。 Qoder 是分批推送更新的,DuoUpdater 现在用和你那份 IDE 相同的方式去问,每次得到的答案都一样。以前提示的版本可能在两个版本之间来回切换,一键安装也可能因为答案中途变了而停在半路。
用 Sparkle 更新的应用,下载的更新过期后现在也能再更新。 如果一个通过 Sparkle 自我更新的应用已经下载了某个版本,之后又出了更新的版本,DuoUpdater 现在会先清掉旧的下载,再装最新版。以前“更新”一直提示现在安装会被撤销,那一行也一直不变。
duo install 会告诉你最新版已经在等着安装了。 如果应用自带的更新器已经下载好了最新版本,它现在会直接说明,并建议退出该应用或运行 duo restart。以前它会错误地说这个应用只能检查、不能安装。
搜狗输入法现在可以一键更新了。 DuoUpdater 会按照这个输入法自己更新的方式来装,装好的应用留在原处;而且会先把你的词库和设置复制一份,所以回滚的时候它们也会一起回来。
放在另一块磁盘上的备份,现在会带上输入法的词库和设置。 以前搬到备份盘上的只有应用本身,所以从那里回滚输入法时,新版本的数据还留在原处。
不再提示那些永远装不上的更新。 JetBrains Air 现在显示的是 Toolbox 真正提供的版本。开发者改名后,留在旧名字下的 App Store 应用现在会告诉你 App Store 实际在更新哪一份,而不是提示一个只会重装另一份的更新。
更多应用的更新说明现在能正常排版了。 用 Markdown 发布更新说明的应用,现在也和其他应用一样显示成原生列表。以前显示的是未排版的纯文本。
“重新启动”不再和应用自己的安装器抢时间了。 当应用已经自己下载好更新、而它的回滚备份该放到外置磁盘上时,这次搬运会等安装器做完再开始。以前它正好发生在 macOS 留给安装器干活的那几秒里。
回滚用的备份现在可以放到另一块磁盘上。 一份备份就是一整份应用的副本,放在启动磁盘上会越积越多。“设置”→“备份”会列出已连接的磁盘和各自的可用空间,已有的备份可以搬过去,新的备份也会在后台写到那里——磁盘没插着时只是推迟,而不是没有回滚点。duo backups 新增了 disks、sync、verify 和 probe。
现在支持千问输入法了。 有新版本时 DuoUpdater 会告诉你,并按这个输入法自己更新的方式来更新它。
更新说明不会再永远停在上一个版本。 如果开发者发布更新的时间比它的更新日志页面早几分钟,旧的说明就会被记在新版本名下并一直留着。CleanShot X 自 5.0.1 发布当天起,一直显示的都是 5.0 的说明。
删除备份现在真的会释放它说释放的空间。 旧版本 DuoUpdater 留下的备份写成了 macOS 拒绝删除的形式,“清理”把副本的大部分留在磁盘上,却照样算作已回收。
“清理”现在立刻就能打开。 以前每点一次,都要把所有已保存的备份重新测量一遍。
WorkBuddy 又能显示最新版本了。 它的更新服务返回的是升级链上的下一步,而不是最新的构建,因此四个 WorkBuddy 版本都停在了两个版本之前。
内部改进。 没有声明自己运行哪个二进制文件的应用(例如 Meta 的 Muse)现在也能读出它的框架和 SDK 了。
通过“重新启动”完成的更新现在也能回滚了。 当应用已经自己下载好更新、你点了“重新启动”时,DuoUpdater 现在会先保存当前版本,这个更新就会和其他更新一样出现在“回滚”里。以前只有 DuoUpdater 自己安装的更新才能撤销。
看看一次更新在应用里改了什么。 对于保存了上一个版本的应用,工作台在“发行说明”旁边新增了“应用包差异”视图:签名与权限、最低 macOS 版本、后台项与登录项、内置的库,以及新增、删除或大小变化的文件。在终端里用 duo diff 也能对比任意两份应用。
应用自己的更新器没有装上更新时,“重新启动”会告诉你。 如果应用关闭了、更新却始终没装上,这一行现在会说明情况,并继续提供“重新启动”。以前转圈停下后什么也不说。
Cherry Studio 又能显示最新版本了。 Cherry Studio 改了 Mac 下载文件的名字之后,DuoUpdater 把一个较旧的版本当成了最新版:较新的副本显示“已是最新”,更旧的副本则被推送那个旧版本。
App Store 更新不会再卡在无响应的辅助程序后面。 以前 DuoUpdater 的辅助程序不再应答时,更新会停在 0%,排在后面的所有 App Store 更新都要等到你退出 DuoUpdater。
“全部更新”不会再重新启动你忽略的应用。 打开“自动重新启动已更新的应用”后,一批更新结束时可能会退出并重新打开已忽略的应用,或者你已跳过其下载版本的应用。
现在支持 Tinycast 和 SuperCmd 了。 Tinycast 或它的 beta 有新版本时 DuoUpdater 会告诉你;除了 SuperCmd 2,现在也能识别开源的初代 SuperCmd。Kimi Code 的更新说明现在也能显示了。
更新说明与提供的版本对得上了。 VS Code 最新的发行说明页面以前是空的;新版本刚出现时,说明面板还可能显示上一个版本的内容。
“发布日志”不会再在每次启动时重复记录同一个版本。 以前 Claude、Raycast、Cline 等应用每次启动 DuoUpdater 都会多出一条重复记录。
工作台侧边栏分成了“应用”“Brew”“回滚”三个标签。 点击标签或在标签上拖动即可切换;搜索会同时筛选三个列表。
内部改进。 对同一个应用的“重新启动”和“回滚”不会再互相干扰;更新源无法再让 DuoUpdater 删除下载文件夹以外的文件;后台检查不会再有让 macOS 询问访问其他应用数据的风险;在“请求”筛选里输入超大的数字不会再让应用退出;在 duo install 中拒绝过的管理员授权,菜单栏现在也会遵从。
iStat Menus 的更新现在能正确识别。 iStat Menus 重新发布某个更新后,DuoUpdater 可能在装好之后仍然提示这个更新,再点“更新”还会报错。
现在支持 Memoh Desktop 了。 有新版本时 DuoUpdater 会告诉你,并能一键更新。
“全部更新”不会再让你重新启动已经被 App Store 重新打开的应用。 App Store 关闭应用、并用新版本重新打开之后,这一行会立刻显示为已更新,不会一直显示“立即重新启动”直到整批装完。
已经自己下载好更新的应用,不会再被重复下载一遍。 有些应用会在后台悄悄下好更新,然后等你重启它。现在在 DuoUpdater 里点“更新”,会直接安装磁盘上已有的那份:几秒钟就装好,不用再下载。以前 DuoUpdater 会把同一个版本重新下载一遍。
现在支持 Cua Driver 和 Petex 了。 两者有新版本时 DuoUpdater 都会告诉你;Cua Driver 还能一键更新,并显示它的更新说明。
同一个更新不会再反复通知你。 当开发者的服务器在两个版本之间来回切换时,以前每次检查都像是来了新更新,又弹一条通知。
刚装好的安装包不会再提示重新安装。 以前它那一行会一直写着会重新打开已下载的安装包。
DuoUpdater 现在需要 macOS 15 Sequoia 或更高版本。 仍在 macOS 14 上的 Mac 会保留现有版本,但不再收到更新;所有 Apple 芯片的 Mac 都能升级到受支持的 macOS。
内部改进。 例行检查不再提前解析下载链接——以前遇到开发者的下载服务器超时,一个本来能用的“更新”按钮可能会短暂消失。
现在支持 Aside 了。 Aside 浏览器有新版本时,DuoUpdater 会告诉你,并显示它的更新说明。
你的 macOS 运行不了的更新不会再提供,而且那一行现在会说明原因。 开发者注明某个版本支持哪些 macOS 时,DuoUpdater 会照着办:升级 macOS 之后,Little Snitch 不再提供一个上限低于你当前系统的版本;Xcode 不再提供需要比你更新的 macOS 的版本;应用需要更新 macOS 的安装包会被拒绝,而不是装上一个打不开的应用。以前只显示一条空白横线的行,现在会写着“尚不支持此 macOS”或“需要更新的 macOS”,点一下就能看到详情。
可以看到每个应用是用哪个 SDK 编译的。 点一下应用名称旁边的标记,详情里现在会写着,例如“使用 macOS 27.0 SDK 编译。”——方便分辨哪些应用已经为最新的 macOS 重新编译过。
分成两个包发布的 Homebrew 应用——一个给旧系统、一个给最新系统——现在会从正确的那个读取。 OnyX 在 macOS 27 上就是这样拆分的,而 DuoUpdater 只看了给旧系统的那个包:要么完全不显示 OnyX,要么拿一个 Homebrew 在 27 上拒绝安装的版本来判断它已是最新。现在你装的是哪一个,DuoUpdater 就跟踪哪一个。
TestFlight 测试版不会再在 TestFlight 启动时丢掉它的更新。 有新版本等着的测试版,以前可能会退回“已是最新”,并一直保持到你再次打开 TestFlight。
Dropbox 和 ToDesk 的一键更新会下载正确的安装包。 在 Apple 芯片的 Mac 上,Dropbox 以前会下载只支持 Intel 的版本,随后被安全检查拒绝;ToDesk 以前提供的是只推送给部分用户的抢先体验版,而不是正式版。
“重新启动”会立刻完成 Spotify 的更新。 当 Spotify 已经下载好自己的更新时,“重新启动”以前要转上好几分钟,更新才会生效。
测试版会跟进到它们的正式版本。 Xcode 测试版现在会收到它的候选发布版,而不是显示已是最新;CotEditor 测试版能看到它的候选发布版;测试周期已经结束的 Carbon Copy Cloner 测试版,会收到它转正后的版本,而不再显示检查失败。
更新说明与你装的版本一致。 以前 Blender 5.2 显示的是 5.1 的说明,Raycast 1.x 显示的是 2.x 的说明。Gemini 的“打开页面”现在会打开 Google 当前的桌面版页面,而不是一个错误页。
设置里的每个选项,说明就在它旁边。 整个区块下面的长段说明,换成了每个控件下方的一行简短文字;选择器只描述你当前选中的那一项。
应用里的名称现在统一写作 DuoUpdater,与网站一致。
来自你尚未信任的 tap 的 Homebrew 包,重新显示出来了。 从 Homebrew 6 起,brew 在列出已安装内容时会悄悄跳过未信任 tap 里的包,于是它们从 DuoUpdater 里消失了,也没有任何解释。现在它们会显示为“未检查 · tap 未被信任”,并附上可以直接复制的 brew trust 命令——你在终端里运行它(或升级)之后,切回窗口就会立刻跟上。
需要管理员密码才能自我更新的应用,不会再被重复更新。 当 Tailscale 这类应用已经下载好自己的更新、正等着重启时,DuoUpdater 看不到这一点,照样提供“更新”,这可能与应用自己的安装程序撞在一起。现在这一行会改为提供“重新启动”,“全部更新”也不会碰它。
Homebrew 区域现在可以更新 Homebrew 本身。 Homebrew 有新版本时,顶部会出现一行,替你运行 brew update。如果你设置了 HOMEBREW_NO_AUTO_UPDATE,这一行就不会出现。
Homebrew 升级失败时,现在会显示 brew 真正的报错。 以前这一行显示的是 brew 在报错之后打印的建议,或者干脆什么都没有,于是“命令行工具版本过旧”这样的信息始终到不了你眼前。一次升级多个包时,也不会在还没做完时就报告已完成。
菜单栏图标可以数到 50 以上了。 以前待更新的应用超过 50 个时,图标会一直停在 50。
更新说明:Claude 的说明按“新增”“改进”“修复”分组,和 Claude 自己的显示方式一致;Mac Mouse Fix 的说明以文本显示,而不再是嵌入的网页;Homebrew 自家的应用现在也有更新说明了。
duo check 在其实没能检查时,不会再说“Everything is up to date.”。 当它读不到 TestFlight、或应用扫描中途放弃时,现在会如实说明。
当应用用多种语言发布更新说明时,现在会显示你自己语言的那一份。 有些应用会随每次发布附上各语言的说明;DuoUpdater 以前只会取应用碰巧排在最前或最后的那一份译文,于是有的应用的说明对所有人都显示成德语,还有的应用每次发布显示的语言都不一样。
已经推出 Mac 版本的应用,不会再显示“此 Mac 不支持”。 你在 Apple 芯片上运行的 iPhone 或 iPad 应用,恰恰在开发者发布了真正的 Mac 版本那一刻被误标成不支持——而那次改动本该让更新更容易拿到,而不是更难。
你同时在测试 beta 的 App Store 应用,不会再被当成 TestFlight 版本。 当开发者把某个 beta 原封不动地转成正式版时,两者的构建号是一样的,你买来的那份拷贝就被交给了 TestFlight 处理——于是 App Store 永远没法给它提供更新。
需要比你当前系统更新的 macOS 才能装的 App Store 更新,现在会直说。 以前这一行照样提供安装,然后 App Store 在最后一步拒绝,屏幕上没有任何解释。
“请求”窗口现在会显示它的记录实际能回溯到多久,并标出它无法完整覆盖的时间范围。 以前在一份只有几小时记录的日志上选“最近 30 天”,看起来和选“最近 24 小时”一模一样,屏幕上没有任何解释。
DuoUpdater 对 TestFlight beta 管到什么程度,现在由你决定,在“设置”→“通用”里。 选“我刷新时”,它读取 TestFlight 已经知道的内容,并在你按下刷新时请 TestFlight 给一个新答案;选“保持最新”,还允许 DuoUpdater 自己去问,于是在后台装好的 TestFlight beta 不会再挂着一个问号等你刷新,正等着你的构建也不会再被藏在“已是最新”后面;选“关闭”则什么都不读,并在那些行上如实说明,而不是猜。已经有完全磁盘访问权限的 Mac 从“我刷新时”开始,其余的从“关闭”开始。
你自己打开一次 TestFlight,DuoUpdater 就能发现它装了什么。 以前你通过 TestFlight 装的 beta 会一直挂着问号,要等到下次刷新——而在 macOS 27 上,TestFlight 对你已经装了的应用不再发出“可以测试了”的提示,这一等可能是好几个小时。
Cline 现在能收到更新了,正式版和 beta 版都可以,并且会显示它的更新说明。 在此之前它那一行只有一个问号而没有版本号——它没有提供 DuoUpdater 读得懂的那类更新源,也没有对应的 Homebrew 包。现在 DuoUpdater 会去问 Cline 自己的更新程序所问的那个地址,因此提供给你的更新就是 Cline 自己会装的那一个,beta 版也会留在 beta 这条轨道上。
把改动按“新增”“修复”这类标题分组的更新说明,现在会保留这些标题。 以前所有分组会被合并成一份平铺的列表,你分不出哪些是新功能、哪些是问题修复。
如果你在 Mac Mouse Fix 自己的通用设置里打开了“Get Beta Versions”,它现在会提供 beta 版本。 以前 DuoUpdater 只看得到 Mac Mouse Fix 的正式发布,于是 beta 版本会一直无人察觉,直到下一个正式版本发布。
构建号只是一个流水计数的应用,不会再把自己的修订版本藏起来。 有的应用报出的版本是 12.10、构建号是 282987,那么 12.10.1 这次发布以前会被读成“已是最新”。
版本名不变、只换了构建的应用,现在会再次通知你。 以前只要某个构建通知过一次,同一个版本名下后来的每一个构建都是悄无声息地到来——那一行亮起来了,角标也数上了,却始终没有通知弹出来。
“全部更新”现在只统计真正更新成功的应用。 需要打开 Apple 的安装器由你来完成的应用,以前在它的窗口还开着的时候就被算作完成了,于是“已更新 2 个应用”可能意味着什么都还没变。
已经装好、只是留下了一点残留的更新,现在会报告为已安装,而不是“授予应用管理权限”。 那时新版本其实已经在运行了,而那一行还在把你送去系统设置。
中止“全部更新”,现在会一并停下正在进行的下载。 以前几个 GB 的传输会一直走到结束,还要重试最多五次,然后才发现它早就被取消了。
共用同一个更新说明页面的两个应用,不会再把说明弄混;而说明分散在各版本页面上的应用,更新之后显示的也是最新的那一份。 Antigravity 和 Antigravity IDE 曾经会有一刻钟的时间互相显示对方的说明;Thunderbird、WeChat、Opera 等应用在更新后会有一段时间继续显示上一个版本的说明。
在应用列表里搜索时会忽略变音符号,和设置里的搜索一样。 现在输入“cafe”也能找到“Café”。
“诊断”页面现在会按发布渠道分别列出健康状况。 以前坏掉的 beta 或预览版规则会被健康的正式版规则挡住,看不出来。
GitHub 返回的“禁止访问”不会再被报成触发了频率限制。 以前仓库转为私有、或者令牌缺少某个权限时,会引导你去添加一个其实没用的令牌。
对于自己更新的应用,如果它等着装的版本比正在运行的还旧,就不会再提供“重新启动”。
内部改进。 安装、备份和安装包校验不会再占用应用其余部分要用的线程,因此进行这些操作时菜单依然跟手;大文件下载期间菜单也保持流畅;“发布日志”现在会把厂商在同一个版本名下发布的每一次发布都算进去;缺少应用自身可执行文件的回滚备份会被拒绝,而不是照样存下来;在一台全新的 Mac 上首次启动不会再记录莫名其妙的数据库错误;duo verify 和 duo reconcile 现在会报告条目塌陷的更新说明,以及连续多日失败的安装包地址;卡住的 duo 命令会在二十秒后放弃扫描,而不是一直挂着。
有些自己更新的应用,不会再在已经有更新版本的时候看起来还是最新的。 更新信息放在刷新很慢的下载服务器后面的应用,在发布之后还可能连着好几天都只被看到旧版本。
Kimi 的发布说明现在会出现在 DuoUpdater 里。
CodeEdit 的更新现在会出现了。 以前有了更新的 CodeEdit,那一行会停在一个问号上,而不是给出更新。
内部改进。 菜单里的 Homebrew 列表填充得更快,duo 命令的启动也更快了。
对 TestFlight beta 点“重新检查”,现在会给出真正的结果。 以前这一行会变成一个问号,一直到下次刷新。
刷新按钮去问 TestFlight 的时候,TestFlight beta 会保持原来的结果。 以前它们会有几秒钟全部变成问号。
现已支持 super.engineering:检查更新、发布说明和一键安装。 新的 nightly 版本会连同其中的改动一起出现在列表里,点“更新”就会替你装好。
Microsoft Edge Beta 此前可能会给你提供正式版的 Edge。 微软的 beta 源曾短暂为空,那段时间里,这一行给出的是一个正式版的版本号,要是点了更新,就会把它装到你的 beta 上面。
TestFlight beta 现在会显示 TestFlight 为它们提供的更新,包括 iPhone 和 iPad 应用的 beta。 刷新按钮会在后台去问 TestFlight,更新不会再在你看到几分钟后就消失,TestFlight 按钮也会打开这个 beta 的页面。
当 DuoUpdater 无法判断某个 TestFlight beta 是否已是最新时,它会直说。 这一行会显示一个问号,而不是说它已是最新——比如你在 TestFlight 里退出了登录,或者已经不再测试这个 beta 的时候。
完全磁盘访问权限现在有了说明,没有它也不会再有什么来烦你。 没有这项权限时,DuoUpdater 不会再惹得 macOS 弹出关于读取其他应用数据的警告;如果某个 TestFlight beta 或 CotEditor 需要它,DuoUpdater 会告诉你一次:为什么需要它,以及去哪里授权。
更新过的应用一旦退出,“重新启动”就会消失。 有的应用退出后还留着辅助进程在运行,它的更新早就生效了,却还一直要求重新启动。
没人回应的 macOS 隐私提示,不会再让检查更新卡住。 即使拿不到那个应用的设置,检查也会继续进行。
Rockxy 和 Ollama 的发布说明又完整了。 Rockxy 的发布说明会回溯到最近的几次发布,而不只是最新的那一个;Ollama 最新的那次发布也不会再被漏掉。
duo,那个可选的命令行伙伴,对 TestFlight 的处理更好了。 无论你是否开着 TestFlight,--refresh-testflight 都能正常工作,而且不会抢占你的屏幕;当 TestFlight 已经公布了更新的构建时,duo check 也不会再把 beta 说成已是最新。
Telegram Desktop 又能检查更新了。 Telegram 改掉了它发布的那个文件的名字,行里再也读不出版本号——于是它显示的是一次检查失败,而不是背后正等着的那个更新。
通过 TestFlight 装的 iPhone / iPad 应用,现在会被识别成它本来的样子。 DuoUpdater 之前把它读成了一次 App Store 购买,于是这一行写错了是谁在托管它,同时还在向商店打听一个根本不存在的条目——每次检查都问一遍,只要这个应用还装着就一直问下去。
TestFlight 的 beta 会用 TestFlight 自己的图标标出来。 由 App Store 照看的行早就带着商店的图标;由 TestFlight 照看的行却是把名字写出来,于是同一类行有了两种标记方式。
切换标签页时,网络窗口的表头不会再动了。 它的两个标签页把标题放在了略微不同的高度上,所以在两者之间来回切换时,窗口看起来像是抖了一下。
滚动整个 app 列表又流畅了。 快速滚动完整列表时会掉帧;现在每一行不必先被构建出来就能报出自己的高度。
App Store 上的 app,发布说明现在一定来自 App Store。 当商店自己的查询没命中或失败时,窗口可能会退回去显示这个 app 另一条分发渠道的说明——那是一个自成体系的版本号的构建——讲的是一个你这份副本根本不会拿到的版本。
Windscribe 切到 Beta 或 Guinea Pig 通道时,拿到的是那条通道的构建。 DuoUpdater 会读你在 Windscribe 自己的设置里选的更新通道,所以一份跟着预发布线的副本,不会再在那条线上明明有更新版本时被告知已是最新。窗口里也会显示这些预发布构建的说明,以前只列正式版的。
Windscribe 现在会检查更新了,并带发布说明。 跑着旧构建的副本会列出它可以升到的版本以及其中改了什么;在此之前,DuoUpdater 根本读不到 Windscribe 的版本。更新仍然通过 Windscribe 自己的安装器完成,因为它要配置一些位于 app 之外的部分。
一个在你点击期间消失了的 app,不会再被装上更新。 如果在你点下去到安装真正开始之间,这个 app 被卸载、被替换、或者变得读不出来了,DuoUpdater 现在会停下来并说明这件事,而不是照样往那个位置装。
duo,那个可选的命令行伙伴,不再把一次 pkg 安装说成已经完成。 安装以 .pkg 形式分发的 app 时,会打开 macOS 的安装器、剩下的交给你,但摘要过去把它算成已安装——明明还什么都没替换,却写着“1 installed”。现在这两者分开计数。它的 --json 输出也会给每一行标上这个 app 到底怎么了,于是脚本不必再去读那句英文说明才能分清失败和有意跳过。
内部改进。 保护一键更新的那道安装前复查,现在也保护 duo install;而一个下载在替换 app 之前必须通过的那些检查,两条用到它们的路径现在共用同一份。
更新源自相矛盾时,点“更新”不再毫无反应。 点下去那一刻跑的复查如果回来一个比行上原本提供的更旧的版本,DuoUpdater 现在会说明这件事,并把这次更新留着。以前它会把这个 app 判成已是最新、从列表里拿掉,而同一个更新在下次检查时又冒出来。
Fork 切到 Develop 通道时,更新又能提示了。 DuoUpdater 把 Fork 的通道设置读反了,跟的是落后不少的 Stable 源——于是 Develop 的副本显示为已是最新,而 Fork 自己早已放出了更新的版本。
Mac Performance Monitor 现在能看到发布说明了。 这个 app 把说明发在自己的仓库里,而不是我们读的那个源,所以窗口里一直没东西可显示。
CotEditor 现在覆盖了,正式版和 beta 两条线都覆盖。 一份副本跟哪条线,由它正在运行的版本和 CotEditor 自己的“Update to prereleases when available”设置决定,所以 beta 副本拿到的是下一个 beta,而不是一个会让它倒退的正式版。
从 App Store 装的 app,绝不会被提供来自别处的下载。 当商店自己的查询失败或返回为空时,这一行现在会说明由商店托管,并且不显示版本号。在此之前,检查可能会转到这个 app 的另一条分发渠道——一个自成体系的版本号的构建——并提议把它装到你的商店副本上面。
绝不会给一个 app 提供会让它退回旧版本的更新。 有些源会把正式版排在实际上更靠前的预发布版之上,装下去会让这个 app 倒退。
一行里的长错误不再把列表其余部分往下顶。 它被限制在两行内,完整文本悬停可见。
又多覆盖了四个 App:WhatCable、Qoder IDE、Qoder 和 Yaak。 每一个都能检查更新、一键安装,发布说明也是作为文本读进窗口,而不是嵌一张网页。
Qoder 的两个 Mac App 现在分得开了。 IDE 和桌面版共用一个名字和一张下载页,但版本线是各走各的,所以现在各自单独跟。
WhatCable 和 Yaak 的 beta 版有了自己的轨道。 跑 beta 的副本以前根本没有来源,会一直停在“失败”上;现在会拿到下一个 beta,发布说明也和正式版的分开。WhatCable 还包括 beta 最终转正的那个正式版——接受它,这个副本就走到正式轨上去了。
检查 App Store 的 App,用掉的流量只剩以前的一小部分。 以前每次检查都会把每个 App Store App 的产品页重新取一遍;现在这些页面会保留一小时,而且是用几次请求问完你所有的 App,不再一个 App 一次。检查间隔设成五分钟时,整体流量大约少三分之一;默认的六小时间隔下页面在两次检查之间就过期了,省下的没那么多。
重新检查一个 App,不再顺带重取所有 App Store 的 App。 以前按一次“重新检查”会把缓存的产品页全部丢掉,下一次定时检查就得为全部买单;现在只刷新你点的那一个。
检查通过 GitHub 分发的 App,流量同样只剩以前的一小部分。 以前哪怕什么都没发布,每次检查也会把每个发布的完整说明重下一遍;现在会先问 GitHub 这个发布自上次以来变没变,没变就什么都不下。每天会把每个发布完整重读一次,所以一个被撤回的发布一天之内就能发现。
跟着 GitHub beta 或 nightly 轨道的 App,现在只要一条发布,不再要一整页。 绝大多数时候答案就是最新那条,只有在不是的那几轮才会去取整页。
Vorssaint 的更新检查不再经过一次跳转。 它的仓库改过名,跟着旧名字走会悄悄把请求落到 GitHub 的匿名频率限制上;现在直接走新名字。
内部改进。 发布产物现在在 GitHub 托管的 Mac 上构建、签名和公证,构建来源任何人都可以验证;记录下来的请求日志也会区分一次回答是来自缓存还是来自网络。
导出的请求日志不再带上你的账号名。 装在个人目录下的 App,以前每一行都会把完整路径写出来;现在一律显示为 ~,无论你用哪种方式把日志取出来。
“请求”标签页现在会说明自己记了什么、没记什么。 它记录的是 DuoUpdater 自己发出的请求。发布说明页面会自己去取图片和字体,App Store 和 Homebrew 的更新由各自的工具完成——这些都不会出现在里面,现在窗口会直说,而不是留给你去猜。
“复制 URL”现在会把地址转义好再给你。 路径里带空格的(Firefox、Thunderbird、Bartender 的下载地址都是),以前是原样复制的——浏览器会替你补上,命令行不会。
CapCut 的 beta 行不再报检查失败。 一个 beta 转正、下一个还没开的这段空窗期里,厂商在那条轨道上什么都不发布——以前这会显示成一行红色,外加一个按了也没用的“重试”。现在这一行只是暂时没有来自那个来源的答案,直到下一个 beta 出现。
Audacity 现在会显示它是用什么构建的那个标记。 它由一个小启动器拉起,再交给旁边真正的程序,而标记一直是从启动器上读的——那个启动器什么都没链接。
App Store 的发布说明现在会用你的语言显示。 以前一律按商店自己的默认语言取,所以系统语言是中文或日文的 Mac 读到的仍然是英文。
DuoUpdater 自己的更新现在会记在它自己名下。 它的版本检查、发布说明和下载,在“网络”窗口里的 App 一列一直是空的。
内部改进。 藏在网址路径里的凭据,现在会在请求被记录之前去掉,就像查询串里的凭据一直以来那样。
“Download Traffic”窗口现在叫“网络”,并多了一个标签页。 “下载”还是你原来那份账本——每次更新作为文件花了多少。“请求”是新的:DuoUpdater 替你发出的每一次请求,为了什么、属于哪个 App、花了多少,并且带一个筛选框,让你可以向这份日志提问,而不是一路翻。顶部的数字回答的是你当前筛选出来的范围,不是历史总量。
检查 Spotify 更新几乎不再耗流量。 Spotify 没有在任何地方公布版本文件,所以检查是从一个两兆的安装包存根里读版本号——而它以前每次检查都把整个文件下一遍,全天如此。现在它会先问这个文件变没变,没变就不下载。
PDF Expert 现在能找到自己的更新,也会显示改了什么。 它读的是一份开发方从 2022 年起就不再更新的发布列表,所以无论你装的是哪一版,它都说“已是最新”。现在它跟着这个 app 自己的更新程序所用的那份列表走,并且现在也能看到以往版本的更新说明,而不只是最新的那一条。
UTM 的预览版现在能收到自己的更新、支持一键安装,并显示对应的更新说明。 预览版副本以前会被拿去和正式版比较、静默地被告知“已是最新”;现在它跟着自己那条线走,而一旦装上这条线转正后的版本,就回到正式轨。
内部改动。 记录下来的请求搬进了和下载账本同一个存储,所以同一次下载的两份账目不会再对不上。你已有的账本原样保留。
又有十五个 app 纳入更新检查,除一个之外都支持一键更新。 AgentsView、AnythingLLM、Chatbox、ChatGPT Classic、DSH Desktop、FluidVoice、GitHub Copilot、Kun、Meetily、Microsoft 365 Copilot、OpenLogi、OpenSuperWhisper、Paseo、T3 Code(alpha 与 nightly 两条线)以及 Vorssaint。例外是 ChatGPT Classic:更新检查照做,但它自带一个会自己更新应用的安装程序,所以这一个仍需你亲自运行。
八个 app 的更新说明现在直接在 DuoUpdater 里显示,不再把你带去网页。 Xcode、Antigravity、Antigravity IDE、AnyDesk、AnythingLLM、Chatbox、Headlamp 和 Helium。其中三个原本连可去的网页都没有:AnyDesk 的页面返回的是机器人校验,Helium 的开发方根本不发布说明页,而 Antigravity IDE 连链接都没有。
Xcode 的 beta 版会告诉你每个 beta 改了什么。 过去这一行只有一个 build 号和一个链接;现在它会列出 Apple 为你所用的这个 beta、以及同一版本此前每个 beta 写的更新说明。
Word、Excel、PowerPoint、Outlook 和 OneNote 现在知道自己的更新已经装好了。 这五个应用会一直提示你重新打开那个你早已完成的安装器,永远走不到“重新启动”那一步——等多久、重新检查多少次都没用。
等待重新启动的行,重新说得清自己了,两个窗口都是。 当一个应用既领先于厂商发布的版本、又在等待重新启动时,按钮上方那行字讲的完全是另一回事;而且对于等待“全部更新”重启的行,工作台和菜单的说法还互相矛盾。
俄语界面里,状态标签不再把应用名挤掉。 顺带修好了所有语言里几个从无关文案借词的状态标签。
厂商只写了发布日期、没写具体时间的应用,现在也有更新说明了。 这类发布以前在任何地方都不留痕迹。
我们拒绝打开的更新说明页面,现在会告诉你原因。 之前是一片空白,看起来就跟这个应用根本不发布说明一样。
微信开发者工具的 Nightly 更新说明不再是空的。
底层改动。 版本号与发布日期的处理收敛到了一处,构建号不会再被当成营销版本号读;发布时间线也不会再为厂商没给出的时刻编一个出来。
检查失败的应用,现在在工作台里看得见了。 之前工作台对检查失败的行什么都不画——被忽略的、已跳过的、由 App Store / Toolbox / TestFlight 管理的也一样——看起来和“已是最新”完全一样。现在两个窗口对同一个应用的说法一致,重试按钮两边都有。
任意一行右键“重新检查”,只问这一个应用。 它同时会重新确认哪些应用正在运行,所以绿点看着不对时,用它纠正最快。
跳过和忽略,现在也能在工作台的右键菜单里撤销。 这两种行都提示你右键撤销,而这个窗口的菜单里原本没有这一项。
后台检查不会再把你正在读的更新说明抽走。 每小时一次的检查会丢弃所有已加载的说明,正开着的说明面板会闪回加载中。现在只有你主动刷新才会重新开始。
断线续传的下载,会先核对再算完成。 服务器重发整个文件、或者提前结束,之前都被照单全收,问题推迟到下一步才暴露成“安装包损坏”。经由某些代理时会永久失败的下载,现在也能完成了。
更新一个应用,不再重新扫描磁盘上的所有应用。 之前每点一次,都要把你所有的应用完整扫两遍,只为了看你点的那一个。
Chrome 的更新说明不会再卡住。 Google 博客一次普通的改版就足以让它停上几分钟;现在无论页面长什么样都能很快读出来。
已经下载好的安装包,不会再被丢掉重新下载。 厂商把同一个版本写成两种形式(v1.2.3 和 1.2.3)时,等待中的安装包认不出来,而“重新启动”可能一直在等一件其实已经完成的事。
管理员安装失败,不再被当成你点了取消。 之前不会显示任何错误,那个应用还会悄悄不再提供一键更新,直到你从它的行菜单里重新申请管理员权限。
绿色的“运行中”圆点,现在认得所有应用。 有些应用启动或退出时 macOS 根本不发通知,它们的圆点会一直停在错的状态,直到别的操作碰巧把它刷新。
刷新过程中“全部更新”按钮不再闪进闪出,刷新途中更新过的应用,也不会被重置成再次提示更新。
按下更新那一刻检查失败,会明确告诉你,而不是被归入“没有要做的事”。
底层改动。 用非常规格式书写的发布日期现在能正确读出,更新说明页面的地址检查覆盖了所有等价写法,而日常记账不再在你 Mac 上每次开关应用时都去读一次磁盘。
Docker 的技术标记现在描述的是 Docker 的界面,而不是它的后台服务。 那一行写的是“原生”;而 Docker Desktop 是一个 Electron 应用。这个标记是从应用包里读出来的,而 Docker 的应用包是一层外壳:它指名的那个程序是用 Go 写的后台服务,自己不带任何框架,真正画窗口的那个应用还在它里面一层。每一步都读对了,只是读错了文件。现在 DuoUpdater 会去看内层的那个应用——但只在外层自己什么都提供不了、且里面恰好只有一个能自证技术栈的应用时才这么做,所以随真正界面一起分发的辅助进程仍然无法把自己的身份借给宿主。Docker 现在读作 Electron 42.5.0;在写下这段话的机器上,一百四十六个应用里只有这一行会变。
CleanShot X 的发布说明重新能读出来了,包括 5.0 的。 CleanShot 发布了它这些年来最大的一次更新,同时把发布说明页面重建了:日期挪到了版本号上面,版本号外面多包了两层,功能版本还会在版本号和更新列表之间插进一段介绍和两个视频链接。DuoUpdater 用来读它的那套规则一条都对不上了。比“面板空着”更糟的是它看起来并不空:之前保存下来的上一版说明仍留在屏幕上,顶着新版本的标题——面板写着“5.0”,下面却是 4.8.10 的改动,而任何地方都没有提示这一点。
在版本发布之前抓到的说明,现在会被重新读取,而不是永远当真。 这是同一件事的另一半。DuoUpdater 比 CleanShot 公布 5.0 的内容早了六分钟就知道 5.0 存在,于是把当时的页面原样存下、归到 5.0 名下——从那以后每次检查都发现“已经存过了”,再也没去看过。现在已保存的说明在被当作定稿之前,每次启动会先向厂商确认一次,所以说明来晚了的版本会在你下次打开时自己补上,而不是一直错着。
从菜单栏里打开的东西,现在第一次点击就能打开。 在某一行上选“更新日志”——或者设置、发布日志——第一次完全没有反应,从第二次点击起才正常。重新打开菜单会让每一次尝试都重新变成“第一次”,所以对于打开菜单、点一下、然后期待窗口出现的人来说,它从来就没成功过。菜单里的第一次点击被拿去把 DuoUpdater 切到最前面了,而不是去做它本来要做的事。打开的窗口现在也会被按在最前面:它此前会被抬上来又在同一瞬间被压回去,那是这件事看起来“点了没反应”的另一种成因。
应用自己不写名字,现在也会有名字。 欧路词典在列表里有图标、有版本号,名字那一格却什么都没有。它的应用包确实声明了显示名称——然后留空,因为真正的名字放在应用的各语言翻译里——而 DuoUpdater 把这个空答案当成了答案,没有接着往下问。现在它会退回到应用的另一个名称,再退回到应用文件本身的名字,所以不会再有哪一行是没名字的。本机一百五十个应用里只有这一个中招;重点在于信息本来就在那儿,只是被跳过了。
重启失败时会告诉你原因,而不是像点了个没反应的按钮。 如果应用有一个窗口在等你——保存提示、登录面板、某个对话框——macOS 就不会让它退出,这一侧对此无能为力:那是应用自己的窗口。以前 DuoUpdater 会空转三十秒,然后把同一个按钮原样放回来,不作任何解释,这看起来就是按钮坏了,于是人们会再按一次。现在那一行会说明发生了什么,并指引你去处理那个窗口。什么都没有被改动,新版本也已经装好了,剩下的确实只有重启这一步——而且只要你自己在接下来十分钟里退出该应用,DuoUpdater 会察觉到,并在新版本上把它带回来,你一下都不用点。
欧路词典的发布说明重新变回一个版本,而不是十六年份的。 这个应用会把它的全部历史——一直回溯到 2.5.0 的每一个版本——塞进当前最新那一版的说明里,于是详情面板把这些全都摆在“26.9.0”这个标题底下,你得翻过十年才能找到刚刚改了什么。现在每个版本都在列表里各占一条,和其他所有应用一样。
说明应用用什么做的那个标记,对 Tauri 现在要的是证据,而不是“像”。 Tauri 在应用包里什么都不留——没有框架,也没有自己的目录——所以那个标记只能靠打包方式,加上“它链接了 Apple 的网页视图”这一条推出来。Longbridge 这几条全中,但它不是 Tauri:它用的是和 Zed 同一套渲染框架来画自己的窗口,只在界面的一角内嵌了网页视图。现在 DuoUpdater 会先从二进制里读出 Tauri 自己的指纹,再下这个判断,所以被叫作 Tauri 的都确实是 Tauri——而 Longbridge 显示成了它本来的样子:一个原生 Mac 应用。
CapCut 不会再因为字节跳动的服务器卡了半秒就变红。 DuoUpdater 用来问 CapCut 版本的那个接口会返回成功状态码,然后大约每五十次里有一次,给回来的不是答案而是一个错误对象——字节跳动自己基础设施内部的一次超时,大约 390 字节,而正常应该是 436 千字节。这一侧没有任何办法把它和“CapCut 改了应答的结构”区分开,于是那一行显示检查失败,读起来就是“这坏了,得有人去修”,而它其实下一次重试就好了。现在这个特定的形状会被认出来:请求会立即重试,只有当它连续五天都这样,才会被当成真正的问题报出来。
列表里的每个应用现在都会说明自己是用什么做的。 App Store 管的应用一直带着商店的标记,其余每一行看起来都一样——Sparkle 应用、Electron 应用和原生应用完全分不出来。现在每个名字旁边都带着那项技术自己的标志——点它能看到名称和一句说明,悬停则出提示:Electron、Tauri、Flutter、Qt、Java、Chromium、Mac Catalyst、在 Apple 芯片上运行的 iPhone 应用,或者原生 Mac 应用——凡是能当作事实读出来的,还会附上运行时的版本(Electron 42.4.1、Qt 6.2、应用内嵌的 Chromium、构建它所用的 Tauri)。这些都是从应用包本身读出来的——打包者不得不带上的框架、启动器需要的运行时、二进制链接的库——所以它是关于“装的是什么”的事实,而不是从应用名字猜出来的。名字太长放不下时,标志会让位,而不是把名字挤到第二行:这一行属于名字。整个功能可以在设置 → 通用里关掉。
LibreOffice 不会再声称“可以降级到你已经装着的那一版”。 它的下载索引列的是三段式版本(26.8.0),而装好的副本报的是四段(26.8.0.3),把缺的那一段补成零之后,装着的那份反而显得更新——于是那一行给出了一句低调的“厂商落后于你”,而这个应用其实正是最新的。现在,来源发布的版本段数比应用报的少时,会被理解成在描述同一个发布,而不是更旧的发布;真正的回滚照旧会说出来。
更新带来的新设置,现在会主动指认自己一次。 一个落在设置窗口中间的新选项,是一个没人会发现的选项。在带来它的那次更新之后,菜单栏的齿轮上会有一个蓝点,它所在的那一页在侧栏里也有一个,控件本身也有一个——直到你看过它为止。只对真正更新过来的人显示:全新安装不会被一堆蓝点迎接,那些功能对它而言本来就是应用的一部分。
上个版本加进来的 Electron 应用,现在真的能一键更新了。 0.3.75 教会了 DuoUpdater 读这些应用自己包里的那个文件,它也确实读到了——算出了新版本、算出了适合你这台 Mac 的下载、连校验用的指纹都算好了,然后无处可送。更新按钮从来没出现过,而 duo install 给出的拒绝理由也不是真正的原因。这一版把安装这条路接上了。实际上你可能完全看不出区别:这个读取方式排在每一条手写规则的后面,所以它只为“没有别的东西覆盖”的应用说话——而今天,在打包这个版本的机器上,这样的应用一个都没有。它的意义在于你明天装的那个还没有人写过规则的应用。
会把你从原生构建换成 Intel 构建的下载,现在会被拒绝。 Apple 芯片仍然能通过转译运行 Intel 应用,这意味着一个纯 Intel 的下载能通过“这台 Mac 跑得起来吗”这道检查、然后干干净净地装上去——把一个本来在原生运行的应用悄悄变成转译运行的副本,而且往后每次更新都会重复一遍。现在每一次就地安装都会拿你手上的和下载来的比一比,拒绝这种交换。反过来、或者从通用构建换成 Apple 芯片构建,都是正常的,照常放行。
当它无法证明一个下载是什么架构时,现在会选择不给,而不是猜。 有些厂商会在默认下载旁边另发一个 Apple 芯片版本,而要判断出默认那个是 Intel 版,唯一的办法是注意到旁边还有一个。如果这第二次检查没有干净地回来——厂商的服务器拒绝了它、连接断了、或者两边对“这是哪个版本”说法不一致——DuoUpdater 不再把这种沉默当成答案。它会告诉你版本号,但不提供安装,这是诚实的做法。
坏掉的规则不会再从“诊断”里消失了。 对于同时被手写规则和新的读取方式覆盖的应用——按设计,这是绝大多数——规则的一次失败会被紧接着读取方式的一次成功抵消掉,于是一个规则其实已经坏了的应用看上去依然健康。现在两者分开记录。这个读取方式也第一次会报告自己的失败:打包这个版本的机器上有三个应用,它们指向的地址其实已经返回“找不到”有一阵子了,而在此之前没有任何东西会把这件事说出来。
能看见更新但装不了的那些行,现在在两个地方给的东西一样了。 菜单栏上原本显示一个写着“打开”的按钮,点下去打开的是访达;而窗口里对同一行什么都不显示。
用 Electron 打包的应用,现在不需要有人先把它写进名单就能被认出来了。 相当多的 Mac 应用会在自己包里放一个小文件,写明它的更新在哪儿。此前 DuoUpdater 只认识那些有人手写过规则的,其余的就静静躺在列表里,旁边没有版本号,也没法告诉你新构建已经发布了。现在它会直接去读那个文件,和它一直以来读 Sparkle 的方式一样——于是这类应用在你装上它的当天就被覆盖,而不是等到有人抽出空来的那天。
已经有手写规则的应用不受影响。 新的读取方式排在它们后面,所以它只能补上空缺,永远不会接管原本已经在正常工作的东西。
有两个细节决定它给出的下载对不对,而这两个都是拿真实应用核过、不是想当然。 有些厂商即便同时发布了 Apple 芯片版,也仍把 Intel 版标成“主要”下载;另一些则给 Apple 芯片版起了一个和 Intel 版看不出区别的文件名,光看名字什么也发现不了。DuoUpdater 按架构挑选;凡是不能确定某个下载能在你这台 Mac 上跑起来的,它会告诉你版本号但不提供安装——而不是递给你一个装得干干净净、然后打不开的东西。
如果你用的是某个应用的 beta、候选版或每夜构建,DuoUpdater 此前一直在盯错轨道。 它判断你在哪条轨道,靠的是在厂商自己的发布列表里找到你手上这个构建——但预发布版本往往沿用它所源出的正式版号,这本来就是预发布的常态,于是它匹配到了正式版那一条。这个错是无声的:界面上不会出现任何异常,你只是再也收不到自己那条轨道上的下一个构建,而看到的更新说明其实属于正式版那条线。这次是把 Supacode 和 TypeWhisper 的预发布包真装上去、看它到底怎么走,才发现的;现在两者都会跟着你真正所在的那条轨道走。
CapCut 的 beta 显示了一个你并没有装的版本号。 那一行写着“9.3.4545 → 9.4.0-beta6”,而 CapCut 自己、访达、以及别的更新工具都把你这份叫作 9.4.0-beta5。有些应用把真实版本号放在与多数应用不同的字段里,而 DuoUpdater 在箭头你这一侧读的是看起来更整齐的那个,在厂商那一侧读的却是真实的那个。它提供的更新一直都是对的,错的只是标签。现在箭头两侧取自同一处——CapCut 如此,另外七个同样构建方式的应用也是如此。
MacWhisper、GitHub Copilot for Xcode、TypeWhisper 和 OpenUsage 现在能看到更新说明了。 这四个此前的说明面板都是空的:它们的更新源里根本不带说明,而这件事直到逐个对照厂商实际发布的内容才被发现。现在 DuoUpdater 会去读 MacWhisper 自己的发布说明页、Copilot 的更新日志文件、TypeWhisper 的更新日志站点,OpenUsage 则指向它的发布列表。
Helium 改为走它自己的更新服务,因此带来了 beta 渠道和小得多的下载量。 此前它是通过公开发布列表跟踪的,而那里只会出现正式版——于是用 Helium beta 的人拿到的一直是正式版。它自己的服务还发布增量补丁,所以一次常规更新大约 40 MB,而不是重新下载 124 MB。正式版和 beta 两种构建都验证过;在替换任何东西之前,下载会先用你已装那份里的签名密钥校验。
Firefox 和 Thunderbird 的 beta、开发者版和每夜构建现在能正常检测更新了——在此之前,这五条发布轨道一次更新都没报出来过。 它们此前读的是 Mozilla 公开的版本文件,那里只发布给人看的那个版本号,而安装之后 “b5” 这截后缀会被剥掉:一个 Firefox beta 整个周期都自称 155.0,于是唯一被问出口的问题是“155.0b5 比 155.0 新吗”,答案是不新。每夜构建更糟——Mozilla 每天出一个,而它们全都叫 157.0a1,所以整整四周的周期里一次都没报。现在 DuoUpdater 改问 Mozilla 自己的更新服务,也就是 Firefox 内置更新器用的那个地址、应用自己就写着的那个,比较的是两边共有的构建标识。发布前用五条轨道的真实安装包逐一核对过:那个服务报出来的标识,与你手上那份应用里的逐字节相同;落后一个构建的 beta 现在会报更新,同一天早些时候构建的每夜版也会。stable 与 ESR 从来不受影响,这次也没有动。有一条限制值得直说:那个服务不发布发布时间,所以这五条轨道的发布日志记的仍然是 DuoUpdater 第一次看见某个构建的时刻,而不是 Mozilla 发布它的时刻。
Wispr Flow、AionUi 和 Devin 现在可以一键更新了,而不只是告诉你有新版本。 这三个应用此前都能看到更新,却都装不了,写下的理由是:厂商分别发布 Intel 和 Apple 芯片两种构建,DuoUpdater 没办法挑。实测之后这条理由并不成立:这三个应用读取的端点本来就是 Apple 芯片那一套,而 DuoUpdater 本身只在 Apple 芯片上运行——根本不存在需要挑的时候。接上一键之前,每个安装包都先下载并检查过:里面装的是对的应用、由与你手上那份相同的开发者签名、经过 Apple 公证,AionUi 那份还与它自己清单里公布的校验和核对过。然后每个都在旧版本上真实安装了一遍,确认装完停在新版本。
Grok Bot 现在可以追踪了——支持版本检测和一键更新。 xAI 的这个桌面应用由 Anysphere(Cursor 背后的公司)构建和签名,更新也走 Cursor 自己的发布服务。常规渠道因此全都答不上来:没有 Sparkle feed,App Store 里没有它,也没有公开的发布仓库,而 Homebrew 的 cask 把更新交还给应用自己。DuoUpdater 改为读取厂商自己的版本端点。另外还有两个端点,都有明确理由没有采用:x.ai 下载按钮背后那个完全不发布版本号;应用自带更新器用的那个,在你已经是最新时返回空响应——这种沉默和端点坏掉无法区分。安装包在接入前也检查过:磁盘映像里装的是正确的应用,由与你手上那份相同的开发者签名,并经过 Apple 公证。有一样东西是刻意不提供的:发布说明。xAI 不为这个应用发布任何版本说明,它唯一公开的 changelog 属于另一个产品。
Comet 和 Msty Studio 现在也能一键更新,并且新增了一项检查,专门盯那些“本可以装却没装”的应用。 这两个是那批里最后还只能看不能装的,结果都不需要新增任何机制——只需要更仔细地看厂商到底发了什么。Comet 的下载链接是带签名的、一小时就过期,所以更新按钮改为指向 Perplexity 自己的网关,链接在你点击的那一刻才生成,而不是几小时前检查时就定死。Msty 在同一个文件里列了四个下载,Intel 版排在第一,而用来校验下载的指纹恰好取的就是第一条;现在它被绑定到真正下载的那个 Apple 芯片版本上,顺带还带来一个好处:检查与点击之间厂商如果发了新版,多数情况下会被拦下而不是悄悄装上。两者都先下载并走过真实的安装闸才发布。更值得说的一课与这两个应用无关:DuoUpdater 此前没有任何机制能发现某个应用被标成只检测、而那个理由早已不成立,所以现在每晚的 recipe 巡检会指出:某个只被观察的应用,其实已经把安装包递到我们手上了。
QQ音乐现在可以追踪了——支持版本检测、一键更新和窗口内的发布说明。 常规渠道都不可用。它完全没有 Sparkle feed,也没有 Sparkle 框架;Homebrew cask 把更新交还给应用自己;而它自身二进制文件里点名的那个更新端点,对我们能问的一切都回复 200 和空响应体——这种沉默和端点坏掉无法区分,所以它被放弃了。DuoUpdater 改为读取供应商下载页面所依赖的数据文件:那个页面本身是个空壳,内容全靠这份文件填充,而这份文件也是发布说明唯一存在的地方——没有博客,没有 appcast,也没有分版本的页面。于是说明直接从这里读取,渲染在 DuoUpdater 自己的窗口中,更新则安装同一份数据里点名的、经过公证的磁盘映像。有一个坑值得记录:这份文件里还挂着另一条六年前的 Mac 记录,和当前这条并排放着,所以两条规则都靠 Mac 下载文件名里的版本号来定位,而不是靠“Mac”这个词本身。还有一个值得说清楚的限制:这份文件公布的是你看到的版本号,从不包含底层的构建号,所以一次保留原版本名称的重新打包,在这里是看不见的。它无法创造不存在的更新——只是无法看到这一种情况。
TimeMachineEditor 现在可以追踪了——支持版本检测和一键更新。 常规渠道都不可用:没有 Sparkle feed(这个应用既没有 feed 地址,也没有会用到它的框架)、App Store 里没有它、也没有公开的发布仓库,Homebrew 的 cask 把更新交还给应用自己。它有的是一个小型供应商网站,唯一的下载链接会在自己的文字里写明版本号——这正是 Homebrew 自己的版本检查所依赖的那个页面、用的也是同一种读法,所以这是供应商本来就打算暴露的界面,而不是一种猜测。更新以供应商自己的安装包形式安装,而不是简单地替换应用,这是刻意的:这份下载还会在应用本体之外放置一个后台调度器、一个命令行工具和一个 launch daemon,所以只替换应用会让一份新副本和一个过时的调度器并排留在那里,没有任何东西会察觉到这种不匹配。
Little Snitch 现在可以追踪了,稳定版和 nightly 版都覆盖。 Object Development 发布了一份版本文件,它自己的更新器在别的方式都不行时会回退到这份文件——Homebrew 读的也是同一份——DuoUpdater 直接读取它,因为 Little Snitch 没有 Sparkle feed,它的 cask 也把更新交还给应用自己。这两条发布线共用同一个身份标识,通常这会让它们无法区分;但这一次版本字符串暴露了身份:nightly 构建会把“nightly”这个词完整写出来(“6.5 nightly (7301)”),而稳定版就是一个朴素的“6.4.1”。值得说清楚它刻意不做的事:它不会替你安装这次更新。Little Snitch 在应用之外还运行着一个系统扩展和一个特权后台服务,单独替换应用是否会让它们保持完好,还没有在真实机器上验证过——所以 DuoUpdater 只会告诉你新版本已经发布,并把你引导到供应商自己的下载页面,而不是拿一个网络过滤器去赌。
Carbon Copy Cloner 现在可以追踪了,而且不会试图把你从一个大版本推向另一个大版本。 Bombich 同时维护着三代产品——CCC 5、6 和 7——它们向系统报告的是同一个身份,这让“有没有更新”变成一个比看起来更棘手的问题:7.1.6 确实排在 6.1.13 之上,但跨版本升级是另一笔单独的购买,而且 CCC 7 需要的 macOS 版本,可能比一台还在跑 CCC 5 的机器实际拥有的还要新。如果只按版本号来回答,那么每一个 CCC 5 和 6 的用户都会被永远告知有一个免费更新在等着——而且每一次都是错的。所以 DuoUpdater 分别从每一代自己的地址读取版本,并且只会在你实际所在的那一代内部提供下一个发布。对于在 CCC 内部打开了 beta 选项的用户,beta 发布也会被纳入。目前只做检测——CCC 会在应用之外安装一个特权 helper,这比现在已有的一键更新要承诺得更多——而这些更新此前完全无法被看到的原因也值得记下来:这个应用自己的更新 feed 对每一次请求都成功应答,却是一个彻底空白的页面。
一个你的 Mac 实际上跑不了的更新,现在不会再被下载并安装了。 每一个应用 bundle 都会声明它能启动的最低 macOS 版本,有些厂商还会声明最高版本——“这个构建不适用于那么新的系统”,这就是一个还没跟上 macOS 27 的应用表达这层意思的方式。DuoUpdater 此前两个都不读。对于通过 Sparkle feed 追踪的应用,这道下限本来就被遵守了,但这类应用只是少数:在一台典型的 Mac 上,从供应商自己的端点或从 GitHub 发布读取的应用占了更大的一半,而 GitHub 发布从来不会在任何地方声明 macOS 要求。所以一个已经跟进到比你的系统更新的 macOS 的应用,此前可能被提供、被完整下载、被换入——然后打不开。DuoUpdater 现在会直接从下载下来的应用本体里读取这项要求,紧挨着既有的处理器架构检查一起判断,并拒绝这次替换,而不是拿一个打不开的副本去换掉一个能用的副本。厂商声明的上限,只要 feed 公布了,现在也会被遵守,所以一个被开发者标记为“不适用于你的 macOS”的构建,一开始就不会被提供。有三个限制值得说清楚。从下载内容里读取这项要求,意味着下载已经发生过了——这防住的是一次失败的安装,而不是那次下载流量,因为对大多数应用来说,根本没有更早的地方可以问。一个最新版本你的 Mac 跑不了的应用,会持续显示那个更新,又持续拒绝安装它;这次拒绝现在会指名它想要的 macOS 版本,但 DuoUpdater 还不会记住这个答案并就此停止提供它。而且这项检查只覆盖 DuoUpdater 自己替换应用 bundle 的那些路径——安装包是把文件交给 macOS 去处理,由系统强制执行安装包自己声明的要求,Homebrew 和 App Store 则各自挑选自己的构建;来自 App Store 中 iPhone 和 iPad 分区的应用被刻意跳过,因为它们声明的版本是一个 iOS 版本号,拿它去跟 macOS 比较,后果会比干脆不检查更糟。有一件事它目前还没有明说:当开发者把某个构建标记为“不适用于你的 macOS”时,那个更新就直接不会被提供,应用会显示为已是最新,而不会说明原因。对于一台较旧的 Mac 来说,这个问题会在你升级 macOS 的那天自然解决。但对于一台太新的 Mac,它不会自己解决,那里欠一个比沉默更好的答案。
百度网盘现在可以追踪了——支持版本检测、一键更新和窗口内的发布说明。 常规渠道都不可用。它没有 Sparkle feed;通过 Homebrew cask 也无法追踪手动安装的副本;其自身 bundle 内引用的更新清单也已经失效——该清单文件、对应的 arm64 版本,以及存放它们的整个目录,全都返回 404。DuoUpdater 改为读取供应商下载页面所调用的端点,并安装该端点返回的经过公证的 arm64 磁盘映像。发布说明也是这样拿到的:“版本更新”页面有一个 Mac版 标签页,但页面发下来是空的,内容靠 API 填充,所以 DuoUpdater 直接读这个 API,在自己的窗口里显示最近 40 个版本。有一个限制要说清楚:这个 feed 只公布你看到的版本号,从不公布底层的构建号,所以版本名不变的重新打包在这里是看不到的。它不会凭空报出不存在的更新——只是看不到这一种情况。
检查失败现在会明确说明失败,而不是显示空白。 如果一个应用的版本只能靠厂商自己的端点来回答,它以前表示“没有答案”的方式只有一种:一条空白的横线,而 DuoUpdater 根本不追踪的应用也是这样显示的。于是厂商的端点超时或搬了地方,看起来就和“DuoUpdater 不追踪这个应用”一模一样——像是一个永久的结论,不提供“重试”,也不计入“App 无法检查”的数量。现在这些情况算作检查失败,它们本来就是:有“重试”按钮,计入数量,并写明原因。但刻意没有把所有情况都算进来:你的 Mac 本来就无能为力的情况——你所在的发布轨道没有 recipe,或者端点要求这台机器没有的身份——仍然只显示原来那条安静的横线;连续失败三轮的检查也仍然会从横幅里退出,而不会一直占着它。由 JetBrains Toolbox 安装的应用,在背后的版本读取失败时也会保留“打开 Toolbox”按钮,因为无论如何,该做的都是打开 Toolbox。
“重新启动以应用”的提醒,现在在你重新启动之后就会消失。 应用自己的更新器在后台下载好构建时,DuoUpdater 会通知你,并在通知上提供“重新启动”按钮。点它是管用的——应用会在新构建上重新打开,并显示“现在运行的是 1.0。”——但被它取代的那条提醒还留在通知中心下面,仍在让你重新启动一个你刚刚重新启动过的应用。DuoUpdater 等的是这条提醒不再有意义的时刻,而它去检查的那一刻,恰好是已经处理过的那一刻。现在只要没有待完成的重新启动,它就会撤下提醒;应用在 DuoUpdater 没运行时自己装好更新而留下的提醒,也会一并清掉。
在同一个版本号下发布许多构建的应用,现在各处都能正确处理了。 Mac 应用带有两个版本字符串:一个是显示给你看的(“1.0”),另一个是真正往上数的构建号。大多数应用两个都会变。有些只变构建号:Amp 一天之内发布了十个构建,每个都叫 1.0;Surge 有四个不同的发布都叫 6.9.0;JetBrains 的预览构建也是这样。DuoUpdater 大约有十几处是靠比较显示的版本来判断“这个变了没有”,而对这些应用来说,不管实际发生了什么,这种比较永远只会回答“没有”——或者在问“这两个一样吗”的地方,永远回答“一样”。下面几条都是它造成的问题。它们出自同一个错误,是 Amp 让它暴露了出来。
“重新启动”不再看似卡住三分钟,然后报告一个根本没发生的失败。 对一个自身更新器已经备好构建、正在等待的应用点“重新启动”,以前会转三分钟圈,然后说失败了——而实际上更新在一两秒内就已经装好,应用也已经在新构建上重新打开。DuoUpdater 一直在等显示的版本号发生变化,而这个变化永远不会来。在 Amp 上实测:一次不到一秒的替换,换来 189 秒的转圈。现在它远不到一秒就会有结果,真正的失败也仍会如实报告。
“重新启动”不再给你一个已经过时的构建。 当应用的更新器下载了一个构建、而开发者随后又发布了另一个时,DuoUpdater 仍然提供“重新启动”——于是你重新启动后立刻就落后了一个构建,而这恰恰是那道检查本来要防止的。这些行现在改为提供更新,它会取回当前的构建。
跳过一个版本,不再让应用从此没了声音。 这一条最值得留意。“跳过此版本”的本意是拒掉一个发布,放下一个过来。但它只记录显示的版本,所以对于多个构建共用同一个版本名的应用,跳过一次就会拒掉之后的每一个发布——永久有效,重启也不恢复,屏幕上也没有任何提示说这个应用已经不再出声。现在跳过会记下它拒掉的是哪个构建。这次修复带来一个副作用:旧版 DuoUpdater 跳过的版本会再向你提供一次,因为旧记录说不清它指的是哪个构建。再跳过一次,就会被正确记下。
看起来什么都没变的更新,重新可以回滚了。 工作台会把不起任何作用的回滚隐藏起来——而对这些应用来说,每一次回滚看起来都不起作用,于是一次真实的更新之后,这一行就消失了,完整的备份明明就在磁盘上,却没有办法用上。
重新启动的提醒又恢复为每个构建只提醒一次,开发者再发一个构建也不会让它失效。 0.3.69 把每五分钟重复一次的提醒,换成了每个待装构建只说一次——但它靠构建名来识别构建,所以对这些应用,它只提醒了第一个,之后的每一个都没有声音。这个问题已经修好。另外:如果应用因为有未保存的工作而拒绝退出,DuoUpdater 会记下一笔,等你处理完再把它重新打开。以前如果开发者在你还没决定时又发布了一个构建,这条记录就会被丢掉,替换完成后应用一直关着,没有任何东西去重新打开它。现在这条记录会跟到新的构建上。
失败的 App Store 更新不再被悄悄记成成功;已下载的安装包如果其实是较旧的构建,也不会再被当成当前提供的那个版本。这两个问题出自同一处比较。发布历史以前还会把一个应用的十个构建算作一次发布;从现在起会分开计算,不过已经记下的历史无法恢复。
这一行本身会写明重新启动将装上哪个构建。 以前它写的是“1.0 → 1.0”——一行看不出任何区别的字。现在只有在变的正是构建号时,它才会写成“1.0 (129) → 1.0 (130)”;版本名本身已经不同时,这一行保持原样。同样的修正也用在了“重新启动”的工具提示、通知、解释安装为何推迟的说明,以及 duo install 的拒绝信息上。
微信输入法和豆包输入法现在又可以一键更新了——而且更新方式和它们自己更新自己的方式一样。 一键更新输入法的功能在 0.3.25 发布当天就被撤回,原因是有用户的输入法设置丢失了。问题出在安装的方式上:它会像首次安装一样,把整个应用替换掉。输入法是靠应用的位置向 macOS 注册的,而这两个应用自我更新时都不会动这个位置——它们只是保留应用本体,替换里面的内容。DuoUpdater 现在做的是同一件事,所以更新之后,注册的还是同一个应用;任何环节出了故障,你正在使用的那个副本都会原封不动地留在原地。这两个应用现在也都不再要求输入密码了。
更新其中任何一个之前,它积累下来的一切都会先复制一份。 你的词库、你的设置和你的账户状态并不保存在应用本体里,所以 DuoUpdater 原本保留的回滚副本管不到这些内容。现在每次输入法更新之前都会先给它们拍一份快照,回滚时一并放回去,而且这份快照几乎没有成本——其中一个应用 578 MB 的数据,只需要十分之一秒,几乎不占额外的磁盘空间。这是一张安全网,而不是一次修复:应用下次启动时要怎么处理你的数据,由它自己的代码决定,谁也挡不住。但现在至少有了可以退回去的东西。
更新不再悄悄拿走一个应用自我更新的能力。 当 DuoUpdater 需要以管理员身份替换一个应用时,它会恢复应用的所有者,却不会恢复应用安装时拥有的权限——而普通下载解压出来的权限,通常比安装程序设置的权限更窄。对这两款输入法来说,这决定了它们自己的更新器是能顺利完成下一次更新,还是连自己的残局都收拾不了。现在,应用安装时拥有的权限会被一并带到替换它的新副本上。
DuoUpdater 不再每隔五分钟就要求你重新启动同一个应用。 一个自我更新的应用,会把新版本留在磁盘上,等你退出它。DuoUpdater 此前每次检查都会注意到这一点,并发一次提醒,所以一个你已经决定暂不重启的构建,只要你留着不管它,就会一直缠着你。现在每个构建只会宣布一次:重新启动它、跳过它,或者忽略这个应用,它就会安静下来,等下一个构建出现时再正常宣布。
等待重新启动的应用会被计入更新数,被忽略的应用则完全不再计入任何数字。 一个已经躺在磁盘上的新版本本身就是一个更新——它只是提前下载好了而已——所以现在它会被计入徽章和“N 个更新可用”这一行,而不是单独另算一个数字。被忽略的应用则走了相反的路:一个你忽略掉的应用可能仍然点亮徽章,它那一行却显示着不起眼的“已忽略”标签、没有任何按钮可点,于是数字指向了一个你根本无从下手的东西。现在,忽略和跳过在提醒、徽章和列表这三处地方,含义完全一致。
搜狗输入法现在被追踪了——靠的是询问它自己的更新器,而不是读取它的网站。 搜狗的更新日志页面发布的是三段式版本号,而安装的副本实际是四段式,所以要跟它比较,就得先把真实的四段版本号裁剪掉一段。它自己的更新检查接口则直接用应用自身的版本编号作答。DuoUpdater 像搜狗自己一样查询这个端点,读取完整的四段版本号,所以哪怕一次只改动最后一段的重新打包,也能被发现。这一条只做到了检测:搜狗的更新器做的事远不止替换应用本身——它还会重新注册一个 QuickLook 生成器、把你的数据搬到新位置、并强制退出输入法——所以更新动作本身仍然交给它自己完成。有一点值得知道,这是搜狗自己的行为,不是我们造成的:他们的端点会根据发起请求的 macOS 版本来回答,运行 macOS 28 的 Mac 如果报上自己的系统版本去问,得到的会是一个 2023 年的构建。DuoUpdater 不会报自己的系统版本去问,所以不管在哪台 Mac 上,你看到的都是当前的最新版本。
本该被后续修复改对的发布说明,现在真的会被重新读取,而不是永远错下去。 说明是按版本缓存的,理由是已发布版本的说明内容永远不会变。这话对说明本身是对的,但对 DuoUpdater 从中提取出来的内容却不一定成立,所以某个应用的说明如果解析出来是乱码,不管后来发布过多少次解析修复,那个版本的说明都会一直乱下去。现在每条缓存记录都会记下是哪一代提取逻辑写入的,由较旧一代写入的记录会被重新抓取一次。两处缓存都是这样处理的——应用的更新日志,以及 Homebrew 公式的说明。
在某个供应商的 CDN 上失败的检查,会在被判定为彻底失效之前再重试一次。 502、503 和 504 都意味着某个中间环节没能连上它背后的服务器:请求根本没有真正送达,所以片刻之后再发一次同样的请求,通常就能成功。Headlamp 的检查就恰好死在这一点上——GitHub 回复了 504,却完全没有附带速率限制的响应头。现在这三种状态码都会在 0.8 秒后获得一次重试。刻意只覆盖这三种:500 是服务器自身出了故障,重试大概率只会重现同样的错误;而对触发限流的请求做重试,只会继续消耗它正在抱怨见底的那份配额。
Homebrew 公式的发布说明现在会跟着它所属的版本走。 此前说明是按公式记住的,而不是按版本,所以一个公式的说明一旦加载过,之后每次查看,在这次会话剩下的时间里看到的都还是第一次加载时那个版本的说明——哪怕后来出现了更新的版本也是如此。读取说明还会占用整个公式列表排队等待的那条队列,所以一次缓慢的 brew info 可能会让它周围的整个列表都卡住。
在工作台的侧栏中右键单击一个应用即可打开它。 这跟菜单栏那些行里一直就有的“打开”是同一个功能,只是放到了你更可能用得上它的这个窗口里。
丢弃已下载的安装包时,它留下的错误也会一起清掉。 扔掉待装的安装包,本来就会让这一行回到“更新”,但你刚刚取消的那次尝试留下的红色失败信息还挂在上面——而且永远不会有东西去清掉它,因为错误只有在这一行变成最新之后才会撤下,而一个仍在提供更新的行永远不会变成最新。现在它会随下载一起消失。
duo,这个可选的命令行伙伴,不再对自己做出不准确的描述。 --timeout 已经被移除——它此前一直被接受、被写进文档,却没有任何代码真正读取它。--budget 第一次被写进文档,提到它的那条消息现在会给出真实的分钟数,而不是永远都说“15”。--max-calls 此前声称默认值是 20,实际却是 6。给数字类的参数传入一个非数字的值,现在会报错:duo verify --max-concurrency 1x 过去会被直接忽略,转而用默认值 4 扫描——这对一个存心想放慢扫描速度的人来说,方向刚好反了。duo verify 现在会统计它真正发出的请求数:一个先答复 502、重试后才成功的 feed,不会再一边悄悄问了两次,一边却报告一个干净的 ok。-h 现在在任何 --help 能用的地方都能用。
又有四个应用被纳入追踪,新增两个发布渠道,而且它们全都是原地更新。 CapCut、Canva,以及在两个站点上发布的 WorkBuddy——国际版和中国版各走各的发布节奏,所以每份安装只会看到自己所在站点的版本和说明。Termius 和 VSCodium 在已经支持的稳定构建之外,新增了 Beta 和 Insiders 渠道。CapCut 的第二条渠道比较麻烦:它是通过 CapCut 内部的一个开关来选择的,版本号里完全看不出来,所以你在哪条渠道上,是直接从磁盘上的副本读出来的,而不是从你碰巧在运行的构建去猜。如果你勾选了那个开关,有一点值得知道——DuoUpdater 会在最新 beta 一出现就立刻提供给你,这可能比 CapCut 自己的分阶段推送本该轮到你的时间还要早。
Android Studio 的预览渠道,现在不会再给你推送一个比你手上这个还旧的构建。 Google 是按发布顺序而不是按版本号来排列它的发布列表的,而 DuoUpdater 把这份列表分别查了三遍——一次找版本号、一次找日期、一次找下载地址——结果一个比更新的 Canary 版本发布得还晚的候选版本,可能从一个条目取了版本号,又从另一个条目取了文件。8 月 26 日,Canary 渠道给出的答案是 2026.1.4 RC 2,而这时 2026.2.1 Canary 2 其实早已发布。现在这些答案必须全部来自同一个条目。
一次重新打包的发布,不会再去安装它本该替换掉的那个旧文件。 当一个项目在不改变版本号的情况下重新构建一次发布,两个文件就会同时挂在同一个标签下——KeePassXC 就曾把 KeePassXC-2.7.11-1-arm64.dmg 和原始的 KeePassXC-2.7.11-arm64.dmg 放在一起发布——而 DuoUpdater 过去只会拿排在前面的那个文件。这个顺序是按字母排的,这次刚好把重建版排在了前面,下次却可能会把它排到最后。从外表完全看不出问题:这一行上的版本号是对的,出错的只是它背后指向的文件。现在会根据文件名本身来判断该选哪一个。
Nightly 和 snapshot 构建,现在会被正确识别为它们本来就是的预发布版本。 VLC 的 nightly、KeePassXC 的 snapshot,以及 Freelens 的 nightly,全部是以稳定版应用的名称和包标识符安装的,所以唯一能暴露它们身份的只有版本号——而这个版本号此前根本没有被读取。它们因此被算作稳定版,nightly 就是这样被推送一个普通发布、拿来覆盖自己的。出于同样的原因,DB Browser for SQLite 的 nightly 版本改为从应用自身的文件名读取,它的问题其实已经存在,只是没有显露出来:它冻结不变的版本号,只有在稳定版还没追上来之前才会排在稳定版前面——一旦稳定版赶上,每一个 nightly 安装都会被悄悄换成稳定版构建。
重新启动一个应用,现在会报告实际发生的情况。 有几个应用会在自己的 bundle 里再打包一个完整的应用——Surge 就是把 Dashboard 放在那里——当只有内部那个应用需要重启时,DuoUpdater 过去会说外部应用已经重启了,可实际上什么都没有重启。现在它会报告内部应用自己的真实结果,也不会再为一次根本没有发生过的重启弹出通知。
已经下载完成的更新,现在会明确说明这一点,而且可以被撤回。 一行如果挂着一个已经下载完成的安装包,此前就只显示一个蓝色的“安装”按钮,别的什么都没有——既没有说明下载已经完成,也没有说明这个按钮的作用其实是重新打开安装程序而不是重新开始更新,而且没有办法改变主意。现在这些信息直接显示在行上,而不是藏在工具提示里,行的右键菜单里新增了丢弃已下载的安装包,可以把下载扔掉,让这一行回到“更新”状态。如果在同一个应用的安装还在进行时丢弃安装包,此前还可能连刚下载完的新包也一起扔掉;现在不会了。
曾经指向已下线页面的发布说明按钮,现在重新指向了有效页面。 微软重命名了 Edge 面向企业的发布说明页面,Termius 也搬动了自己的更新日志,导致三个 Edge 渠道和 Termius 都会把你带到一个已经不存在的页面。此前从来没有人检查过这些页面是否还在,这就是为什么它们能在其他一切看起来都很健康的情况下无限期地烂下去——现在会定期检查它们了。Edge 的 Dev 渠道则干脆没有按钮:微软已经停止为它发布说明,而所有还存在的页面都属于另一条发布线。
DuoUpdater 现在能跟进到 Raycast 版本 2 了,但仅限于能运行它的 Mac。 Raycast 2 需要 macOS Tahoe 和 Apple silicon。不满足这个条件的 Mac 会留在版本 1 这条线上,也不会再被告知一个它本来就装不了的新版本。这两条线现在也各自保留自己的发布说明,所以版本 1 的安装读到的是版本 1 的历史,而不是版本 2 的。
现在有更多应用的发布说明,会渲染在 DuoUpdater 自己的窗口里了。 BetterDisplay 的全部三条轨道、Shotbase,以及 WorkBuddy 在两个站点上的发布说明。BetterDisplay 的说明不再以其下载按钮的原始标记结尾,也不会在每一次发布里都重复一遍贡献者名单。
duo,这个可选的命令行伙伴,现在会拒绝它看不懂的命令行。 一个拼错的标志过去会被直接忽略,而一个被忽略的标志,效果就跟你根本没传过它一样——所以 duo verify --githubb 会悄悄检查每一个 recipe,而不是你要求的那一个。未知标志、缺少取值的标志,以及多余的参数,现在都会报错,并说明这个命令实际能接受什么,--help 也能在任何命令之后使用。
BetterDisplay 的预发布版和内部构建,现在会提供给要求它们的人。 BetterDisplay 把它全部三条发布线都塞进同一个更新 feed 里,靠自己设置里的两个开关来在其中做选择。DuoUpdater 过去只能从你碰巧正在运行的构建去猜你走的是哪条线,可如果你打开了某个开关、却还没来得及真正切换过去,这个猜测就完全说明不了问题——结果某个把两个开关都打开、但还停留在稳定版上的人,会被告知自己已经是最新的,尽管 BetterDisplay 自己的更新器其实已经在提供领先四个版本的构建。现在直接从 BetterDisplay 读取你实际的选择,也包括这种情况:打开内部构建之后,普通的预发布版也会继续推送。它那些仅限 Apple silicon 的预览构建则被特意排除在外,所以一台 Intel Mac 永远不会被提供一个它根本运行不了的版本。
连续切换两次应用的发布渠道,不会再让这一行卡在旧的结果上。 翻转一次渠道开关,会让 DuoUpdater 重新检查那个应用;如果在第一次检查还没跑完时又翻转了第二次,这第二次翻转过去会被直接丢弃——导致这一行继续向刚刚关闭 beta 的人推荐 beta 版本,直到某个不相关的事件恰好触发了另一次检查为止。现在较新的那次翻转会接管较旧的那次,而一次中途被打断的检查,会把它没来得及查到的应用标记为留给下一轮,而不是当成已经处理过记下来。翻转之后,这一行也会更快进入检查中的状态,屏幕上的答案已被你的开关作废、却还没刷新的那段时间也就更短了。
寄居在另一个应用内部的应用,现在会跟着外层应用一起被关闭、重新打开。 有几个应用会在自己的 bundle 里再打包一个完整的第二个应用——Surge 就是把 Dashboard 放在那里。macOS 把它当成一个独立的应用,所以当外层应用被更新时,没有什么东西会去关闭它:它继续运行着刚被替换掉的旧版本,而那份副本已经不在它以为的位置上,也就没法再和自己所属的应用通信。现在这些内嵌应用会跟着它们所寄居的应用一起关闭,并在之后重新打开,你原本实际在操作的那个窗口,会重新回到最前面。只有你自己本来就能打开的应用才会被这样处理;应用为自己运行的那些看不见的辅助进程则不受影响。
曾经悄悄停止生成的回滚点,现在又重新开始生成了。 应用可能会给自己的某个文件加锁,让任何东西都无法删除它,而用于回滚的副本也继承了这把锁——结果它永远无法被下一次生成的副本替换,一次被中断的尝试就可能留下一个谁都清理不掉的副本。此后,这个应用的每一次更新都在没有退路的情况下照常进行,只用一行文字说明了这一点,却没给出任何理由。开发者自己 Mac 上的某个应用,就这样卡了两天。现在,用于回滚的副本不再带着这把锁,已安装的应用会完全保持开发者设置时的样子,一个卡住的旧副本也再不能挡住本该替换它的那个。
一次成功的更新,不会再被报告为失败。 macOS 有可能已经把新版本放到位,却在删除被替换掉的旧版本时失败。DuoUpdater 过去对这个结果照单全收:它会说一个其实已经更新过的应用没有被更新,或者说没有回滚点——尽管一个完整的回滚点其实就好好地存在备份仓库里——而在前一种情况下,它还会让你去授予一个你其实早就授予过、而且无论如何都帮不上忙的权限。现在在报告结果之前,会先检查磁盘上实际发生了什么。
版本行不再重复版本号里已经包含的数字。 当一个应用正在等待被重新启动时,这一行会显示你正在运行的版本,以及重启后会切换到的版本。两边都带着构建号——当开发者在同一个版本名称下发布好几个构建时,这个构建号能让差异看得清楚;但版本号本身已经不同的时候,它就纯粹是噪音了。Chrome 那一行的宽度全用来打印“151.0.7922.174 (7922.17…”,还没显示到真正变化的那几位数字,空间就用完了。现在只有当构建号才是真正变化的部分时,它才会显示出来。
重新打开应用以完成更新,现在统一成了一个说法。 过去,如果应用自己的更新器已经把新版本准备好,界面上用的是“Restart”;如果新版本要等你退出应用才生效,界面上用的则是“Relaunch”。这个区别在幕后确实存在,但对你来说没有任何意义:不管是哪种情况,点击的都是同一个操作,就算你一直不点,结果也一样——反正下次退出应用时,更新总会生效。现在界面统一显示“重新启动”——包括通知、工具提示,以及帮你自动执行这一步的设置项——这也是 Chrome、Claude 以及大多数自我更新应用一直在用的说法。只有英文曾经为这个概念用了两个词;德语、日语、俄语和中文一直只用一个词,西班牙语和法语现在也改用了它们在别处本来就在用的说法。
需要管理员权限的更新,现在会主动请求授权,而不是失败后把锅甩给某个权限。 此前,判断一次替换是否需要密码,看的是应用所在的文件夹,而不是应用本身。任何由 macOS 以 root 身份安装的东西——包括每一个 App Store 应用,以及任何由安装程序包装上的应用——都会通过这项判断,走上那条不需要密码的路线,然后根本没法完成:删除旧版本需要对其内部目录的写入权限。macOS 报告这次拒绝时用的错误码,和它拒绝“App 管理”权限时用的是同一个,于是这次失败就变成了一条请你去授予“App 管理”权限的提示——而这完全帮不上忙,因为真正的障碍是文件所有权。在一台普通 Mac 上,中招的是每一个 App Store 应用,外加少数几个其他应用,而且只有管理员账户会遇到这个问题——标准账户此前已经被正确处理。现在这些更新会走真正可行的那条路线,应用也会保留原本的所有者,而不会被悄悄改成属于你。
App Store 应用现在也和其他应用一样,能拿到回滚点了。 备份此前会跳过它们,理由是商店总能把之前的版本重新取回来。但事实并非如此——App Store 只提供应用的当前版本——这就使得 App Store 成了唯一一条应用了更新、却没法撤销的路线。现在这些应用也会像其他应用一样在更新前先备份,而且因为副本是通过克隆生成的,在更新真正替换掉原文件之前,几乎不占用额外的磁盘空间。恢复一个 App Store 应用的回滚点时,会额外说明一件只有商店才有的事:更新会立刻重新出现在“更新”列表里,如果开启了自动更新,它还会自己再应用一次。而当一次商店更新原本就不可能被应用时——比如一个在 Mac 上运行的 iPhone 应用,或者一个你所在地区根本不销售的应用——现在也不会再生成回滚点了,这样就不会出现“提供回滚到自己当前正在运行的版本”这种情况。
Longbridge Desktop 现在被纳入追踪,涵盖它的两条发布线。 稳定版和预览版都支持版本检测、官方 Apple silicon 构建的一键安装,以及在 DuoUpdater 自己窗口中渲染的发布说明,插图也包含在内。
WhatsApp 的发布说明现在会在它的 App Store 检查仍在进行时就先显示出来。 那个窗口本该显示的 App Store 页面,此前是登记在一个查询永远匹配不上的名字下面,所以说明窗格就一直空着,没能把它显示出来。
菜单头现在能在更小的空间里容纳更多信息。 DuoUpdater 自己的版本号现在紧挨着它的名字显示,点击它会打开它自己的发布说明。“全部更新”已经挪到了单独一行,这样翻译后的文字标签也有足够空间完整显示,底部的操作则都改成了图标。过去用来宣布 DuoUpdater 自我更新的横幅已经取消——取而代之的是版本号旁边的那个闪光图标会亮起来,并一直亮着,直到你看过更新内容为止。对菜单来说太长的状态行,现在会以省略号结尾,而不是把菜单撑宽。
设置不再把它提供的更新选项截断显示了。 决定更新如何应用的那两个菜单,此前在好几种语言下都会把自己的标签文字截断。现在的措辞变短了,在文字仍然放不下的地方,会自动换到第二行。
“新功能”现在会说明每个版本是什么时候发布的。 侧栏里的每个版本现在都会带上它的发布日期。
特定架构的更新,现在会挑选这台 Mac 真正能运行的那个构建。 有几个应用会在同一份 Sparkle feed 里把同一个版本发布两次——一次给 Apple silicon,一次给 Intel——过去遇到这种情况,DuoUpdater 选的是下载地址碰巧按字母排在前面的那个。现在它会读取 feed 里标注的硬件要求,如果厂商没有填写,就改读文件名里的架构信息。现在总是选原生构建,这台 Mac 根本无法启动的构建也不会再被提供。GitHub 发布也按同样的方式处理,而且不会只因为最新的产物针对另一种架构,就把一个完全正常的 recipe 判为失效。
ChatGPT 的更新检查现在会跟随账户所绑定的发布轨道。 OpenAI 有时会把商业版和企业版账户留在较旧的桌面构建上,而让新构建先推送给消费者账户。DuoUpdater 过去在这项检查里忽略了账户的套餐类型,结果就是悄悄给所有人都选了最保守的那条轨道:它可能会说你装的版本莫名其妙地比最新版还新,而 ChatGPT 自己其实已经在下载更新的构建;也可能会推荐一个应用自己的更新器随后又会替换掉的构建。现在它发出的更新请求与应用自己发的相同,并带上从 ChatGPT/Codex 登录状态读到的套餐标签。如果拿不到这个标签,就继续保持保守的行为;账户凭证本身则永远不会被放进请求或诊断信息里。
一次 macOS 迟迟不回应的重新启动请求,现在不会再卡住之后的每一次更新了。 Launch Services 偶尔会接受重新打开一个已更新应用的请求,然后就再也没有回调。这一行此前会一直卡在“正在重新启动…”状态,它的“重新启动”按钮保持禁用,DuoUpdater 自己的更新也只能排在它后面等待。现在,一个发起一分钟后仍未得到回应的重启请求,会被直接判定为失败并释放,这样这一行就能恢复正常,更新器的其余部分也能继续工作。
快速拖动滚动条时,发布日志现在也能一直保持内容不空白。 旧的惰性加载列表如果被一次大幅跳转甩到后面,会短暂留下一片空白窗口,直到新位置周围的行被重新创建出来。日志现在改用一种可以直接跳转到目标位置的可回收列表。刷新控件在箭头图标和旋转指示器之间切换时,现在也能保持同样的占用空间,所以检查开始时最底下那一行不会再跳动了。
下载流量现在会标记出使用了二进制补丁的更新。 新的下载记录的是实际走完的那条路线——而不只是当时有没有提供补丁——并会在历史记录里带上一个“增量”徽章。来自 0.3.62 版本、体积明显更小的那些补丁下载,现在也会被识别出来——即便它们是在流量账本还没有“路线”这个字段之前就已经记录下来的。
当开发者以这种方式发布更新时,现在就只会下载真正变化的那部分。 有些应用会在每次发布时附带一个小补丁——足以把你手上的旧版本打成新版本,而不用重新下载整个安装包。DuoUpdater 过去会忽略这些补丁,每次都下载完整安装包。现在只要有一个补丁正好匹配你当前所在的那个具体构建,它就会采用这个补丁。ChatGPT 的上一次更新因此只有 1.9 MB,而不是 605 MB;Docker 的是 87 MB,而不是 582 MB。不管走哪条路径,最终得到的都是完全相同的应用——同样的签名、同样的字节,而且在这个功能上线之前,已经拿完整下载版本核对过。当没有补丁适配你现在的版本,或者补丁应用失败时,完整下载依然会像以前一样照常进行,所以不会因为这个功能而导致任何安装失败。
DuoUpdater 不再去下载一个应用已经在自己下载的更新。 很多应用本身也会自我更新,我们俩要是同时去抓同一个 600 MB 的文件,就等于让你多下载了一遍。DuoUpdater 现在会注意到有下载正在进行,然后不去打扰它,并在这一行上说明情况,而不是默默什么都不做。如果那次下载实际上已经被放弃了,十分钟后就不会再把它算数,这样就不会有什么被一直卡住。
一个应用已经准备好的更新,不会再被覆盖掉。 会自我更新的应用,常常会先在后台下载,然后等你退出它们之后才把新版本换上去。在这种情况下安装更新,表面上看起来成功了,可你一退出应用,安装结果就被推翻了——如果应用自己那个等待中的版本,比 DuoUpdater 刚安装的还要旧,你最后反而会比一开始更落后。现在不管这些待应用的更新携带的是哪个版本,都会放手让它们自己完成。
DuoUpdater 自己的更新体积也变小了。 它自己的发布现在也用上了同样的补丁机制,所以从较新版本更新时,下载的是几百 KB,而不是十一 MB。
更新留下的说明文字,现在会自己清理干净。 当 DuoUpdater 把一个应用移交给它自己的更新器时,这一行会说明这一点——“已把它切到前台,让它自带的更新器来安装”。这句话过去会一直留在那里:唯一能移除它的办法,就是在同一个应用上再启动一次更新——所以更新明明早就完成了,这行字却还挂在下面,描述着几个小时前就已经结束的事情。现在只要应用变成最新状态,它就会立刻消失。至于那条提示“更新已应用但没有回滚点”的警告,则被特意保留了下来,因为它描述的是已经发生过的更新,只有在更新结束之后才真正有意义。
一次被拒绝的更新,现在会用你的语言解释原因。 当有其他东西正在安装时——不管是 DuoUpdater 正在批量处理,还是终端里的 duo——这一行都会告诉你原因。在一个其他部分都已经翻译好的窗口里,唯独这条消息只有英文,而且末尾还带着一个进程号——这个号码在终端里有用,可在菜单里你根本拿它没办法。现在这条消息已经翻译好了,说的是你该怎么做,而不是告诉你锁被谁占用。命令行工具仍然会打印进程号,在那里你确实能拿它做点什么。
发布规律那一行,连同它的时间显示,现在都会跟随你的系统语言设置。 “发布日志 → 规律”此前把一切都汇总成一句“Most often ships Friday, around 6 PM”(最常在周五、大约下午 6 点发布)。对于那些会根据语境变化星期名称词形的语言来说,把星期几硬塞进句子中间是说不通的,结果就用错了词形——俄语显示的是“Чаще всего выходит пятница”,而它需要的其实是“по пятницам”这种形式。现在它变成了一个固定标签:“高峰:周五,下午 6 点前后”,在任何语言里都是对的。时间格式也跟着走——如果你的 Mac 用 24 小时制显示时间,这里也会一样,包括图表坐标轴上的时间。
设置搜索现在能听懂你屏幕上显示的那些词了。 设置侧栏上方的搜索框,此前匹配的是一份只写了英文、从未翻译过的附加关键词列表,所以搜索英文的“rollback”能命中“通用”,而搜索德语的“Zurücksetzen”或中文的“回滚”却什么都搜不到。这些关键词现在在 DuoUpdater 支持的每种语言里都有了。英文关键词在所有语言里仍然管用,因为它们来自的那份文档本身就是英文写的。
打开“显示全部”不再让菜单变得迟钝。 过去每次这份包含你所有应用的完整列表出现时,都会把整个列表完整测量一遍——在一台有 127 个应用的 Mac 上,这大约要花一秒钟,而且几乎全部耗在了排布那些远在可视范围之外的行上。每次切换都要付出这个代价,而不只是第一次。现在只有真正显示在屏幕上的那些行才会被创建。
更新失败留下的说明,不会在失败过去之后还一直挂着。 当一次更新无法应用时——比如因为其他东西已经在安装——这一行会用红色文字说明原因。但此前从来没有什么东西会把这行字撤掉:即使更新真的完成了、这一行变回一个对勾,旧的说明仍然留在下面,而且会一直留到 DuoUpdater 重启为止,中间每一次重新检查都清除不了它。现在只要应用变成最新状态,它就会立刻消失。而仍在等待中的更新,它们的失败原因不会被动——这样你还没来得及看的原因,就不会被后台运行的检查悄悄清掉。
“全部更新”按钮的大小,不再随列表长度变化。 当只有少数几个更新待处理时,它此前会被画得比应有的尺寸更小,旁边多出八十点的空白,而只要列表一变长,它又会立刻跳回正常大小。现在它会始终保持应有的大小。
下载流量现在不仅显示版本号,还会说明更新换到了哪个具体构建。 很多应用会在同一个版本名称下发布好几个构建——Surge 就曾把四个不同的发布都标成“6.9.0”——所以这些行此前显示的是“6.9.0 → 6.9.0”,却什么信息都没告诉你。它们现在会显示“6.9.0 (12028) → 6.9.0 (12030)”,但这只在版本名称本身不够用的时候才会出现;如果版本号已经变了,构建号就只是噪音,会被省略掉。
被记录下来的构建号,是真正落地生效的那一个——更新完成之后,直接从应用本身读取出来。 而不是开发者的更新 feed 宣称的那个号码——feed 确实会报不准;而且这样一来,对根本不发布构建号的那些渠道(GitHub、Homebrew 和 App Store)也同样适用。一个还停在 macOS 安装器窗口、尚未完成的更新,永远不会被瞎猜一个构建号;在它真正落地之前,什么都不会记录。
一次什么都没改变的下载,现在会被标记出来。 偶尔,一次更新会重新获取并安装你 Mac 上其实已经有的那个构建——可能是开发者那边版本号对不上,也可能是某个镜像源提供的其实就是你已经有的东西。这是白白花掉的真实带宽,而流量窗口正是你会想看到这一点的地方。现在这些行会带上一个“无变化”标签。在这个版本发布之前记录的下载没有构建号可以比较,所以它们会被原样保留,而不会被瞎猜一个结论:未知不等于没有变化。
DuoUpdater 现在会告诉你,让所有应用保持最新,实际在下载流量上花费了多少。 它其实一直在精确到字节地统计为你获取的每一次更新——只是这个统计数字此前没地方显示,只能安静地躺在一个谁也读不到的文件里。现在有了一个“下载流量”窗口,可以从菜单底部的图表按钮打开,本月的流量数字就直接印在那个按钮旁边,不用打开任何东西,最常见的那个问题就已经有答案了。窗口里有:总流量、最近三个月并排对比及其变化幅度、字节来源的构成分析,以及按花费多少给每个应用排名——点开某个应用,还能看到它经历过的每一次更新、版本从哪升到哪,以及那次下载的大小。
这个总数,对自己看不到的部分是坦诚的。 Homebrew、App Store,以及那些通过自己内置更新器完成更新的应用,都是自己获取字节的,DuoUpdater 从来不会经手——所以这个数字只是一个下限,而不是完整的统计。这一点其实一直如此;改变的只是窗口现在会一直显示这句说明,而不再只出现在还没有任何记录时看到的那个空白界面上。
你后来重新命名或删除过的应用,仍然会保留自己的历史记录。 流量是按应用在磁盘上的位置来记录的,这也是为什么同一个应用的两个不同渠道——比如 Android Studio 的 Canary 和 Beta——能够被分开统计,而不是被合并在一起。代价是,一旦应用被重新命名,它的历史记录就被留在了一个已经不存在的旧名字下面。当 OpenAI 把 Codex 改名为 ChatGPT 时,这就把 30 GB 的下载记录拆成了两个看起来像是重复项的条目。现在这些条目会被归到一起、显示在列表底部并变暗,上面有一个标题说明它们的来历。没有任何数据被丢弃,总数里仍然包含它们。
从 App Store 安装的应用,不再被提供开发者自己的下载版本。 很多应用会同时在两个地方发布——一份在 App Store,另一份是从开发者自己网站直接下载——顶着相同的身份,实际却是两个真正不同的构建。直接下载版本通常会领先,因为它不用等 App Store 的审核。对于从 App Store 安装的应用,DuoUpdater 会优先查 App Store;但只要这次查询因为任何原因失败——网络掉线、商店没有响应——它就会悄悄转去查开发者自己的网站,并把那边找到的随便什么版本都提供给你。WhatsApp 就曾因此显示“26.32.75 → 26.33.19”——是个真实存在的版本号,只是来错了地方。如果真的装了它,就会把你的 App Store 版本替换成一个 App Store 自己再也无法更新的版本。现在,从 App Store 安装的应用只会针对 App Store 本身做检查,不会再查别处。
在应用内部切换它的更新渠道,现在会立刻被识别到。 有些应用允许你在普通发布和 beta 发布之间选择——Surge、Tailscale、Fork、OrbStack、IINA、Alfred 等等——DuoUpdater 会跟着你的选择走,这样就永远不会给你推送一个你没要求的构建。它过去只有在你退出应用,或者打开 DuoUpdater 某个窗口的时候,才会注意到这个变化。而这两种情况都覆盖不了人们实际的操作方式:关掉这个设置、让应用继续开着、瞥一眼菜单栏。更糟的是,应用不会在你点击的那一刻就把设置写入磁盘,而是等它自己方便的时候才写——Surge 在这一步就花了五分钟——所以哪怕是“退出应用”这个时机,都有可能读得太早。DuoUpdater 现在会直接监视这个设置本身的变化,并在一两秒内重新检查那个应用。把 Surge 切换回普通发布后,它这一行的 beta 版本号现在会立刻清除,而不会像以前那样最多留上一个小时。
一次什么都没能连上的检查,不会再被误判成一切正常。 当所有来源都失败时——没有网络,或者代理悄悄拒绝了连接,而 Wi-Fi 图标却仍然显示一切正常——检查失败的应用会被隐藏起来,面板只显示“127 个应用 · 均为最新”。而这和一切真的都是最新时看到的画面一模一样,导致一次失败的检查和一次成功的检查根本没法区分。现在面板会说明有多少应用没能连上、具体出了什么问题,并提供重试——而且只重试这些失败的应用,其余已经检查成功的则保持不动。
A helper macOS wouldn't switch on no longer sends you to reset your whole Mac. App Store updates install through a small background item macOS asks you to approve once. When that approval was refused, DuoUpdater had exactly one explanation for it: the system's record of the item is damaged, and repairing it takes a Terminal command that clears the background-item approvals of every app on your Mac. That is one cause among several, and macOS never says which one applies — the ordinary one is that the switch is simply off. The message now leads with that instead: turn DuoUpdater on under "Allow in the Background" in Login Items & Extensions, and DuoUpdater opens the pane for you. The Terminal reset is still written down, as the fallback for when DuoUpdater isn't listed there or switching it on changes nothing. The message also stopped running off the edge of its card.
Two Settings options read on one line again. The two options under Install routing — how App Store updates install, and what happens to apps that ship their own updater — used to put their name to the left of the menu, the way options do everywhere else on macOS. Adding six languages moved both names above their menus instead, because a German or Russian option can be long enough to run off the end of the line, and the safe layout was applied to every language at once. Each row now decides for itself: the name stays beside the menu as long as the menu leaves room for it, and only moves above when the text genuinely needs the width. In English, Japanese and Chinese both rows are back on one line; in German and French they stay stacked, which is the only way they fit without being cut off.
DuoUpdater now speaks Russian, Simplified Chinese, Japanese, German, French and Spanish. It follows whatever language your Mac is set to, and switches with it — there is nothing to turn on. If you would rather read it in a language your Mac isn't set to, macOS can do that per app: System Settings ▸ General ▸ Language & Region ▸ Applications. Everything the app writes itself is translated: the menu-bar panel, every Settings page, the setup window, the notifications, the alerts, and the small print under each option. Counts are handled the way each language actually handles them rather than by bolting an "s" onto the end, which matters most in Russian, where "1 update", "2 updates" and "5 updates" take three different endings — and in Chinese and Japanese, where they take none.
Two things stay in English on purpose. Release notes are the vendor's own words, so they arrive in whatever language the vendor wrote them in — translating them would mean rewriting what a developer said about their own release. And the duo command-line tool stays English, the way command-line tools generally are.
An App Store app that was open when you updated it comes back again. Last release drew a line in the wrong place. It was fixing something real — an update that failed, or that you cancelled, could bring an app back to life minutes after you had closed it yourself — but it decided whether to reopen by asking whether someone had answered a quit prompt. Almost no App Store update shows one: the prompt only appears on a route DuoUpdater stopped using a while ago. So from 0.3.53, updating an App Store app that was running closed it and left it closed, with nothing on screen to say why. The question it asks now is whether the update actually landed, which is the thing that decides whether a quit is coming at all. This is verified against a real App Store update rather than reasoned about: the store terminates your app to replace its files and never brings it back — despite its own prompt promising that it will.
DuoUpdater now tells you when App Store is waiting on you. The store cannot replace an app while it is open, so it puts up a "cannot be open during installation" prompt and waits — with no time limit, holding the update and a download slot for as long as it takes. That prompt is a small panel inside App Store's own window, which may be sitting on some entirely unrelated page; App Store bounces its Dock icon a few times and gives up; and DuoUpdater lives in the menu bar with no Dock icon to bounce. An update could sit there indefinitely with nothing anywhere telling you a click was needed — one took four and a half minutes here, ending only because someone thought to look. The row now says what is being waited for, from the moment the update starts.
The dot that marks an app as open no longer lags behind. macOS does not reliably announce that an app has launched — some apps never trigger it at all, while quitting is always announced. DuoUpdater listened only for those announcements, so a row could insist an app was closed while its window sat in front of you, and only correct itself when some unrelated app happened to open or quit. It now re-derives which apps are running whenever it rescans, which includes the moment you open the menu.
Two things a review caught before you did. The warning that an update was applied without a rollback point was being erased before it could be read: restarting an app cleared the whole note, and restarting after an update is the default. It is now retracted only by whoever wrote it. And Update All could refuse to restart an app — telling you its own updater had a version staged and waiting — when nothing was staged at all: it compared what was on disk now against a build number recorded before the install, so an app whose version name had not changed looked like a conflict.
An app that already downloaded its own update is no longer asked to download it again. Plenty of apps fetch their next version quietly in the background and hold it until you next quit them — that's the "relaunch to update" state you see inside Claude, TablePlus and others. DuoUpdater has always recognised that state in Electron apps and offered you Relaunch instead of an Update, because the bytes are already on your disk. Apps built on Sparkle, which is most of the rest, were invisible to it: TablePlus sat there with a 133 MB download and an unpacked 382 MB copy of 26.9.11 in its cache, while its row offered to fetch 26.9.11 for you all over again. Those apps are now recognised too — the row offers Relaunch, and nothing is downloaded twice.
One thing it deliberately won't do is offer Relaunch for an older build. An app can be holding a version behind the one you have, which happens when a vendor releases to some people before others and DuoUpdater installed the newer one first. Relaunching there would quietly move you backwards, so the row doesn't offer it — and DuoUpdater won't restart the app for you either, because that restart is the exact signal the app's own installer is waiting for.
Three ways an update could go wrong that a review caught before you did. An App Store update that failed, or that you cancelled, could bring the app back to life minutes later — you would quit it yourself and it would reopen, because DuoUpdater had noted "this app may need reopening" before anything had actually closed it. It now only reopens an app when a quit was genuinely asked for. Separately, the check added last release to stop an app's own updater undoing ours compared only the version name: a vendor that ships several builds under one version number slipped straight past it, which is the same failure it was written to prevent. It now compares the build number too. And an app's own updater is no longer assumed to be waiting just because a downloaded copy is sitting in its cache — Sparkle leaves those behind for ten days after an interrupted install, which could have left a Restart button that did nothing but explain itself.
Same changes as 0.3.51, reissued so it can actually reach you. 0.3.51 went out carrying the same internal build number as 0.3.50. That number, not the one in the version name, is what an update check compares — so anyone already running 0.3.50 was told they were up to date and never offered it. This release carries the changes below under a build number that is properly newer. If you are reading this on 0.3.51, nothing about the app changed between the two.
An update that kept coming back. ChatGPT would offer a new version, install it, restart — and a minute later the same update was waiting again. The install was never the problem: the new version really did land on disk. What happened next is that ChatGPT's own updater put a different one back. There are two lists involved, and DuoUpdater was reading the wrong one. The first is everything the vendor has published; the second is what the vendor is actually handing out to your Mac today, which can be an earlier build while a release is still rolling out. DuoUpdater was reading the published list — the same address ChatGPT itself is configured with, which is what made it look right — and offering you a build the vendor was still holding back. ChatGPT had meanwhile downloaded the build it was being offered and parked it, waiting for the app to close. Restarting to apply our update is what closed it. DuoUpdater now asks the same question ChatGPT's own updater asks, so the two agree on what the current version is.
Restarting an app no longer applies somebody else's update. That collision isn't unique to ChatGPT. A lot of apps download their next version quietly and install it the instant you quit them — the "relaunch to update" state you've seen in Claude, TablePlus and others. They'll wait hours for that quit; one was observed holding on for nearly seven. When DuoUpdater restarts an app to put its own update into effect, that restart is exactly the signal they're waiting for, and theirs runs second. Before restarting, DuoUpdater now checks whether anything is waiting, and what version it holds. If it's the same version — which is the common case, and harmless — nothing changes. If it's a different one, the app isn't restarted: the row says what's waiting and leaves the choice to you, rather than quietly swapping in a build you didn't pick.
An App Store update no longer leaves your app closed. Updating an app you have open through the App Store means closing it — the store's own installer quits it to replace the bundle, and doesn't open it again afterwards. DuoUpdater knew to reopen it, but only if you'd answered its own "quit to finish updating" prompt. Answer the identical prompt in the App Store window instead, or take an update that raises no prompt at all, and nothing remembered that your app had been open. It updated correctly, said "Updated ✓", and your app was simply gone — with no restart offered, because by then there was no running app to restart. What decides now is whether the app was running when the update started, which is the thing that was actually true, rather than which window you happened to click in.
豆包输入法 now gets checked, and shows its release notes. It was in the list — DuoUpdater looks inside /Library/Input Methods — but nothing anywhere knew where to ask about it, so the row sat blank forever and read like "nothing to do". It now reads the same endpoint the vendor's own download button reads, and compares the same build number the vendor versions by — so even a re-release that keeps the version name unchanged shows up, rather than passing as the version you already have. The release notes come from the feed the input method's own updater polls, laid out as a proper list rather than a link to a page that doesn't exist. As with 微信输入法, DuoUpdater will tell you a new version is out but will not install it for you: an input method is registered with the system by its installer, not merely copied into place, and quietly swapping the bundle is how you lose your personal dictionary.
Zed's release notes come back instead of an error. DuoUpdater asks GitHub about a lot of apps, and GitHub lets an unidentified caller ask only sixty questions an hour from one network — a budget every GitHub-hosted app on your machine shares. Zed's notes were the ones losing that race, and the panel showed a failure rather than the notes. Those requests now carry the GitHub token you saved in Settings ▸ GitHub, or the one the gh command-line tool already holds if you use it, which lifts the limit far out of the way. Without a token nothing gets worse: Zed's notes now come from a single, smaller source that covers both its channels, where before each one fetched its own web page.
Notion's release notes are about the app you have. They were being read from Notion's product announcements — the page that introduces features as they launch. Those posts are titled by feature, not by version, so nothing on that page ever lined up with the version number on Notion's row, and the notes for the update you were being offered were never there to find. They now come from Notion's own "What's new" page for the Mac and Windows app, where the versions are the ones you can actually compare against.
Some release notes were quietly incomplete, and no longer are. A release's final line could go missing from ChatWise's notes. Two of Postman's releases were cut off mid-sentence — both at the exact point where the text contained a quotation mark. Six apps' notes are now read from the same data the vendor's own site is built from, rather than picked out of the finished page, which is both sturdier and how those two ended up whole. HBuilderX is the one trade-off: its notes now come from the official release document, which covers the HBuilderX editor itself and not the bundled uni-app and uniCloud module logs, so its entries are shorter than before — the same releases, in the vendor's own words, minus the parts about other products.
Release notes that arrive inside an app's update feed are laid out properly. A good number of apps ship their notes as a small piece of a web page tucked inside the feed their updater reads. DuoUpdater used to hand that straight to the system's HTML renderer, which produced a serif font nothing else in the app uses, bullets indented into the margin, and — the part that mattered — a long list could be cut in half, with everything after the cut silently absent. Those notes are now read into the same list layout the rest of the panel uses: right font, right indentation, and nothing dropped. Where the markup is too tangled to be sure of, DuoUpdater leaves it to the old renderer rather than risk showing you part of a list as if it were all of it. TablePro benefits most — its notes had stopped being found at all.
Four more apps show their release notes properly, instead of an embedded web page. Alcove, Docker, Kiro and Waku each publish their notes somewhere a program can read them — Alcove and Docker on their own sites, Kiro as a feed, Waku on GitHub — but DuoUpdater was showing you the web page instead, which meant the vendor's fonts, the vendor's navigation, and no way to move between versions. They now read as proper entries in the list, the same as everything else. Kiro's feed covers three separate products; only the notes for the app you have installed are shown.
A release whose notes are written as sentences no longer vanishes. Some releases don't have a bulleted list of changes — they have a line or two of prose, sometimes only "no public-facing changes in this release". DuoUpdater understood lists and nothing else, so those releases were skipped entirely: if you happened to be running exactly that version, the panel had no entry for it at all, as though your own build had never shipped. They're kept now. Where the notes are laid out in a way that still can't be read cleanly — a table, mostly — the page is shown as before rather than half-converted into something misleading.
Antigravity IDE gets checked. It's a separate app from Antigravity, with its own version, and it was in your list but nothing ever checked it — the row just sat there with no version to compare against and no way to find out. It's checked now. DuoUpdater won't install it for you; it will tell you when a new version is out and where to get it.
DuoUpdater tells you when it updated itself, and what changed. It installs its own updates quietly, on purpose — it waits until you're away from the machine and swaps itself without asking, because a tool that interrupts you to talk about itself is getting in the way of the work it's supposed to protect. The cost was that you'd end up on a new version you never agreed to and never saw the notes for: every other app in your list has a "what changed" panel, and the one app that changed under you in silence was this one. Now the menu says so once, and opens its release notes. It's shown until you read it rather than for a set time — the update this is for is the one that landed while you were asleep. A fresh install doesn't get told it was updated, because it wasn't, and going back to an older build on purpose doesn't either.
And you can read those notes any time. The ✨ button at the bottom of the menu opens every release DuoUpdater has ever shipped, with the one you're running marked in the list.
Switching an app's update channel in that app is noticed straight away. Some apps let you choose between their stable and pre-release builds in their own settings — Tailscale, Fork, Surge, OrbStack, TablePlus, CleanShot, IINA, Alfred, DuoPaste. That choice lives in the app's own preferences, where nothing tells DuoUpdater it has changed, so switching from a beta track back to stable left the row still comparing you against the beta — and still offering it — until the next scheduled check, up to an hour later. DuoUpdater now re-reads that choice when one of those apps opens or quits, and when you come back to its own window, and re-checks just the app that changed.
Tailscale's Release Candidate track is one of the choices DuoUpdater understands. Tailscale publishes three: stable, release candidate, and unstable. Only two were known here, so a Mac opted into release candidates was quietly checked against stable instead, and reported up to date whenever a candidate build was newer than the stable one. All three are now checked against the track you actually chose.
Confirming a quit late no longer leaves the app updated but closed. Some apps guard their own quit with a dialog — Claude, for one, asks about an active conversation — and that dialog could land in the middle of a Relaunch. DuoUpdater rightly refuses to sit there while you decide (and it still won't force the quit past your unsaved work), but once it stepped aside it also stopped listening. Answer the dialog a minute later and the quit went through, the app's own updater swapped in the new version — and then nothing happened: some updaters deliberately don't reopen the app after installing, DuoUpdater was no longer watching, and you were left staring at an app that had simply closed. It now leaves a note for itself when it steps aside: if you do confirm that quit within the next few minutes, it waits for the update to finish landing and then brings the app back, in front if that's where it was. The note expires after a few minutes, so a quit hours later is just you closing the app, and stays that way.
The same late answer now works for a plain Restart. A save prompt could block the Restart button (and the automatic restart that follows a one-click Update) in exactly the same way, with a worse ending: the new version was already on disk, so when you finally dismissed that prompt and the app closed, DuoUpdater's next look around decided there was nothing left to restart and quietly dropped the badge too — an app closed, an update half-applied, and no trace that anything had been asked for. Answering a save prompt within a few minutes now finishes the restart it belonged to, and the app comes back on the new version.
App Store updates tell you they're waiting, and don't leave the app closed if you answer late. When the App Store finishes downloading an update for an app you have open, it asks for that app to be closed before it can install — and DuoUpdater waits, indefinitely and on purpose, for you to say when. Until now that request lived only inside the menu: if you never opened it, an update sat downloaded-but-not-installed all night, and background checks waited with it. It now also arrives as a notification with a Relaunch button, so you can answer it without hunting for the row. And if your app puts up a save prompt of its own after you tap Relaunch, answering it minutes later no longer strands the app closed — the update lands and the app is reopened, the same as if it had quit right away.
The list holds still while you are clicking down it. Every finished update used to re-sort the list on the spot: the app that just landed left the pending group, or picked up a Restart badge and jumped to the top, and everything below it slid up a row — under the pointer of anyone working down a list of Update buttons. Click the top one, go for the next, and the next one had moved. Now the order is held from the first click until the whole round is done: an app that finishes shows its confirmation in place, nothing else moves, and the list settles once at the end, when the finished apps drop away as they always did.
One less version number on a row that needs a restart. An app with an update waiting and an earlier update still needing a restart tried to print all three versions on one line — installed, available, and the older one still running. On four-part versions like Chrome's it did not fit, and the line was cut off exactly where the digits started to differ, so the part left visible said nothing at all. The line now sticks to the comparison that matters — what you have and what is offered — and the running version moved to the row's tooltip, where it fits.
No more Dock icon. DuoUpdater is a menu-bar app — everything it does starts from the icon up there — but it also held a Dock slot, and the only thing that slot ever did was open the same window the menu bar opens. It now runs from the menu bar alone. If you would rather keep the Dock icon, Settings ▸ General ▸ "Hide the Dock icon" turns it back on, and with it the badge that shows the number of pending updates; hidden, that count lives on the menu-bar icon instead.
An app you ignored is no longer checked at all. Ignoring an app hid its row, but every check still asked its vendor after it — one network request per app per round, spent on an answer nothing would ever be said about. On an unauthenticated GitHub budget of sixty requests an hour, those were requests taken from the apps you do watch. Ignoring now means not asking, which is what "hide an app from update checks" always claimed. Naming an app on the command line still checks it, and so does asking for hidden rows, since both are you asking about that app specifically. Skipping a version is unchanged and still checked — whether the version on offer is still the one you skipped can only be known by asking. And un-ignoring re-checks that app on the spot, instead of leaving the row blank until the next round comes due.
An app you ignored stops notifying you. An app that updates itself leaves a "Relaunch to apply it" reminder in Notification Center and repeats it every few minutes until you act on it. That reminder never consulted the ignore list, so an app you had ignored went on sending it — hidden in DuoUpdater's own list, still arriving every five minutes, with nothing on screen to explain where it was coming from. Ignoring an app now silences those reminders and clears any already waiting in Notification Center. Skipping a version does the same for that version.
A download no longer squeezes the app's name or its version off the row. While an app was downloading, the progress bar and its percentage claimed enough of the row that a long name wrapped onto a second line, a long date-style version was clipped at both ends to something unreadable, and at 100% the percentage itself broke across two lines. The row now measures what the name and the version actually need and fits the progress readout into what's left: the bar gives way to a compact ring, and the percentage stays. Nothing is given up until there is genuinely no room for it.
An update no longer looks like it fired twice. With one update pending, installing it briefly emptied the list: the row dropped out the moment the new version reached the disk, the "Everything is up to date" placeholder took its place and jumped the window's height, and then the row reappeared saying "Relaunching...". Nothing was actually wrong underneath — the app still had to be restarted to run the new code — but it read as though something had happened twice. The row now stays where it is from the click through to the relaunch. The step that used to announce "Done" while the app was still being restarted says "Installed" instead; "finished" is left for the confirmation at the end, where it belongs.
The workbench sidebar follows the arrow keys again. Holding an arrow key walked the selection off the edge of the list and left it there, moving through apps that were never drawn and not catching up when the keys stopped. The selected app stays in view.
Moving through that sidebar is quicker. Every keypress was re-deriving, once per app per row, a fact about the whole list that had not changed — on a machine with 124 apps that came to about fifteen thousand redundant filesystem-path lookups per keystroke. It is derived once now. Fast key repeat can still outrun the list; there is more to do here.
An App Store update says "Update", not "Get". When the background helper has not been approved there is no way to install an App Store update in place, so the row hands off to the App Store app instead — but the button for that read "Get", which is what the store says about an app you do not own yet. Every row that reaches it is an app you already have, with an update waiting. It says "Update" now, and when the helper is what is missing the tooltip says so, since approving it in Settings is what turns those updates into one click.
The action column lines up. A checkmark or a small badge at the end of a row was centred in its slot while a wide button sat flush against the row's edge, so the right-hand column read as ragged — and visibly out of line with the Homebrew row pinned below it. Everything ends on the same edge now.
WeType (微信输入法) now reports the version you actually have. Its version was being read off the name of the vendor's installer file, which turns out to carry the installer's version rather than the app's — the installer is a small downloader that fetches the real app separately, and the two numbers drift apart. DuoUpdater now reads the same manifest the vendor's own installer reads, so the version matches your copy and new releases show up when they ship. WeType still has to be updated with the vendor's installer rather than in place: replacing the bundle skips the input-method registration step and was found to lose settings.
A beta build can never arrive on the stable channel. The fix in 0.3.44 looks further back through an app's releases when the newest one has no Mac build attached. That wider search could also see the developer's beta and release-candidate builds, which the normal check never shows you — so an app that happened to publish a release without its Mac download could have offered you a beta. Nothing had actually hit this, and now nothing can: the wider search only ever considers finished releases.
Notion's release notes are readable again. Notion restyled its releases page and DuoUpdater could no longer pick the posts out of it, so the notes fell back to showing the raw web page. They render as proper entries again.
PureMac's updates are visible again. The developer publishes a separate command-line tool from the same place as the app, and its release was being read as if it were the app — as version 1.0.0, which looks older than what you have installed, so the app reported itself up to date and every real update stayed hidden. It now reads only the app's own releases.
An update that only ever existed for phones no longer sits in your list. Some apps share one version number across Mac, Windows, Linux and mobile, and sometimes a release goes out to the phones alone — the version number moves, but no Mac build is ever made. DuoUpdater was reading that as a Mac update, which left an update you could never install and that never went away. It now looks for the Mac download itself rather than trusting the version number, so those releases are correctly ignored. LocalSend was the app affected; its row now reads as up to date, which it is.
LocalSend installed from its own website updates in one click. Now that the right release is identified, its Mac disk image can be installed in place like the rest.
An App Store copy is never replaced with a build from elsewhere. A few apps are published both on the Mac App Store and as a download from their developer, under the same identity — LocalSend is one. Those are genuinely different builds, and the App Store's copy has to keep updating through the App Store. DuoUpdater now leaves those copies to the store instead of ever offering the developer's build over them.
Updates that the wider ecosystem never picks up get flagged for us. Our nightly recipe sweep could only tell whether an app's version could still be read, not whether the answer made sense for a Mac — which is why the LocalSend problem had to be spotted by hand. It now also compares against Homebrew, and raises a flag when we are reporting a version that nobody else has packaged long after it was published. Nothing about this is visible on your Mac; it is how this class of mistake gets caught by machine next time.
Package updates are read more thoroughly before they are opened. The check added in 0.3.41 asks an installer package where it installs, and refuses one that will not say. It was reading only the summary the package publishes about itself; it now also reads the package's own file list, which is what the installer actually follows. That means a package that keeps quiet in its summary is still understood instead of turned away. Every app that updates this way was re-checked against its real installer, and none of them changed.
A download link that stays broken now gets noticed. When a vendor's server has a bad minute, DuoUpdater waits it out rather than crying wolf — but that was letting a download link that had been broken for good slip by unremarked, because it looked the same as a bad minute on any single check. It now tells the difference: brief trouble is still ignored, trouble that lasts is flagged and fixed. Nothing changes on your Mac; this is about broken apps getting repaired sooner instead of quietly staying broken.
Release notes can't be pulled down to an insecure page. A vendor's notes page is loaded over a secure connection, but nothing stopped that page from redirecting itself to an insecure one. Now it can't.
Updating OneNote no longer installs the whole of Microsoft Office. OneNote's update was being fetched from Microsoft's combined Office installer, which puts Word, Excel, PowerPoint, Outlook and OneDrive on your Mac along with it. If you keep OneNote on its own, that was five applications you never asked for. It now downloads Microsoft's standalone OneNote update, which installs OneNote and nothing else.
Package updates are checked more strictly before they are opened. An update that arrives as a macOS installer package now has to say where it installs, and has to name the app you are updating. A package that will not say is refused rather than let through. Every app that updates this way — Office, Edge, Teams, OneDrive, Tailscale, ToDesk, AweSun and the rest — was checked against its real installer first, so this should never fire on a normal update.
Telegram's one-click update stops vanishing when Telegram's servers hiccup. Working out where to download Telegram from needs one extra request to their servers, and those servers were intermittently refusing it — a few minutes at a time, then fine again. When that happened the update was still detected, but the Update button quietly disappeared for that check and you had to go to the website yourself. DuoUpdater now retries before giving up, so a brief hiccup no longer costs you the one-click install.
Package installers are checked against the app they claim to update. Some updates arrive as a macOS installer package, which runs with administrator rights the moment you confirm it. Until now the only check was that the package came from the same developer as the app being updated — which would have let any package from that developer through, not just the right one. DuoUpdater now also reads where the package says it will install and refuses it if that is not the app you are updating. Nothing changes for a normal update; this only ever fires on a package that does not belong.
Release notes only open over a secure connection. The notes pane loads a vendor's own page for some apps, and one of those pages was still being fetched over plain http. Those pages now have to be https, and a handful of other malformed addresses are refused outright rather than loaded.
Fewer apps can go silently missing after a vendor renumbers. Zotero's jump to 10.0 in 0.3.37 exposed a whole class of this: an app disappears from the update list, with no error, because the vendor changed how many numbers are in its version. Twenty-one apps — among them VS Code, Discord, Obsidian, Figma, WhatsApp and GIMP — no longer depend on that staying the same. The rest were checked and deliberately left alone, because for those a looser check would risk reading the wrong number off the page.
The download percentage no longer spills out of its row. While an update downloaded, the number next to the progress bar sat too far right — clipped by the edge of the list instead of lining up with everything else in the column — and there was more empty space between the bar and the number than there needed to be. Both are fixed; the percentage still holds a fixed width, so the row stays steady as it counts up to 100%.
Zotero 10 shows up as an update again. Zotero numbered its new major release 10.0 — two numbers where every previous release had three — and DuoUpdater's check for it quietly stopped recognising the version. Nothing looked wrong: no error, no failed check, Zotero simply never appeared in the list, so anyone still on 9.0.6 was never offered the upgrade. It is recognised again, and the one-click install is unchanged.
A package update that leaves the old copy running now offers to restart it. Some updates install as a package handed to macOS's own installer, and when that finished it left the previous copy still running the old version — with no prompt, so you had to notice and quit it yourself. DuoUpdater now spots that and offers a Restart, the same as it already does for other kinds of update. It only offers one when a copy that was open before the install is genuinely still running the old code — if the installer (or you) already relaunched the app, or it wasn't open, nothing is shown.
WeChat DevTools (微信开发者工具) is checked for updates now — Stable, RC and Nightly each on their own track. It used to sit there as "unknown": since version 2.02 the app reports Electron's stock identity on disk, calling itself version 36.6.0, and all three channels look identical from the outside. DuoUpdater now reads the real version and channel out of the app's own configuration, so whichever track you installed is the only one you are offered — a Nightly install is never handed a Stable build, or the other way round. Updates install in one click, and the release notes for the exact build show up in the window.
App Store updates stop breaking every time DuoUpdater updates itself. Replacing the app left the background helper from the previous copy running, and macOS then never started the new one — so the helper looked switched on while every App Store update failed talking to a copy that no longer existed. Only a restart cleared it. The helper now steps aside when it has been idle for a minute, which means a replaced app heals itself by the next update. For a Mac already in that state there is a Restart Helper button in Settings → Diagnostics and on the update that failed; it asks for an administrator password and takes effect immediately, no restart.
Diagnostics can tell you whether the helper actually works. "Enabled" only ever meant "switched on", and the difference between that and "answering" showed up as a failed update. A Check button now says which one you have, in those words.
Backups: choose what to delete. "Clean Up Now" only removed backups belonging to apps you had uninstalled, so on most Macs it deleted nothing and said nothing while the size stayed put. It now opens a list — every backup with its app icon, the update it would roll back, its size and date — with everything selected and anything you want to keep unselectable. The button says how much the selection frees. Backups whose records had gone missing were previously counted in the total but impossible to see or remove; they are listed too, marked unusable.
A silent self-update no longer leaves DuoUpdater sitting in front of you. When it applied its own update in the background, macOS brought back the windows that had been open — and bringing a window back also brings the app forward, so an update nobody asked for landed on top of whatever was being worked on and stayed there. DuoUpdater now notes which application was in front before it replaces itself, and gives the front back to it on the way in. Windows still return exactly as they were.
Docker updated to the version it said it would. Docker publishes its releases in an order that puts an older one first, and DuoUpdater read the newest version from that list while taking the download link from the top of it — so it fetched 574 MB, kept a backup, installed 4.86.0 over the 4.86.0 already there, and went on offering 4.87.0. Downloads are now matched to the version each entry declares for itself, whatever order a vendor lists them in.
An update that changed nothing is no longer reported as done. The check that runs immediately after installing already knew Docker hadn't moved; it was overruled by a success message and an "Updated ✓". When an update we applied ourselves leaves the app exactly as it was, that is now shown as the failure it is, naming what was installed and what is still on disk.
Silent self-updates no longer wait for an empty screen. With the switch turned on, DuoUpdater would hold its own update back while any of its windows were open — which, for a window people leave open, meant waiting until the app was quit. Open windows no longer delay it (macOS brings them back after the restart), while a DuoUpdater you are actually using still does: it waits for the keyboard and mouse to go quiet before restarting itself.
Installing DuoUpdater's own updates silently now actually is silent. The switch introduced in 0.3.30 downloaded the new version in the background and then still asked before applying it — the one thing it was meant to spare you. It now applies the update itself, at a moment when doing so interrupts nothing: no check or install running, nothing waiting to be relaunched, no DuoUpdater window open, and you working in another app. It restarts itself there, without a prompt. Until such a moment arrives it simply waits, and if none ever comes the update is still applied when you quit — so the wait can delay a version, never lose one. Leaving the switch off is unchanged: you are asked, as before.
An App Store update no longer blames you for a permission you already gave. When DuoUpdater is replaced while it is running — by its own update, or by a rebuild during development — the previous copy of its background helper keeps holding the slot the system reserves for it, while macOS still reports the helper as switched on. Every App Store update then failed with a red line telling you to go turn it on in Login Items, where you would find it already on, and the button offered beside that message quietly did nothing. That state is now recognised and named for what it is, along with the one thing that clears it. DuoUpdater no longer tries to repair it by re-registering the helper: that was measured to switch the background item off and leave it unable to be switched back on.
DuoUpdater can now update itself without asking. Settings → Updates has a new switch for installing DuoUpdater's own updates in the background, taking effect the next time it restarts. It stays off unless you turn it on, and the prompt-and-wait behaviour is unchanged for everyone who leaves it alone.
Its own updates are noticed within the hour. DuoUpdater checked for its own new versions once a day, so a release could sit unseen for most of a day. It now checks hourly.
Eight more AI desktop apps are tracked. OpenCode Desktop and OpenChamber follow their GitHub releases; Wispr Flow, Granola, Comet, Windsurf, AionUi and Msty are read from their vendors' own version endpoints.
Installer packages stay the same package from verification to macOS Installer. DuoUpdater now seals the selected installer before closing or replacing any existing Installer window, checks it again immediately before opening, and refuses the hand-off if another local process changed the file in between. This preserves Sparkle's signature guarantee all the way to the package you see in Installer without making the menu-bar UI pause while large packages are checked.
Multi-installer disk images handle more real-world package names without guessing. Versioned beta, release-candidate, Apple Silicon, and universal package names are recognized when they identify one unique product, while similarly named helpers and sibling products remain excluded. Older bundle-style macOS installer packages are supported by the same integrity checks.
Failed installer downloads are cleaned up immediately. A bad signature, unreadable disk image, cancelled download, or rejected package no longer leaves a full installer sitting in temporary storage until the next day's cleanup.
Signed Sparkle updates that arrive as installer packages now work. A few apps publish a perfectly valid, cryptographically signed .pkg instead of an app archive. DuoUpdater offered those updates, downloaded them, and then tried to unpack the package as though it were a zip — an update that could never finish. They now go to macOS's own Installer, after DuoUpdater verifies both the Sparkle signature on the download and the installer identity inside it.
A disk image containing several installers is no longer allowed to make a guess. Some vendors put a main installer, helpers, and sibling products in one image. Matching on a fragment of the filename could pick a helper simply because its name contained the app's name. DuoUpdater now opens a package only when it is the sole choice or can be identified uniquely; otherwise it stops and leaves the decision to you instead of presenting the wrong installer.
The App Store helper is more tightly scoped to your login session. The privileged helper now takes the account identity directly from macOS's authenticated XPC connection and refuses a request whose claimed user does not match. Normal App Store updates behave exactly as before; the change closes off a signed client from redirecting the helper into another user's session.
Release notes show their formatting instead of its punctuation. Notes that come from a project's GitHub release were rendered exactly as written — **bold** with the asterisks, links as [text](url). Bold is now bold and links are links. This affected every app whose updates come from GitHub, which is most of the open-source ones.
Arrow-keying down the app list no longer crawls. Holding an arrow key felt like moving one row at a time through mud, and long release notes made it worse. Three things were doing it: a permission check on every app in the list ran again for every row drawn; the notes for whichever app you passed through were re-parsed on each keypress; and a long set of notes — one project's runs to 54,000 characters — was laid out in a single pass, which froze the window for about two seconds. The check is now computed once per list, parsed notes are kept, and long notes are laid out only as far as you have scrolled. Worst measured stall went from ~2.1 s to under 0.6 s, and what remains is the deliberate pause before the detail pane catches up rather than a freeze.
Input methods are never updated by replacing the app, and 微信输入法 (WeType)'s one-click from 0.3.25 is withdrawn. Settings were lost on a Mac during the work that added it. What we can show is that the copy in the protected system folder was never actually replaced by DuoUpdater — but an older copy of the input method was installed and launched elsewhere on that machine while testing, inside the window where the settings were rewritten. Nothing here is proven, and an input method's dictionary is not something to test a theory on: WeType now reports its version and sends you to the vendor's installer, which registers the input source with the system — a step that replacing the app bundle skips, and the likely reason that Mac then appeared twice in WeType's own device list.
The refusal is not specific to WeType: DuoUpdater no longer offers a one-click for anything installed as an input method, whichever vendor it comes from. Those apps still report their versions and link out.
Three more apps now report their updates, and all three install with one click. Hidden Bar, XQuartz and EasyFind were sitting in the list as a grey "unknown".
Hidden Bar is the interesting one: it ships with an update feed configured, so from the outside it looked like it was already covered. The feed answers, and is well-formed, and contains no releases at all — which is indistinguishable from a healthy feed until you look inside it. Its version now comes from its release tags instead. EasyFind ships no updater at all, so a copy installed from the vendor's site had no way to learn about new versions.
XQuartz installs through the system installer rather than by replacing the app, because it is not just an app: it lays down a whole X11 stack, and swapping only the app bundle would leave the rest at the old version. macOS asks for the administrator password itself, as it does for any package.
Thirteen more apps now report their updates, twelve of them with one click. GIMP, MongoDB Compass, Meld, Emacs, Tor Browser, Zotero, GrandPerspective, TigerVNC, qBittorrent, Opera, LibreOffice, pgAdmin 4 and Telegram Desktop were all sitting in the list as a grey "unknown" — installed, with nothing to say about them. Each was worked out by downloading the vendor's actual build and reading its identity out of it, so a one-click only appears where the download is signed by the same developer as the copy you already have. The exception is qBittorrent: its own build isn't signed by an identified developer at all, so it reports its version and sends you to the project's page.
Opera, LibreOffice and pgAdmin 4 nearly joined that exception. All three publish nothing but a directory listing, and listings sort alphabetically — "100" comes before "99" — so the newest release is not the first one on the page. Reading the version was never the problem; building a download link was, because the obvious way to build one would have picked whichever release happened to be listed first. They now download the release that was actually compared. That is the one mistake a signature check cannot catch for you: an older build of the right app, signed perfectly.
1Password and Inkscape now install with one click too, and both were previously written off. 1Password's official download turns out to be a small installer program rather than the app — signed and notarised by 1Password, so every safety check passes it, and installing it would have replaced your password manager with its own installer. DuoUpdater now fetches the package that installer itself downloads. Inkscape's download page hands out its file through a one-time link that changes with every release; the same file also sits at a plain, predictable address, which is what gets used.
微信输入法 (WeType) now installs with one click. It lives in a folder only an administrator can write to, which is why it used to only report its version. It now goes through the same administrator prompt as any other app in a protected location.
Release notes for Opera, Inkscape and 1Password. Opera publishes one page per major version, Inkscape one wiki page per release, and 1Password a feed — all three now render as proper change lists in the app instead of a link out. 1Password's version and its notes both come from that feed now, which is a published interface, rather than from scraping the page beside it.
Discord's version check works again. Discord moved its downloads to a different server and the check was still looking at the old address, so DuoUpdater quietly reported "no version" for Discord Stable while everything else kept working. It now keys off the part of the address that names the release channel, which is the part that actually has to be right.
Fifteen more apps now report their updates, and all but one install with one click. Rancher Desktop, Cherry Studio, RedisInsight, Upscayl, WailBrew, Wave Terminal, Lens, Termius, Unity Hub, iStat Menus, Inkscape, Google Gemini, Antigravity, AnyDesk and Kiro were all showing as a grey "unknown" — installed, with nothing to say about them. Each one was worked out by reading the vendor's own build rather than trusting a download page: the version now comes from wherever that app's own updater looks, and a one-click only appears where the download is signed by the same developer as the copy you already have. Three of them (Google Gemini, Antigravity, Kiro) publish nothing a download page can be scraped for; their real update services answer the same questions their own updaters ask, so that is what DuoUpdater asks too.
AnyDesk in particular was written off and shouldn't have been. Its download and changelog pages both refuse anything that isn't a person with a browser, so an earlier sweep concluded the app was unreachable. The plain-text changelog on the same server answers fine — and it is what AnyDesk's own Homebrew entry has always read.
Updating an app in a location that needs an administrator password now asks, once. Most apps live in /Applications, which you can write to; a few — input methods, for one — live where only an administrator can. Those used to show an Update button that could never work. Now the button asks for the password, and if you dismiss that prompt DuoUpdater takes the hint: the row switches to Open and stops asking on every release. "Ask for administrator access again" in the row's right-click menu brings the button back. The choice is remembered for that copy of the app specifically, so declining for one install doesn't silence another.
An up-to-date Xcode beta no longer claims the vendor is behind it. Under "Show all", a row whose vendor has fallen behind what you have installed shows a muted note saying so — you're ahead, nothing to do. Xcode was getting that note while sitting on exactly the build Apple was offering: it publishes a build number plus a human label ("27.0 beta 5"), and comparing that label against the plain "27.0" the bundle reports made a release look newer than its own beta. The note now settles on the build whenever both sides have one, so the same release is recognised as the same release however it is labelled. A vendor that has genuinely fallen behind is still called out.
44 more apps now report their updates. Apps that publish on GitHub but ship no update feed of their own used to sit in the list as a grey "unknown" — DuoUpdater could see them installed and had nothing to say about them. Bruno, UTM, kitty, KeePassXC, Godot, Bitwarden, VSCodium, draw.io, Podman Desktop, Anki, Raspberry Pi Imager, LuLu, MarkEdit, Clash Verge, Freelens, Tabby, Espanso, Moonlight, SwiftBar, Sequel Ace, balenaEtcher, DB Browser for SQLite, OpenLens, Headlamp, OpenMTP, Goose, Caffeine, noTunes, KeepingYouAwake, MiddleClick and a dozen more now show a real version, and 36 of them install with one click like any other app. Which ones was decided by downloading each vendor's actual build and reading the identity out of it, so a one-click only appears where the download is signed by the same developer as the copy you already have. Seven — Alacritty, Flameshot, MarkText, darktable, OWASP ZAP, BlueBubbles and Wine — publish builds Apple hasn't notarised, so those report their version and send you to the vendor rather than installing anything. LocalSend is report-only for a different reason, corrected here after this version shipped: its builds are notarised, but its newest release attaches no macOS download at all, so there is nothing to install until the project starts publishing one again.
Apps that already carry a Sparkle feed needed nothing: they were checked as part of this sweep and were already working, which is why names like Rectangle, Maccy, iTerm2 and Telegram aren't in the list above.
An update that your Mac couldn't run is now refused rather than installed. Where a developer publishes one download per processor, DuoUpdater picks between them by the file's name — and names are not always honest: three of the apps above ship an Apple silicon build under a name that says nothing about it, or says the opposite. Before an app is replaced, its new version is now checked against the processor in your Mac, read out of the program itself instead of its name. If it can't run here, the update stops and your working copy is left exactly as it was. Nothing about a normal update changes; this is the case that used to end with an app that no longer opened.
An unanswered permission prompt no longer leaves the update check hanging. To tell a TestFlight build apart from an App Store one, DuoUpdater reads TestFlight's own database, and macOS keeps that behind the "access data from other apps" permission. Until that was answered the read didn't fail — it waited, indefinitely, for a prompt that might be sitting behind another window or might never be answered at all, and the scan behind it simply never finished. Nothing timed out and nothing said why. It now waits a few seconds and then carries on without TestFlight's side of the story; the only thing missing in the meantime is whether those particular apps came from TestFlight, and it sorts itself out on the next scan once the permission is granted.
Homebrew updates work behind a proxy. If your Mac reaches the internet through a proxy, upgrading brew packages failed with curl: (28) Failed to connect while every other update went through fine. Homebrew shells out to curl, which — unlike the rest of DuoUpdater's networking — doesn't read the proxy you configured in System Settings; it only reads proxy environment variables, and an app launched from the Dock or at login has none. DuoUpdater now passes your system proxy settings down to Homebrew itself. Nothing changes on a machine with no proxy configured, and a proxy you've already exported in your own shell still wins.
Claude's updates now show up while they're still rolling out. Anthropic releases Claude in stages: a build goes to a fraction of Macs at a time, and the public download page only catches up at the end. DuoUpdater was reading that public page, so for the whole of a rollout — most of a day, in the case of 1.30096.5 — it told you Claude was up to date while Claude itself had already quietly downloaded the new version and was waiting for a relaunch. It now also asks the same endpoint Claude's own updater asks, which answers for your Mac specifically, and offers whichever of the two is further ahead. Nothing about which build you're offered has changed: it is either the public release or the one your Mac was already allocated. Claude also gains real publication times, so its releases now appear in the Release Log with the moment Anthropic shipped them rather than an estimate.
A superseded package update no longer leaves its window sitting there. Updates that go through macOS's own installer — Microsoft Office, AweSun, ToDesk — open an Installer window and then wait for you. If you left one open and a newer release came along, installing that one opened a second window, and they stacked up. The older window is now closed once its replacement is ready, and its download cleaned up with it. A window that's mid-install, or asking for your password, is left strictly alone.
duo says which copy is which when two apps share a name. Naming an app that is installed twice — two Xcode betas, say — printed both candidates as a bare "Xcode" and told you to name one exactly, which matches both again. The listing now carries each copy's version, and when the matches genuinely share a name it asks for the path instead of repeating advice that cannot work.
"Open download page" no longer downloads a file. On apps whose page DuoUpdater knows — ToDesk and UU Remote among them — that button handed your browser the installer package instead of opening anything: the link it used was the same one the updater downloads from, so clicking it started a download you didn't ask for. The page and the package are now kept apart, and the button opens the vendor's actual download page. Where a source only ever publishes a package and no page at all — a bare Sparkle feed — there is now no button rather than one that downloads something.
The app list responds to the arrow keys again. Opening the workbench window left the keyboard focus nowhere in particular, so ↑ and ↓ did nothing until you clicked a row — and after clicking into the release notes on the right, or switching to another app and back, they stopped working again. The list now takes the keyboard when it opens and takes it back at the points it used to lose it. Typing in the search box is untouched: a search you've started keeps the caret.
The Brew section starts collapsed. Casks and command-line formulae are a side channel for most people, and having that tree open by default pushed your actual apps up the sidebar every time the window opened. It now starts closed and remembers however you leave it.
Xcode betas and release candidates are now detected, and two copies can be told apart. Xcode was a grey "v27.0" with no update information at all, and if you keep more than one build around — a current beta beside the previous one — they were indistinguishable: same name, same version, same icon. Each row now reads its real build, so an update shows as "27.0 beta 1 (27A5194q) → 27.0 beta 5 (27A5237l)". Which track a copy belongs to is worked out from Apple's published builds rather than guessed from what you named the folder, and you'll only ever be pointed at something at least as finished as what you have — a beta can be superseded by a beta, a release candidate or the finished release, never the other way round. Updating still means going to Apple: the downloads need you signed in with your Apple ID, so the row links to Apple's download page and its release notes.
Cursor's release notes are shown properly instead of an embedded web page. Cursor writes its changelog as dated posts rather than numbered releases, so the notes pane fell back to loading the website. Each post is now shown as its own entry — its date, its headline and its changes — the same as every other app with readable notes.
duo check now shows what changed when the version number doesn't. Updates that keep the same version and only move the build — Surge, the JetBrains previews — printed as "6.9.0 → 6.9.0" on the command line, which was accurate and told you nothing. It now shows the builds, matching what the menu bar has always shown.
Apps that ship their own updater are now updated directly by default. These are the ones like Chrome, VS Code, Cursor and the Electron apps — and because they are also the apps you tend to leave running all day, the old default of stepping aside while they were open meant they were almost never updated at all: the row offered to open the app and left the rest to you. DuoUpdater now downloads the vendor's own installer and applies it whether or not the app is running, then quits and relaunches it so the new version takes effect. Anything that installs background components alongside the app — Tailscale, Office — ships a package that macOS's own installer handles, so those pieces are still put in place properly. If you would rather nothing was touched while an app is open, Settings → General → Self-updating apps still has the old behaviour, and changing it back does not affect anything already installed.
An app whose developer changed its update signing key can be updated again. Apps that update through Sparkle sign each release with a key, and the copy you already have carries the matching public key to check it against. If a developer generates a new key and ships it without a hand-over release signed by the old one, that check fails for everybody — the app's own updater is just as stuck as DuoUpdater was, and the update sits there refusing to install with a signature error. DuoUpdater now recognises that specific situation: when the new download carries a different key of its own and the release was signed with it, it stops trusting the signature and falls back to the same checks it uses for apps that publish no signature at all — the download must be validly signed by Apple's developer certificates, and by the same developer as the app it is replacing, for the same app. A download that fails any of that is still refused, as is a bad signature that isn't explained by a key change. Mirage Beacon 1.3.0 was the first to hit this.
Update All no longer says a running app is finished before its restart. When an update had already replaced an app on disk but Update All was still busy with other installers, the row briefly showed a green checkmark and disappeared even though the old version was still running. The app now stays visible with its running and installed versions, explains that it is waiting for the batch restart, and offers Restart now. The completion checkmark appears only when the update is actually in effect.
Apps installed by a .pkg can now be rolled back. DuoUpdater keeps a copy of the previous version before it updates an app, so a bad update can be undone. Apps that install through macOS's own installer — Microsoft Office, AweSun, ToDesk and the like — never got that copy: the rollback was skipped for them entirely, so the one kind of update DuoUpdater can't watch land was also the one you couldn't back out of. They're now backed up like everything else.
Rollback no longer refuses apps that write inside their own bundle. Some apps keep working files in amongst their own program files — ToDesk stores its settings database and logs there, and doing so breaks the seal Apple puts on an app. DuoUpdater checked that seal before restoring a backup, so for those apps it declared a perfectly good backup damaged and refused to put it back. It now checks the copy against a fingerprint taken when the copy was made, which is the thing that actually matters: that what's being restored is exactly what was saved. Tampering with a stored backup is still caught, and still refused.
When a backup isn't possible, it says so instead of failing quietly. A few apps keep program files that your account simply can't read — EasyConnect is one — and no copy of those can be made. Rather than attempting it and reporting a failure part-way through an update, DuoUpdater now checks first, tells you which file is in the way, and updates anyway; you just don't get a rollback point for that one app.
The same app no longer appears several times over. Some apps leave a dated copy of themselves behind every time they update — DuoPaste, for one, parks a DuoPaste.backup-20260716-183428.app next to the real thing on each self-update. Those copies are complete, working app bundles as far as anything on disk can tell, so DuoUpdater listed each one as its own app: three identical DuoPaste rows, each offering the same update. Worse, taking one of those offers would have installed the new version into the backup, leaving the app you actually use untouched and creating another stray copy. Backup and duplicate bundles are now recognised for what they are and left out of the list, as are exact clones of an app found in two places. Genuinely separate installs that happen to share an identity — Firefox alongside Firefox Beta, two versions of Android Studio kept side by side — still each get their own row.
The list now reliably notices apps appearing and disappearing. DuoUpdater watches your Applications folders so that an app updating itself in the background, or one you drag to the Trash, is reflected within a few seconds. That watch could quietly stop working — nothing crashed, nothing was reported, it simply stopped hearing about changes, and the list then went stale until you reopened the window. It's now rebuilt periodically and after your Mac wakes from sleep, with a fresh scan each time, so a watch that dies recovers on its own instead of staying dead for the rest of the session.
An app that gets restarted after an update no longer jumps in front of what you're doing. When DuoUpdater updates an app that's currently running, it quits and reopens it so the new version actually takes effect. Reopening it also pulled it to the front — so an update to something sitting quietly in the background could drop a window on top of the thing you were typing into. The app's position now survives the restart: whatever was in front comes back in front, and whatever was in the background comes back in the background, still there and still updated, just not in your way. The same applies to apps DuoUpdater reopens after an App Store update. Apps that weren't running at all are, as before, updated on disk and left closed — updating an app never starts it up.
Fixes AweSun's update failing with "the server returned HTTP 404". DuoUpdater worked out where to download AweSun's installer by building the filename itself from the version number. Oray then renamed the file — the same 16.6.0 build, one letter's difference — and every attempt at the update hit a dead link. It now takes the filename straight from Oray's own download listing rather than guessing at it, so a future rename won't break it again.
An installer you've already downloaded no longer downloads again. Some apps update through an installer package that macOS opens for you to confirm. If you closed that window without finishing — or quit DuoUpdater and came back — the row went back to offering "Update", and taking it fetched the whole package a second time. ToDesk's is 375 MB. The download was on your disk the entire time; nothing was pointing at it. Those rows now offer "Install" instead, which just re-opens the file you already have. If the installer window is still open it comes forward rather than opening a second one, and the offer stands until either the download is gone or a newer version comes out — at which point the old package would be the wrong one, so the row goes back to a normal "Update".
Homebrew packages that aren't apps now show up. DuoUpdater tracked outdated Homebrew formulae, and left casks alone on the grounds that a cask installs an app, which already gets its own row. That holds right up until a cask installs no app — a command-line tool like codex, a font, a driver. Those had no row anywhere: nothing for the app list to find, and not a formula either. codex sat three versions behind without a word. They're now part of the Homebrew panel, which reads "packages" rather than "formulae" to match. Casks that do install an app are still managed per-app exactly as before, and apps that update themselves are still left to their own updater.
"Update All" now includes apps that install from a package. A handful of apps — ToDesk and AweSun among them — ship their update as an installer package rather than something DuoUpdater can swap into place on its own. Those were quietly left out of "Update All" and had to be updated one row at a time; if such an app was the only other update pending, the button disappeared altogether rather than acting on just one. They're now part of the batch, and they run at the very end: everything that updates unattended finishes first, so nothing opens a window or asks for your admin password until the rest is already done. One caveat worth knowing — DuoUpdater can't tell when macOS's installer has finished, so if two package updates come up in the same batch, both installer windows open one after the other rather than waiting in line.
Fixes updates going unnoticed for days at a time. DuoUpdater re-uses the answers it gets from each app's version feed so it isn't re-downloading the same file every few minutes. The problem was how long it trusted a stored answer: when a vendor's server doesn't say how long its reply stays valid, macOS guesses — and it guesses longer the longer that feed has gone unchanged. So the very feeds that had been quiet for a while were exactly the ones DuoUpdater stopped re-reading, and a new release could sit there for days with the app still reporting "up to date" and no error to show for it. Every version check now always asks the server whether anything changed, while still skipping the download when nothing has. OrbStack 2.2.2 is the release that surfaced this; the same blind spot applied to most apps checked directly against their vendor, including Chrome, Cursor, Claude, ChatGPT, Warp, Spotify, and Visual Studio Code.
Homebrew-managed apps no longer get stuck at the version they were on when DuoUpdater started. The catalog DuoUpdater reads to learn the latest version of a Homebrew app was loaded once per launch and never refreshed, which is invisible if you quit the app daily and wrong if you leave it running for weeks. It now refreshes periodically. As a bonus, machines with no Homebrew casks installed no longer download that 5 MB catalog at all.
Uses less memory and does less work in the background. This release is entirely under the hood — nothing about what DuoUpdater does has changed, only what it costs to leave running. Every update it downloaded used to leave a small amount of memory behind that was never reclaimed; harmless once, but it adds up over the weeks a menu-bar app tends to stay open. Separately, while the main window was open DuoUpdater re-read every installed app from disk every 15 seconds and started a system process each time to see what was running — that now happens every three minutes, since the filesystem watcher already notices a real change the moment it happens. Recording the release history after each check also used to save its file once per app rather than once per check, and release notes could be fetched more than once when the same page was already on its way in.
No more beachball while an app is relaunching. Clicking Update on another app while one was being quit and relaunched could freeze DuoUpdater for a moment — the spinning rainbow cursor, an unresponsive window, a click that seemingly did nothing. Relaunching an app now happens in the background instead of on the interface, so the rest of the list stays live and clickable throughout. The same freeze could show up when opening an app from a row's right-click menu, or when handing an update off to an app's own updater; both are fixed too.
Backups from uninstalled apps are now cleaned up automatically. DuoUpdater keeps one backup of an app's previous version so an update can be rolled back. Backups for apps you've since uninstalled or moved were never reclaimed and could quietly pile up gigabytes of disk space over time. They're now deleted automatically during the regular update check. Settings shows how much space backups are currently using, with a toggle to turn off the automatic cleanup and a "Clean Up Now" button to run it on demand.
JetBrains Toolbox apps no longer show a stuck or incorrect "update available." Version checks for Toolbox-managed apps (IntelliJ, Android Studio, Fleet, Air, and others) now always ask live rather than sometimes falling back to a local cache that could never actually report a new version — it fixes both a status that lingered after Toolbox had already installed the update, and one that never appeared in the first place.
Claude Desktop's release notes are now shown in DuoUpdater. Update entries for Claude Desktop now include Anthropic's own per-version changelog instead of a generic notice.
Update All now also relaunches apps that were only waiting on a restart. If an app had already downloaded its update and just needed a relaunch to finish — Claude, for instance — clicking Update All used to skip it, leaving a stray "Relaunch" button behind. It now relaunches those too, in the same pass, whenever automatic restart-after-update is on.
App Store updates recover from a receipt hiccup instead of just failing. Occasionally a Mac App Store update downloads in full but the very last install step trips over a "receipt" error — a transient App Store glitch that a second attempt usually clears. DuoUpdater now retries once automatically. If it still doesn't take, the row offers an "Open App Store" button to finish the update from the App Store's Updates page, instead of leaving a raw error on screen.
ToDesk update detection fixed. A change to ToDesk's download page stopped DuoUpdater from reading its latest Mac version, so ToDesk updates went unnoticed. Detection now reads the version reliably again.
App Store updates no longer show a scary error for an app that's already up to date. If the Mac App Store had quietly updated an app in the background — TestFlight, say — DuoUpdater's row could go stale and, on Update, try to reinstall the version that was already there. macOS's installer rejects that with an alarming red "The upgrade failed", even though nothing was actually wrong. DuoUpdater now confirms an App Store app really is behind before reinstalling, and treats a no-op reinstall as "already up to date" — settling the row quietly instead of showing an error.
App Store updates now ride out network hiccups. A brief connection drop mid-update — a flaky link, or a proxy resetting the connection — used to fail an App Store update outright with a "could not connect to the server" error. Those updates now retry automatically a few times before giving up, so a momentary blip no longer strands an update that a second attempt lands cleanly. Clicking Update again after a failure also clears the old error immediately, instead of leaving it on screen next to the spinner.
Apps that update themselves clear from the list faster. When an app like Chrome finishes updating itself in the background while an App Store update is running, its "update available" row now clears promptly — it no longer lingers until the rest of the queue finishes.
One-click updates for four more apps. HBuilderX, JetBrains Toolbox, and Microsoft Edge's Beta and Dev channels now update in place with a single click, instead of only telling you that an update exists. HBuilderX also now reads its version straight from DCloud's own release feed, so it picks up new builds sooner and more reliably.
Self-updating apps stay in their own lane. A running app that ships its own Sparkle updater is now handed off to that updater — the same courtesy DuoUpdater already gave other self-updating apps — instead of being replaced underneath it, unless you've chosen "Always replace" in Settings.
Fixes
Fixes
See when your apps actually ship. DuoUpdater now keeps a Release Log: a running timeline of every release the apps you track put out, each stamped with its publish time. Open it from the clock icon at the bottom of the popover.
Release-habit heatmap. A new Patterns view charts releases by weekday and hour, so you can see when an app tends to ship — pick any single app for its own pattern and version history, or view all of them together. History is backfilled from each app's update feed, so the heatmap is useful right away instead of starting empty.
Honest about what it can't time. Apps that publish an exact release date (Sparkle, GitHub, Alcove) are timed to the minute. Apps that only expose a version number get a clearly-marked "≈" estimated window — bounded by when DuoUpdater last saw the old version and first saw the new one — and never skew the heatmap.
Fixes
Passwordless App Store updates. Updating Mac App Store apps no longer interrupts you for your password every time. DuoUpdater now installs a small, signed privileged helper (one-time approval) and bundles mas, so App Store updates apply directly in the background.
Cleaner "Restart to finish" lines. When an app updates itself on disk while it's still running, the pending-restart line now shows the real marketing version on both sides — e.g. 1.8.x (build) → 1.9.0 (build) instead of a bare build number on the left.
Fixes
Apps that update themselves now clear correctly. If an app updated through its own updater (for example, Chrome via "About Chrome") while DuoUpdater was busy installing other updates, it could keep showing a stale "update available" row long after it was already current. DuoUpdater now re-checks the moment the installs finish, so the row clears right away instead of lingering.
"Update All" shows the whole queue. Every app in an "Update All" run now shows a "Queued" state immediately, instead of leaving the ones further down the list looking idle with a clickable Update button. Clicking Update on an app that's already queued can no longer start a second install of it.