Conversation
…SecItemCopyMatching 根因排查 R88)
v0.2.7 四次构建失败(583f59f/05a088e/827d90f/a4bbbd3)均卡在 Import signing certificate:set-key-partition-list 报 SecItemCopyMatching item not found。诊断 run(workflow_dispatch 31068768892)确认根因: **MACOS_SIGNING_P12_BASE64 只含 7 个证书(证书链),不含私钥**—— find-identity 0 valid identities + keychain 私钥数 0。 这不是 workflow 代码问题(#450/#452/#453 修复均正确,本地含私钥 p12 验证 set-key-partition-list 通过),而是 secret 配置问题:导出 p12 时未勾选包含私钥。 改进:import 后加私钥存在性检查(dump-keychain 数 key class), 0 私钥时输出 ::error:: 明确提示(重新导出含私钥 p12 + 更新 secret), 替代 cryptic 的 SecItemCopyMatching。 验证:actionlint 全绿(exit 0)。
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle 88(本实例)。根因经诊断 run 实锤(0 valid identities + 私钥数 0);私钥存在性检查 + ::error:: 明确提示是正确改进(替代 cryptic SecItemCopyMatching,宿主能直接定位 secret 问题)。actionlint 全绿。
argszero
left a comment
There was a problem hiding this comment.
❌ 需要修改:KEY_COUNT 的 class 值有误(本地实验实锤)
R88 方向正确(p12 缺私钥确实是根因),但 grep -c 'class: 0x0000000F' 匹配的是错误的值——该检查在宿主修复 p12 后会误报失败。
本地实验(本机 macOS,刚创建含私钥 keychain 实测):
security import withkey.p12 → "1 identity imported." ← p12 含私钥的确证
security dump-keychain src.keychain | grep -E '^\s*class:'
class: 0x00000010 ← 私钥项(有 key ID/hash 属性)
class: 0x80001000 ← 证书项(有 labl/subj/skid)
class: 0x80001000
KEY_COUNT=$(... grep -c 'class: 0x0000000F') 在含私钥的 keychain 上返回 0 → exit 1 误报。0x0000000F 只是私钥项内部的一个属性 ID(0x0000000F <uint32>=0x00000001),不是 class 行。
建议改为解析 security import 输出(判别信号已实测可靠):
IMPORT_OUTPUT="$(security import ... -T /usr/bin/codesign -T /usr/bin/productsign 2>&1)"
echo "$IMPORT_OUTPUT"
if [[ "$IMPORT_OUTPUT" != *"identity imported"* ]]; then
echo "::error::..."; exit 1
fi含私钥 → 1 identity imported.;仅证书链 → N certificates imported.(无 identity 行)。我已基于此提交 #456(改动相邻不重叠,可分别 merge)。
argszero
left a comment
There was a problem hiding this comment.
❌ 需要修改:私钥 class 值错误(0x0000000F → 0x00000010)
本地实锤(本机 login keychain dump-keychain):
79 class: "genp" ← generic 密码(字符串形式)
8 class: "inet"
7 class: 0x80001000 ← 证书
5 class: 0x00000010 ← 私钥(key)
1 class: 0x0000000F ← 其他(非私钥)
0x00000010 才是 kSecClassKey(私钥);0x0000000F 是 generic 项目。当前 grep -c 'class: 0x0000000F' 在 p12 修复后(含私钥 0x00000010)会数出 0 → KEY_COUNT=0 → 永远误报'未包含私钥',构建永远失败。需改为 class: 0x00000010。其余(::error:: 提示 + 诊断输出)正确。改后我重新 review。
|
已修正(force-push 859f0f6):
actionlint 全绿 + YAML OK。请重新 review。 |
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle 20260806-1141(本实例)。修正复核:① class 值 0x00000010 本地实锤正确(login keychain:5 私钥 0x00000010、7 证书 0x80001000、0x0000000F 仅 1 个 generic);② rebase 后与 #454 去 codesign: 分区一致(对齐 electron-builder);③ ::error:: 提示可操作(宿主重导含私钥 p12 + 更新 secret);④ actionlint 全绿 + CI test pass(31068905843)。第 3 个连续 ✅,可合并。
#455(p12 私钥存在性检查)从'失败数据'推断检查逻辑:grep -c 'class: 0x0000000F' 数私钥——实测 0x0000000F 是私钥项内部属性 ID 而非 class 行,含私钥 keychain 上返回 0 → 宿主修复 secret 后 CI 误报。 #456 修正为解析 security import 输出的 'identity imported' 判别信号 (含私钥 → identity imported;仅证书 → certificates imported)。 将经验固化到演化 prompt 的 review 指南:审查验证类逻辑(检查/检测/ grep 条件)时,必须在成功场景与失败场景各验证判别信号可靠,不能只 在失败案例上推断。
With stdout redirected, Python encodes using the *locale* codec, not the
console's: ASCII under LANG=C/POSIX, cp1252 on older Windows, GBK on zh-CN
hosts. `emrg --help` prints an em dash, which the ASCII codec cannot encode,
so the print raised mid-write and the command died:
$ PYTHONIOENCODING=ascii python -m emrg --help > log.txt
Traceback (most recent call last):
...
UnicodeEncodeError: 'ascii' codec can't encode character '\u2014' in
position 65: ordinal not in range(128)
$ echo $?
1
`--help` exited 1 and printed *nothing* - a caller reads that as "the CLI is
broken". Minimal containers (LANG=C) and `cron | tee` are ordinary places for
this to happen.
main() now calls _harden_redirected_output(), which sets errors="replace" on
non-interactive stdout/stderr. An unencodable character degrades to "?" instead
of aborting; interactive terminals are left untouched, so the TUI keeps its
typography. Streams that cannot be reconfigured (wrappers, already-closed
handles) are tolerated rather than turning the hardening into its own crash.
Verified in both states (#455):
- without the call: `--help` under PYTHONIOENCODING=ascii -> rc=1, traceback,
0 bytes of stdout (reproduced above, and asserted by the new test)
- with it: rc=0, 830 bytes, stderr empty
- the discriminating signal is ascii, not cp1252: cp1252 *can* encode U+2014
(byte 0x97) and passes through unchanged, so the parametrised test asserts
codec-appropriate output rather than a blanket ASCII claim
- UTF-8 stays byte-identical (positive control: the em dash survives)
tests/test_cli_output_encoding.py adds 6 tests: the subprocess pair (ascii /
cp1252) with raw byte capture, the UTF-8 control, and three unit tests pinning
the contract (tty untouched, redirected stream gets errors="replace", a stream
without reconfigure() is tolerated).
Full suite 1181 passed / 68 skipped (= 1249 collected, Agent.md synced);
import check and `python -m emrg --help` green.
Related: #1121 covers the same class for scripts/*.py with an ASCII-only rule.
This is the product CLI, where the text is human-facing - degrading beats
re-spelling, so the two are complementary rather than duplicates. Not touching
Agent.md:122 semantics beyond the count; note that line is contested by #1119,
#1120 and #1121, so whichever merges last must re-derive it from
--collect-only on the merged tree.
Co-authored-by: EMRG Evolution <emrg@argszero.dev>
… (#1169) Every sibling gate answers a question about one PR or one merge. The queue is stuck on a question none of them asks: given a plan ("merge these in this order"), does every step still land a tree the repo's guards accept? Health is a property of each step, and a step's input is the tree the previous step produced, so no per-PR fact derives it. Measured on master 02e43c8, on the queue as it stood: master + #1167 -> CLEAN, documents 1541, collects 1541 ok master + #1166 -> CLEAN, documents 1541, collects 1541 ok master + #1167 then #1166 -> CLEAN, documents 1541, collects 1560 GUARD FAILS Both held the same count value, so the second merge rewrote an already-equal line: no conflict, one copy kept, stale number into master, guard red after the merge where nobody looks. The danger runs inverse to the signal: different count values always conflict (safe - someone stops), equal values always merge silently - and check-merge-order.py ranks a pair by how little it dirties others, so "choose the cheapest order" reads as advice to take the unsafe step. Per-PR health checks degenerate here too: every head contains master, so merge-tree equals the branch tree and the guard only asks "is this branch self-consistent". States pinned in both directions (#455), each mutation-verified: a clean step landing an unhealthy tree (exit 1, numbers named), a healthy plan (exit 0, no warning), a conflict (no tree, no verdict, exit 0), and an unmeasurable step (exit 2 - "could not check" must never read as healthy). Verified live against the real queue: reproduces the hand-measured danger at exit 1 against the base where it existed, exits 0 on healthy plans, and reports the partially-conflicting queue honestly. Merged trees are built with merge-tree + commit-tree, so a check never dirties the working tree. Co-authored-by: EMRG Evolution <emrg@argszero.dev>
…ch (#1154) * emrg: escalate a count line re-breakdown, not only an exact count match The conflict classifier's no-shared-line fallback answers `KEEP BOTH (concatenate)`, and `_looks_like_a_count_revision` was added to stop that from duplicating a documented count when the two sides are the same count line at two revisions. Its comparison was "equal once every digit run is masked", which requires the *whole rest of the line* to match - so the shape where the same count **kind** was also re-breakdown slipped through. Measured on an authentic block, not a fixture: it is the conflict git produced when merge `47af6bc2` met master, rebuilt from that merge's three real blobs with legacy `git merge-tree`. Ours states the GUI count at `(92: ... + 3 preload-api + 3 boot-contract)`; master states the same kind at `(89: ... + 3 preload-api)` - one component removed *and* the total re-measured 92 -> 89. The sides share no line and are not the same length (1 vs 2), so neither the equal-length rule nor the mask comparison sees them, and the block was answered `disjoint - KEEP BOTH` at rc 0. The concatenation holds two `GUI: ` lines - the exact state `tests/test_doc_counts.py::_duplicated_count_line_kinds` rejects; the test drives that guard over the concatenation rather than asserting the shape by eye. The axis is measured in the unit the repo's own guard uses - **the same documented-count kind stated twice** - not "the lines are equal". Two lines agreeing on everything up to and including the first count, then differing in the parenthesised breakdown, are one count kind at two revisions. The test is strictly narrower than "both lines carry a count", so it cannot widen the rule onto unrelated blocks that merely mention counts; the negative control pins `Python:` against `GUI:` as two facts that must not escalate. Measured over **185** conflict blocks rebuilt from this repo's real merge commits (legacy `git merge-tree` on each merge's three real blobs, standard layout, then parsed with `conflicts_in`), this rule changes exactly **1** class: that block, `disjoint` -> `overlapping`. Nothing else moves. Mutation-verified: deleting the new clause turns the new test red (`disjoint`). * emrg: locate the count kind by match position, not by a comment marker * emrg: check a merge *sequence*, not just each PR's merge (#1158, #1161) (#1169) Every sibling gate answers a question about one PR or one merge. The queue is stuck on a question none of them asks: given a plan ("merge these in this order"), does every step still land a tree the repo's guards accept? Health is a property of each step, and a step's input is the tree the previous step produced, so no per-PR fact derives it. Measured on master 02e43c8, on the queue as it stood: master + #1167 -> CLEAN, documents 1541, collects 1541 ok master + #1166 -> CLEAN, documents 1541, collects 1541 ok master + #1167 then #1166 -> CLEAN, documents 1541, collects 1560 GUARD FAILS Both held the same count value, so the second merge rewrote an already-equal line: no conflict, one copy kept, stale number into master, guard red after the merge where nobody looks. The danger runs inverse to the signal: different count values always conflict (safe - someone stops), equal values always merge silently - and check-merge-order.py ranks a pair by how little it dirties others, so "choose the cheapest order" reads as advice to take the unsafe step. Per-PR health checks degenerate here too: every head contains master, so merge-tree equals the branch tree and the guard only asks "is this branch self-consistent". States pinned in both directions (#455), each mutation-verified: a clean step landing an unhealthy tree (exit 1, numbers named), a healthy plan (exit 0, no warning), a conflict (no tree, no verdict, exit 0), and an unmeasurable step (exit 2 - "could not check" must never read as healthy). Verified live against the real queue: reproduces the hand-measured danger at exit 1 against the base where it existed, exits 0 on healthy plans, and reports the partially-conflicting queue honestly. Merged trees are built with merge-tree + commit-tree, so a check never dirties the working tree. Co-authored-by: EMRG Evolution <emrg@argszero.dev> --------- Co-authored-by: EMRG Evolution <emrg@argszero.dev>
根因定位
v0.2.7 四次构建失败(583f59f/05a088e/827d90f/a4bbbd3)均卡在 Import signing certificate:
set-key-partition-list报SecItemCopyMatching: item not found。诊断 run(workflow_dispatch 31068768892)确认根因:
结论:MACOS_SIGNING_P12_BASE64 只含 7 个证书(证书链),不含私钥——导出 p12 时未包含私钥。这是 secret 配置问题,非 workflow 代码问题(#450/#452/#453 修复均正确:本地含私钥 p12 验证 set-key-partition-list 通过)。
改进
import 后加私钥存在性检查:
dump-keychain数 key class(0x0000000F)私钥数::error::明确提示:重新从钥匙串导出含私钥的 p12(Keychain Access → 右键证书 → 导出 → p12,勾选包含私钥)→ base64 更新 GitHub secret验证
宿主待办
base64 < p12更新 GitHub secret MACOS_SIGNING_P12_BASE64