未签名的 macOS 构建包,是怎么把「划词翻译」搞坏的
一次真实排查记录:从「快捷键划词后翻译框是空的」,一路查到 macOS 的 TCC 权限与代码签名机制,最后用一张免费的自签名证书收尾。
案例项目:pot-desktop(Tauri 1.x + React),环境 macOS 26 / Apple Silicon。
但结论适用于任何需要辅助功能权限(Accessibility)的 macOS 应用:划词取词、全局快捷键、模拟按键、屏幕录制、自动化脚本类工具都会踩同一个坑。
摘要:先说结论(TL;DR)
如果你的 macOS 应用需要辅助功能权限,而你在用未签名 / ad-hoc 签名的自建包(比如 CI 里跑的测试构建),那么:
每次重新构建,系统授予的权限都会失效,而且失效得非常安静——不报错、不弹窗,功能只是「不工作了」。
根因是 macOS 的权限记录(TCC)绑定的是代码签名身份,而 ad-hoc 签名没有稳定身份。
个人本地使用的最省事解法:用钥匙串里自建的一张自签名证书重签一次 app,免费、不需要 Apple 开发者账号、不需要重新构建。
一条命令是核心:
codesign --force --deep --sign pot-local /Applications/pot.app
想知道为什么、以及有哪些坑,继续往下读。
一、症状:功能「静默」失效
用户视角的描述只有一句话:
使用快捷键把选中的文本翻译,但是现在没有自动粘贴到翻译框中。
具体表现:
最容易误判的地方就在这里:窗口弹出来了,说明快捷键注册成功、进程活着、UI 正常。于是第一反应必然是「前端代码被改坏了」——尤其当你前几天刚动过翻译窗口的代码。
我一开始也是这么怀疑的。但代码是无辜的。
二、排查过程:先证伪,再看日志
2.1 第一步:用 git 证伪「代码被改坏了」
在读任何业务逻辑之前,先确认改动范围。这一步能省掉大量瞎猜:
# 当前分支相对主分支到底动了什么
git diff --stat master...HEAD
# 取词链路上的关键文件,最后一次改动是什么时候
git log --oneline -3 -- src-tauri/src/window.rs src-tauri/src/cmd.rs
结果很清楚:本分支只动了 CI 配置、文档、i18n 和两个前端组件;而取词链路上的三个关键文件——源文本框组件、Rust 侧的取词函数、取词结果的读取命令——一行都没改,最后一次改动还是几个月前的上游提交。
经验:怀疑「某功能被改坏」时,先用 git log -- <相关文件> 划定嫌疑范围。如果嫌疑文件根本没被碰过,那问题就在代码之外(环境、权限、配置、依赖),继续读代码是浪费时间。
2.2 第二步:读日志(这一步给出了答案)
Tauri 应用的日志默认在这里(把 com.pot-app.desktop 换成你自己的 bundle identifier):
# macOS
tail -f ~/Library/Logs/com.pot-app.desktop/pot.log
日志量很大(TRACE 级别会把每个 HTTP 分块都记下来),所以直接过滤关键词。取词失败最可能的关键词就是权限、取词函数名、以及事件名:
grep -n "Accessibility\|get_text\|selection\|reject:" ~/Library/Logs/com.pot-app.desktop/pot.log | tail -30
三行输出就把问题钉死了:
[13:28:00][INFO ][pot] MacOS Accessibility Trusted: false
[13:28:08][ERROR][selection::macos] get_selected_text_by_ax error:No selected element
[13:28:08][ERROR][selection::macos] get_text_by_clipboard error: Output {
status: ExitStatus(unix_wait_status(256)), stdout: "",
stderr: "507:541: execution error: “System Events”遇到一个错误:“osascript”不允许发送按键。 (1002)\n" }
翻译一下这三行说了什么:
启动时辅助功能权限就是
false—— 应用自己知道自己没权限。主路径失败:通过 macOS 的 Accessibility API(AX)直接读取「当前选中的文本」,失败。
兜底路径也失败:退化成用
osascript模拟按Cmd+C把选中内容复制到剪贴板再读,被系统拒绝——错误码1002就是「没有自动化/辅助功能权限」。
2.3 为什么「静默」失效
看一眼取词的调用链就明白了。以 pot 用的 selection crate 为例,Rust 侧逻辑简化后是这样:
pub fn selection_translate() {
let text = selection::get_text(); // ← 两条路都失败时返回空字符串
if !text.trim().is_empty() { // ← 空字符串:跳过写入
state.0.lock().unwrap().replace_range(.., &text);
}
let window = translate_window(); // ← 窗口照样创建
window.emit("new_text", text).unwrap(); // ← 照样发事件,只是载荷是空的
}
关键在于 get_text() 的失败被吞成了空字符串,而不是 Result::Err。于是:
flowchart TD
A["按下 Alt+C"] --> B["selection::get_text()"]
B --> C{"AX API 读取选中文本"}
C -- "权限不足" --> D{"回退:osascript 模拟 Cmd+C"}
C -- 成功 --> G["拿到文本"]
D -- "权限不足" --> E["返回空字符串 """]
D -- 成功 --> G
E --> F["窗口照常弹出<br/>emit('new_text', '')"]
G --> F2["窗口弹出<br/>emit('new_text', text)"]
F --> H["前端源文本框:空<br/>❌ 无报错、无提示"]
F2 --> I["前端源文本框:有文本<br/>✅ 触发翻译"]
style E fill:#ffe0e0,stroke:#c00
style H fill:#ffe0e0,stroke:#c00
style I fill:#e0ffe0,stroke:#0a0
「窗口能弹出」和「取词成功」是两件完全独立的事,前者不能作为后者的证据。这正是这个 bug 具有欺骗性的原因。
经验:排查这类问题时,日志的价值远高于读代码。尤其是权限类问题——它不会抛异常,只会让某个返回值变成空/默认值。如果日志里没有相关记录,值得先给可疑链路补上日志再复现一次。
三、根因:TCC 权限绑在代码签名上
到这里问题变成了:权限为什么会没了? 明明之前用官方发布版的时候,划词一直是好的。
区别在于「之前用的是官方签名版,现在用的是自己 CI 打的测试包」。验证一下这个包的签名状态:
codesign -dv /Applications/pot.app
出问题时的输出(这是坏的状态):
Executable=/Applications/pot.app/Contents/MacOS/pot
Identifier=pot-c521813909554c9a ← ⚠️ 不是 com.pot-app.desktop!
Format=app bundle with Mach-O thin (arm64)
CodeDirectory v=20400 ... flags=0x20002(adhoc,linker-signed) ← ⚠️ ad-hoc
Signature=adhoc ← ⚠️ 没有真正的签名
Info.plist=not bound ← ⚠️ Info.plist 没被签名覆盖
TeamIdentifier=not set
Sealed Resources=none ← ⚠️ 资源没被封装
四个警告信号,每一个都在说同一件事:这不是一个被正确签名的 app bundle。
3.1 什么是 ad-hoc / linker-signed
Apple Silicon 上,所有可执行文件必须有签名才能运行。所以当你不提供任何证书就构建时,链接器会自作主张打一个「ad-hoc 签名」——只是对二进制内容做哈希,不涉及任何身份。
后果是 Identifier 不来自 Info.plist 的 bundle id,而是从二进制文件名 + 内容哈希临时生成的,所以变成了 pot-c521813909554c9a 这种鬼东西。在系统眼里,这跟 com.pot-app.desktop 是两个毫不相干的应用。
3.2 TCC 是怎么记住「你授权过」的
TCC(Transparency, Consent, and Control)是 macOS 管理隐私授权的子系统,辅助功能、屏幕录制、麦克风、自动化这些开关都归它管。它的记录不是「记住某个路径」——否则把恶意程序改名成 pot.app 放到同一个位置就能白拿权限了。
它记的是两样东西:bundle identifier + 代码签名的「指定要求」(Designated Requirement, DR)。
DR 可以直接看:
codesign -d -r- /Applications/pot.app
用正规证书签过之后是这样:
designated => identifier "com.pot-app.desktop" and certificate leaf = H"5860018...8AD593"
^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
bundle id 必须匹配 证书指纹必须匹配
而 ad-hoc 签名没有证书,它的 DR 只能退化成「二进制内容哈希(cdhash)必须完全一致」。
于是就有了这个致命差异:
┌─────────────────────────────────────────────────────────────────┐
│ ad-hoc 签名(坏) │
│ │
│ 构建 #1 → cdhash=aaa… → 你在系统设置里授权 → ✅ 能用 │
│ 改一行代码重新构建 │
│ 构建 #2 → cdhash=bbb… → TCC 记录对不上 → ❌ 静默失效 │
│ (旧记录还留在列表里,开关还是打开的) │
├─────────────────────────────────────────────────────────────────┤
│ 证书签名(好) │
│ │
│ 构建 #1 → DR: id + 证书指纹 → 授权一次 → ✅ │
│ 改代码、重新构建、换版本号…(证书不变) │
│ 构建 #N → DR 完全相同 → 无需再授权 → ✅ │
└─────────────────────────────────────────────────────────────────┘
最坑的地方:失效后,系统设置 → 辅助功能列表里那个 pot 条目还在,开关还是打开的。你看着 UI 一切正常,实际上那条记录对应的是一个已经不存在的旧签名。这就是为什么「静默」——用户看不到任何异常,只能看到功能不工作。
⚠️ 关于 TCC 的内部实现,Apple 没有公开文档。上面的模型来自可观测行为(DR 内容、重签后权限失效/保持的实际结果)与社区共识,用来解释和预测现象足够可靠,但不必当成精确的实现描述。
3.3 为什么会踩到这个坑
我这次的直接触发原因是改了 CI:为了能在没有 Apple 证书的情况下打测试包给自己用,我在 workflow 里显式清掉了所有签名相关的环境变量:
# .github/workflows/package.yml —— 测试构建分支
- name: Build and Package
run: |
if [ "$GITHUB_EVENT_NAME" = 'workflow_dispatch' ]; then
env -u TAURI_PRIVATE_KEY \
-u APPLE_SIGNING_IDENTITY \
-u APPLE_CERTIFICATE \
-u APPLE_CERTIFICATE_PASSWORD \
-u APPLE_TEAM_ID \
...
pnpm tauri build --bundles dmg --target ${{ matrix.target }}
else
export APPLE_SIGNING_IDENTITY="${{ secrets.APPLE_SIGNING_IDENTITY }}"
...
pnpm tauri build --target ${{ matrix.target }}
fi
这个改动本身没错——没有证书就是签不了。问题在于我没有意识到「未签名」会顺带废掉辅助功能权限,于是把「划词取词失败」误判成了自己前一天改的前端代码有 bug。
容易掉进来的典型场景:
Fork 上游项目自己打包,但没有上游的签名 secrets
本地
cargo tauri build快速验证,从来不配签名用 CI 打 nightly / 测试包分发给自己或小范围测试
从「官方签名版」切换到「自建版」时,功能突然不好使
四、三种解法,怎么选
个人本地使用,选 A。 下面详细展开;B 放在第七节,需要分发时再看。
顺便说清楚一个常见误解:codesign -s - 这种「普通 ad-hoc 签名」解决不了问题。它和构建时的 linker-signed 一样没有稳定身份,DR 仍然绑 cdhash,重建照样失效。必须是有证书的签名。
五、方案 A 实操:自签名证书(推荐)
5.1 一个关键前提:不需要重新构建
这一点很多人会绕远路。签名是对已构建产物的操作,只依赖 codesign(Xcode Command Line Tools 自带),跟构建工具链完全无关。
我这台机器压根没装 Rust:
$ which cargo rustc
cargo not found
rustc not found
照样能签。所以流程是:
CI 打包(未签名)→ 下载 dmg → 拖进 /Applications → 本地 codesign 重签 → 授权一次 → 长期可用
而不是「改 CI 配置 → 本地装 Rust → 重新构建」。后者对本地自用来说纯属多余。
5.2 步骤 1:创建自签名证书
打开钥匙串访问(Keychain Access)→ 菜单栏「钥匙串访问 → 证书助理 → 创建证书」:

三个字段:
「让我覆盖这些默认值」勾不勾?
不勾也完全能用。唯一值得勾的理由是有效期:默认只签发 365 天,到期后这张证书就没法再用来签新包了;而重新创建证书 = 新的证书指纹 = DR 变了 = 辅助功能权限要重新授权一次。
想省掉一年后这次麻烦,就勾上,在随后的「有效期(天数)」页把 365 改成 3650(10 年),其余页面一路默认「继续」。特别注意别去动「密钥用法扩展」和「扩展的密钥用法」那两页——把「代码签名」用途改掉的话,codesign 会拒绝使用这张证书。
创建过程中会提示「这是自签名根证书、不受信任」之类,继续即可。
5.3 步骤 2:确认证书可用
security find-identity -v -p codesigning
期望输出:
1) 586001838B3C7BFBCE3DEB94E2B8DA05FE8AD593 "pot-local"
1 valid identities found
如果显示 0 valid identities found:去钥匙串访问里双击这张证书 → 展开「信任」→ 把「代码签名」设为「始终信任」→ 关闭窗口(会要求输入密码)→ 重新运行上面的命令。
前面那串十六进制就是证书指纹,也就是会被写进 DR 的东西。记不住没关系,codesign 用证书名字就够了。
5.4 步骤 3:重签 app
⚠️ 先完全退出应用(菜单栏图标 → 退出)。给正在运行的 app 重签会导致签名与内存中的映像不一致,行为不可预期,甚至可能被系统直接杀掉。
# 1. 重签(--deep 会把 bundle 内嵌的可执行文件一起签)
codesign --force --deep --sign pot-local /Applications/pot.app
# 2. 验证签名身份
codesign -dv /Applications/pot.app 2>&1 | grep -E "Identifier|Signature|Authority|flags"
# 3. 验证 DR(这才是 TCC 真正看的东西)
codesign -d -r- /Applications/pot.app
签好之后的正确状态:
Identifier=com.pot-app.desktop ← ✅ 回到真正的 bundle id
CodeDirectory v=20400 ... flags=0x0(none) ← ✅ 不再是 adhoc,linker-signed
Signature size=1755 ← ✅ 有真实签名(不再是 "Signature=adhoc")
Signed Time=Aug 18, 2026 at 22:17:17
Sealed Resources version=2 rules=13 files=3 ← ✅ 资源已封装
designated => identifier "com.pot-app.desktop" and certificate leaf = H"5860018...8AD593"
对照第三节那份「坏的状态」,四个警告信号全部消失。
--deep 的作用在这个 app 上是实打实的——bundle 里除主程序外还内嵌了两个 OCR 可执行文件:
$ find /Applications/pot.app -type f -exec file {} \; | grep -i mach-o
/Applications/pot.app/Contents/MacOS/pot: Mach-O 64-bit executable arm64
/Applications/pot.app/Contents/Resources/resources/ocr-x86_64-apple-darwin: Mach-O 64-bit executable x86_64
/Applications/pot.app/Contents/Resources/resources/ocr-aarch64-apple-darwin: Mach-O 64-bit executable arm64
不加 --deep 的话,这些内嵌二进制保持原样,外层 bundle 的封装校验可能对不上。
补充:Apple 官方文档标注
--deep主要用于验证场景,签名时推荐「由内向外逐个签」。对分发产物应当遵循官方做法;但对本地自用的这种简单 bundle,--force --deep是最省事且实践中可靠的选择。
5.5 步骤 4:重新授权一次
重签后 DR 变了,必须手动清掉旧记录重新授权一次。 这一步不能省,也是最容易漏的一步。
系统设置 → 隐私与安全性:
辅助功能:如果列表里已有
pot,先选中它点-删掉,再点+重新添加/Applications/pot.app,并打开开关。⚠️ 直接把开关关掉再打开是没用的——那条记录本身绑的还是旧签名。必须删掉重加。
自动化:展开
pot,勾上「系统事件」(System Events)。这是给osascript模拟Cmd+C那条回退路径用的。
5.6 步骤 5:验证成功
重新启动应用,看日志第一行:
grep "Accessibility Trusted" ~/Library/Logs/com.pot-app.desktop/pot.log | tail -1
期望:
[INFO][pot] MacOS Accessibility Trusted: true ← ✅ 成了
然后实际划词试一次,日志里应该不再出现 fallback to get_text_by_clipboard——说明 AX 主路径直接成功了。
从此以后:CI 出新包 → 下载安装 → 只跑一遍 codesign --force --deep --sign pot-local /Applications/pot.app → 直接可用,权限不用再动。因为 DR 里的 bundle id 和证书指纹都没变。
六、坑与注意事项
坑 1:千万别加 -o runtime(加固运行时)
网上的签名教程十有八九会让你加上 -o runtime(Hardened Runtime),因为公证(notarization)强制要求它。但如果你只是本地自用,加上反而会坏事。
原因:加固运行时默认禁止发送 Apple Events。而很多取词/自动化类工具在 AX API 失败后会退化成 osascript 模拟按键——这条路正好依赖 Apple Events。加了 -o runtime 又没配对应的 entitlement,这条兜底路径就死了。
如果确实需要开加固运行时(比如为了公证),得配上 entitlements 文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.automation.apple-events</key>
<true/>
</dict>
</plist>
然后 codesign -o runtime --entitlements entitlements.plist -s <identity> ...。
顺带提一个待验证点:pot 的 src-tauri/tauri.conf.json 里 macOS.entitlements 是 null,而官方发布版是经过公证的(必然开了加固运行时)。那么官方版的 osascript 回退路径在加固下是否真的可用,我没有实测过。走公证路线时值得单独验一遍这条路径,别想当然。
坑 2:证书过期 = 权限重新失效
自签证书默认 365 天。过期后:
已经签好的 app 不受影响,继续能跑、权限继续有效(签名验证看的是签名时刻的有效性)。
但不能再用它签新包了,
codesign会报错。重新创建证书 → 新指纹 → 新 DR → 辅助功能权限要再授权一次。
所以要么建证书时就把有效期设成 3650 天,要么接受一年后重新走一遍第 5.5 节。
坑 3:0 valid identities found
三个常见原因,按顺序排查:
# 1. 证书类型选错了(选了 SSL 服务器而不是代码签名)→ 删掉重建
# 2. 证书未被信任 → 钥匙串访问里双击 → 信任 → 代码签名 → 始终信任
# 3. 证书建到了错误的钥匙串(比如"系统"而不是"登录")→ 确认位置
security find-certificate -c pot-local -Z | grep -E "keychain|SHA-1"
坑 4:Gatekeeper 隔离属性
自签证书不被 Gatekeeper 认可,从浏览器/CI 下载的包首次打开仍可能弹「无法验证开发者」。这跟 TCC 权限是两个独立机制,自签解决不了。
# 查看隔离属性
xattr -l /Applications/pot.app
# 移除(如果有 com.apple.quarantine)
xattr -dr com.apple.quarantine /Applications/pot.app
也可以右键 → 打开,手动放行一次。
顺便说明:com.apple.provenance 这个属性和隔离无关,不用处理。
坑 5:给正在运行的 app 重签
会导致签名与已加载映像不一致。签之前一定先完全退出应用——不是关窗口,是真正退出进程(菜单栏图标 → 退出,或 pkill -x pot)。
坑 6:别把自签和 Developer ID 混用
如果哪天你升级到了 Developer ID 证书,DR 会再变一次(不同的证书指纹),辅助功能权限又要重新授权一次。这是预期行为,别以为哪里坏了。
坑 7:更新器(updater)会绕过你的签名
如果应用带自动更新功能,它下载安装的是官方/CI 的原始包,不会带你的自签名。更新完权限就又没了。本地自用建议在设置里关掉自动更新,改成手动下载 + 重签。
一键脚本
把重签 + 去隔离 + 验证打包成一个脚本,每次装新包跑一次就行:
#!/usr/bin/env bash
# sign-local.sh —— 用本地自签名证书重签 app,供个人使用
set -euo pipefail
IDENTITY="${SIGN_IDENTITY:-pot-local}"
APP="${1:-/Applications/pot.app}"
[[ -d "$APP" ]] || { echo "❌ 找不到 app:$APP"; exit 1; }
if ! security find-identity -v -p codesigning | grep -q "$IDENTITY"; then
echo "❌ 找不到可用于代码签名的证书:$IDENTITY"
echo " 请先在「钥匙串访问 → 证书助理 → 创建证书」中创建(自签名根证书 / 代码签名)"
exit 1
fi
APP_NAME="$(basename "$APP" .app)"
if pgrep -x "$APP_NAME" >/dev/null 2>&1; then
echo "⚠️ $APP_NAME 正在运行,先退出它"
osascript -e "quit app \"$APP_NAME\"" 2>/dev/null || pkill -x "$APP_NAME" || true
sleep 2
fi
echo "🔑 使用证书「$IDENTITY」重签:$APP"
codesign --force --deep --sign "$IDENTITY" "$APP"
echo "🧹 移除隔离属性"
xattr -dr com.apple.quarantine "$APP" 2>/dev/null || true
echo "✅ 签名结果:"
codesign -dv "$APP" 2>&1 | grep -E "^(Identifier|Signature|Authority)" || true
codesign -d -r- "$APP" 2>&1 | grep "^designated" || true
cat <<EOF
下一步(仅首次、或换过证书后需要):
系统设置 → 隐私与安全性 → 辅助功能:删除旧的 $APP_NAME 条目,重新添加并打开开关
系统设置 → 隐私与安全性 → 自动化:给 $APP_NAME 勾上「系统事件」
EOF
用法:
chmod +x sign-local.sh
./sign-local.sh # 默认 /Applications/pot.app
./sign-local.sh /Applications/其他.app # 指定路径
SIGN_IDENTITY=my-cert ./sign-local.sh # 指定证书
七、方案 B:Developer ID + 公证(需要分发时)
只有当你要把包发给别人时才需要这条路。$99/年的 Apple Developer Program 是硬门槛。
7.1 准备证书
加入 Apple Developer Program
开发者后台创建 Developer ID Application 证书(注意不是 Development、不是 Mac App Distribution)
导出成
.p12,设一个密码
7.2 配置 CI(以 Tauri 1.x + GitHub Actions 为例)
Tauri 的 bundler 直接读这些环境变量,仓库 Settings → Secrets and variables → Actions 里配好即可:
App 专用密码在 appleid.apple.com → 登录与安全 → App 专用密码 里生成。
workflow 里就是普通的 export:
- name: Build and Package
run: |
export APPLE_SIGNING_IDENTITY="${{ secrets.APPLE_SIGNING_IDENTITY }}"
export APPLE_CERTIFICATE="${{ secrets.APPLE_CERTIFICATE }}"
export APPLE_CERTIFICATE_PASSWORD="${{ secrets.APPLE_CERTIFICATE_PASSWORD }}"
export APPLE_TEAM_ID="${{ secrets.APPLE_TEAM_ID }}"
export APPLE_ID="${{ secrets.APPLE_ID }}"
export APPLE_PASSWORD="${{ secrets.APPLE_PASSWORD }}"
pnpm tauri build --target ${{ matrix.target }}
7.3 公证凭据的两种方式,别踩坑
APPLE_ID+APPLE_PASSWORD+APPLE_TEAM_ID:推荐,配好 secrets 就能用。API Key 方式(
APPLE_API_ISSUER+APPLE_API_KEY):坑在于APPLE_API_KEY只是 Key ID,Tauri 1.x 还需要你把AuthKey_<KEYID>.p8文件放到~/private_keys/或./private_keys/。很多 workflow 只 export 了变量、没有落地这个文件,结果公证静默跳过或直接失败。
如果你 fork 的项目 workflow 里两组变量都 export 了(pot 就是这样),只配 Apple ID 那一组,别管 API Key 那两个。
7.4 验证公证结果
# 检查是否已装订公证票据
xcrun stapler validate /Applications/pot.app
# 完整的 Gatekeeper 评估
spctl -a -vvv -t install /Applications/pot.app
期望看到 source=Notarized Developer ID。
八、排查清单(可直接照着抄)
遇到「需要辅助功能权限的 macOS 应用突然不工作」,按这个顺序走:
# ① 应用自己认为有权限吗?(把 bundle id 换成你的)
grep -i "accessib\|trusted" ~/Library/Logs/<bundle-id>/*.log | tail -5
# ② 签名是不是 ad-hoc?(Signature=adhoc / linker-signed 就是问题所在)
codesign -dv /Applications/<你的>.app
# ③ DR 长什么样?(绑 cdhash 而非证书 = 重建必失效)
codesign -d -r- /Applications/<你的>.app
# ④ 本机有可用的签名证书吗?
security find-identity -v -p codesigning
# ⑤ 代码到底改没改过取词链路?(证伪"代码被改坏")
git log --oneline -5 -- <取词相关文件>
对应的判断:
九、几点总结
1. 权限问题不会报错,只会让返回值变空。 这类 bug 最贵的成本是误判方向。get_text() 失败返回 "" 而不是 Err,让「取词失败」伪装成了「前端渲染问题」。写这类调用时,把失败和「真的没选中文本」区分开、并各自打日志,能省下后来人几个小时。
2. 「窗口弹出来了」不能证明「取词成功了」。 排查时要警惕这种看起来相关、实际上独立的信号。
3. 先用 git 证伪,再读代码。 git log -- <文件> 花 5 秒,能直接排除掉一整个方向。
4. 改 CI 的签名配置,属于会影响运行时行为的改动。 「反正是测试包,签名无所谓」——对需要系统权限的应用来说不成立。如果要在 CI 里跑未签名的测试构建,最好在 README 或 workflow 注释里写一句「未签名包会丢失辅助功能权限,安装后需本地重签」,免得几周后自己都想不起来。
5. 免费的自签名证书能解决 90% 的本地自用问题。 不需要 $99,不需要公证,不需要重新构建。5 分钟配置,一次授权,长期有效。
参考
Apple:
com.apple.security.automation.apple-eventsentitlementman codesign/man spctl/man notarytool
排查环境:macOS 26(Tahoe 26.6.2)/ Apple Silicon / Tauri 1.8.1 / Xcode Command Line Tools。文中的签名输出、日志片段、DR 内容均来自真实排查过程。
评论区