Repository navigation
docs(agents): wait only on a PID you recorded — the WAIT side of the shared process table - #17809
Conversation
The shared-process-table rule is stated for SIGNALS only, and the guard's ALLOWED list ends by calling `pgrep -f` a harmless READ. That is correct about killing and is exactly where the trap sits: a name-matched WAIT has the same root cause (one process table, text matching, no ownership check) and the same property that makes the kill rule a rule rather than advice — it reports success. A `pgrep -f "$PAT"` run from a shell whose own command line contains "$PAT" matches the asker itself, so the wait never returns. AGENTS.md's kill rule gains the wait clause in its own positive form. The line ratchet is at 1075/1075 and the flat 120-byte line budget leaves that line 7 bytes of headroom, so the clause is paid by deleting "Don't leave orphan servers behind." — a restatement of the bolded "shut it down yourself when the task is done" that precedes it. No ceiling raise; the file stays at 1075. The spelling detail the ceiling could not hold (`cmd & pid=$!`, a pidfile, a wrapper that prints its own PID) lands in the guard header, which carries no ceiling — header comment only, no guard logic and no new refusal: a wait that never returns spends only the asker's own budget. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK
维护者速读(终稿)改了什么 — 两处受管文字。(1) 为什么改 — #17104 实测:一个 dev 用 风险与代价(含回滚) — 纯文字;18 条派生门禁全绿、棘轮 1075/1075 绿、受管判定 GOVERNED 是预期;该钩子不在 objectui 的 pin 清单里,下游无联动。回滚 = revert 本 PR。 席位意见 — 席内按契约复审档复核通过,建议合并。dev 纠正了本席派发词里两处关于棘轮/行宽的错读,已记录。 你要做的 — 受管面:请人工合并 PR #17809(本席不翻 ready、不入队、不挂 auto-merge)。 Generated by Claude Code |
… own subject or the zero is void (objectstack-ai#17837) The zero-hit rule bound the control to HIT, not to be SCOPED to the claim: a control can prove the instrument works while pointed at a different corpus, path shape, module/package boundary or quoting, and the zero then comes back as a reassuring negative that survives review (three misses in one hotcrm shift, two more measured by the triage seat). The rule now says the control shares the claim's own subject and the target's failure mode (a phrase that wraps across lines needs a control that wraps the same way), or the zero is VOID rather than negative. Landed in all three carriers of the discipline in one PR, per SKILL.md 「一条规则在本文与核心条款一处改动,另一处同 PR 同改」: `pm-dispatch/SKILL.md` :161-:162, `references/core-rules.md` :45, and the dev-side reading in `.claude/agents/os-dev.md` :54. Every file stays at its ratchet ceiling; the payment is density inside the same section, itemised below. Not a new gate. Part of objectstack-ai#17569 ## Acceptance notes **Premises (re-taken on `origin/main` `0cd841a16` at 2026-09-12T10:03Z, after `git fetch origin main`):** 1. SKILL.md :161 read 「- 零命中必须用确定存在的邻近词反查,否则零命中不成立。」 (78 bytes); file 812 lines, CEILINGS row 812, `MAX_LINE_BYTES = 120` — holds. The ratchet script lives at `scripts/pm/check-skill-line-ratchet.mjs` (the dispatch word spelled it without the `pm/` segment; same script). 2. core-rules.md :45 read 「- 零命中必须用确定存在的邻近词反查才成立;仓不可达时 ⛔ 不得当成查过且干净。」 (110 bytes); 151 lines at ceiling 151 — holds; it IS the rule's core-rules mirror and is edited here. 3. os-dev.md :54 read 「 - 空结果要同会话一个已知必中的控制词答了命中才算读数。」 (83 bytes); 403 lines at ceiling 403 — holds. 4. SKILL.md :175 and core-rules.md :51 are the dedupe-control siblings — left byte-identical (the new clause is general; no cross-reference needed). 5. Open-PR scan re-taken at 2026-09-12T10:07Z over all 24 open PRs' file lists (REST `pulls/{n}/files`): zero hits on the three files. Control, same corpus and same regex family: 5 of those PRs touch `.claude/` paths (objectstack-ai#17828 and objectstack-ai#17803 on `platform-readings.md`, objectstack-ai#17823 `rest-channel.md`, objectstack-ai#17809 a hook, objectstack-ai#17515 `decision-analysis.md`) — the zero is scoped. `origin/main` moved 3 commits (to `51b024a16`) during the run; none touched the three files. 6. Sibling clause from PR objectstack-ai#17566 (spec-property-retirement SKILL.md :59) read: 「零编写实例普查要并跑一个同族已知存活的键作对照,两读数都报;同得零即没测出。」 — the new wording keeps that vocabulary (a control shares the claim's family/subject; a control that measures nothing voids the reading) rather than introducing a second idiom. 7. The card quotes a dispatch-template sentence 「pair every zero with a control word that must hit」 as a second carrier inside SKILL.md. On `origin/main` no such sentence exists (grep for `must hit`, `pair every zero`, `control word` over `.claude/**` and `scripts/pm/**` returns zero; control: `零命中` hits :161, :542, :724) — the template left in the rules-only rewrite. No second carrier to edit. **Edited lines and byte counts (each ≤ 120):** | file | line | bytes | text | |:--|:--|--:|:--| | SKILL.md | :161 | 113 | 「- 零命中须用确定存在的邻近词反查;控制词须与主张同主体,否则该零作废,不是阴性。」 | | SKILL.md | :162 (new) | 114 | 「- 同主体 = 同语料、同路径形、同包界、同引法、同失效形态:跨行短语配跨行控制词。」 | | SKILL.md | :165 (merged) | 105 | 「- 时间戳形如 `YYYY-MM-DDThh:mmZ`,树读数另带 ref 或 tip;无时间戳的读数按未取处理。」 | | core-rules.md | :44 | 112 | 「- 核验 main 用 fetch 后的 `origin/main`,⛔ 不用共享检出树;仓不可达 ⛔ 不当查过且干净。」 | | core-rules.md | :45 | 113 | 「- 零命中须用必中词反查,且与主张同语料/路径形/包界/引法/失效形态,否则该零作废。」 | | os-dev.md | :54 | 113 | 「 - 空结果要同会话已知必中、与主张同主体同失效形态的控制词答了命中才算读数。」 | **How density was paid (all inside the 平台读数纪律 section of each file):** - SKILL.md 812/812: the clause takes two lines (:161-:162, +1); the two timestamp lines (:164 「时间戳形如 …,树读数另带 ref 或 tip。」 and :165 「无时间戳的读数是格式错误不是现值,读者按未取处理。」) merged into one (−1). Dropped words: 「是格式错误不是现值,读者」 — the operative half 「按未取处理」 survives, matching core-rules' own wording of that rule. - core-rules.md 151/151: no two neighbouring lines in the section merge under 120 bytes, so the clause is one line (:45) with the five sames inline; the 仓不可达 half of the old :45 moved onto the `origin/main` line (:44). Dropped words: 「确定存在的邻近」 → 「必中」, 「时」, 「不得当成」 → 「不当」, 「的工作树」 → 「树」, 「先」. The wrapped-phrase example is carried by SKILL.md only (terser register). - os-dev.md 403/403: one line, byte-for-byte replacement of :54 (83 → 113 bytes); the 范围 list is otherwise untouched. **Frame block pin:** `sed -n '734,755p' .claude/skills/pm-dispatch/SKILL.md | md5sum` = `3327d02c56f8a0eca88569dad2270f32` before and after (net line count above it is 0, so the block did not move). **Gates (derived with `node scripts/pm/dispatch-gates.mjs --commands` in the worktree, 18 commands; reconciliation via `--ran`: 「18 derived, 18 run, 0 NOT-MEASURED, 0 UNRUN」):** all 18 exit 0 at head `9e15e315f`. `pnpm check:pm-skill-ratchet` verdict lines: 「SKILL.md is 812 lines (ceiling 812; headroom 0)」 · 「core-rules.md is 151 lines (ceiling 151; headroom 0)」 · 「os-dev.md is 403 lines (ceiling 403; headroom 0)」. `pnpm check:skill-frame-sync`: 「the one declared copy of the decision frame is internally coherent … 4 axes」. `pnpm check:nul-bytes`: 「OK (scanned 8465 text file(s) …; no raw ASCII control bytes)」. `check:doc-formula-expressions` first answered exit 3 PREREQUISITE NOT MET (`@objectstack/formula` and `@objectstack/lint` unbuilt) — not a measurement; built both under the verify lock (VERDICT command-exit 0, 169 s) and re-ran: exit 0. **Changeset:** `.claude/**` publishes nothing from any released package (`node scripts/check-changeset-fixed.mjs` exit 0; no `files[]` of any package covers `.claude/`) — `skip-changeset` applied by additive POST and read back. **Deferred rider (⛔ not in this PR):** the card's two `references/platform-readings.md` rows (`git grep -- 'a/**/*.ext'` skips files directly under `a/`; `grep -c $'\x00'` is not a NUL probe) wait on that file's serial behind PR objectstack-ai#17828 and PR objectstack-ai#17803. objectstack-ai#17569 stays open for the rider PR; hence `Part of`, not a closing keyword. **Governed surface:** `.claude/**` — this PR stays a draft at the human terminal; nothing here flips it ready or arms auto-merge. ## 维护者速读(草稿) **改了什么:** 「零命中必须配控制词反查」这条规则,在三处载体(pm-dispatch SKILL.md、核心条款 core-rules.md、os-dev 开发 agent 定义)各加一句:控制词必须与主张同主体 —— 同语料、同路径形、同包界、同引法、同失效形态 —— 否则这个零作废,不算阴性读数。三个文件行数不变,各自仍顶着 ratchet 上限。 **为什么改:** 一个班次里三次「控制词命中了、零仍是假的」:控制词证明工具能用,却没证明工具对准了主张。旧文只要求控制词「命中」,不要求它「与主张同一件事」,于是错误读数反而带着安心感回来、过了复核。 **风险与代价(含回滚):** 纯文本规则,不加门禁、不加脚本;付费方式是同节内合并两行时间戳规则(SKILL.md)与把「仓不可达」半句挪到 origin/main 那一行(core-rules)。回滚 = revert 这一个 commit,无生成物、无依赖。 **席位意见:** (留空,席位定稿成评论) **你要做的:** 读三处新句是否表达了你要的判据;确认后人工合并(governed surface)。rider 半张(platform-readings 两行)另开 PR,本 PR 不关卡。 --- _Generated by [Claude Code](https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK)_ Co-authored-by: Claude <noreply@anthropic.com>
Fixes #17104
The shared-process-table rule is written for SIGNALS only.
guard-process-kill.sh's ALLOWEDlist ends by calling
pgrep -fa harmless READ — correct about killing, and exactly where thecard's measured trap sits. A name-matched WAIT has the same root cause (one process table, text
matching, no ownership check) and the same property that makes the kill rule a rule rather than
advice: it reports success. A
pgrep -f "$PAT"run from a shell whose own command linecontains
$PATmatches the asker itself,head -1takes it, and the wait never returns.Two prose edits, no guard logic and no new refusal — per the grading, a wait that never returns
spends only the asker's own budget, so this half is a written rule, not a hook.
Edit 1 —
AGENTS.md, Prime Directive #8 (the kill rule's line)Before (lines 533–534, 89 + 113 = 202 bytes):
After (lines 533–534, 113 + 116 = 229 bytes):
File line count: 1075 before, 1075 after. No ceiling raise.
How the line was paid — and why the dispatch's first route is not available
The dispatch offered three routes and noted the ratchet "pins a widest-table-row byte count, not
prose width". That is true of
MAX_TABLE_ROW_BYTES(AGENTS.md 768, headroom 0, untouched here),but it is not the whole ratchet:
check-skill-line-ratchet.mjsalso carries a flatMAX_LINE_BYTES = 120prose rule, and line 534 measured 113 bytes — 7 bytes of headroomagainst a clause that needs ~80. Route (a), riding line 534, is arithmetically impossible.
Route (b) — folding into the paragraph's existing lines — is re-wrapping slack into new content.
os-dev.mdforbids that spelling explicitly (「付行数棘轮的唯一合法货币是删内容:⛔ 不拿 re-wrap(折行合并)当筹行,新增以删减付账」), and the ratchet's own header names widening-to-budget as the
defect it was built against. Flagging rather than silently choosing: the dispatch offers a route
os-dev.mdcloses, andos-dev.mdwins.So route (c), paid by deletion in the same edit:
Don't leave orphan servers behind.(34bytes including its leading space) is deleted. It loses nothing — it is a negative restatement of
the bolded
shut it down yourself when the task is donethat immediately precedes it in thesame sentence pair; the rule's positive form, its prescribed teardown command and its scope all
survive untouched. No other content was removed.
Both new lines are within the 120-byte budget (113, 116). The wrap falls inside a bold span,
which is this file's own established convention — lines 532→533 already wrap inside
shut it down yourself when the task is done.Edit 2 —
.claude/hooks/guard-process-kill.sh(header comment only)Line 60's sentence keeps its meaning about signals and gains the caveat (85 → 91 bytes, plus
an 88-byte continuation line):
A new header section then carries the positive form and the spelling detail the AGENTS.md ceiling
could not hold (
cmd & pid=$!thenwait "$pid", a pidfile, or a wrapper that prints its ownPID), the measured five-hour self-deadlock, and the reason this half is deliberately unguarded.
.claude/hooks/carries no ceiling (the ratchet header states this explicitly), so the detailbelongs here — which is also the file's existing division of labour with the kill rule.
File: 333 → 361 lines. New comment lines are ≤94 bytes, matching the file's existing widest
comment line exactly.
bash -nclean; hook still wired in.claude/settings.jsonand still-rwxr-xr-x.Verification
Final commit
35a20c6.Governed verdict —
node scripts/pm/check-governed-merges.mjs --test AGENTS.md .claude/hooks/guard-process-kill.sh, exit 3:Guard self-test —
.claude/hooks/guard-process-kill.selftest.sh: 69 passed, 0 failed bothbefore and after the edit (exit 0). It pins the refusal text, not the header comment, so a
header-only change leaves it green and no pin needed updating.
Ratchet —
pnpm check:pm-skill-ratchet, exit 0:Derived gate set —
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 18 families from the 2-path change set; all 18 run, all exit 0. Reconciled with
--ran:Two gates first returned exit 3 = PREREQUISITE NOT MET (not a finding):
node_moduleswasabsent until
pnpm install, andcheck:doc-formula-expressionsadditionally needed@objectstack/formulaand@objectstack/lintbuilt. Both re-ran at exit 0 after the builds(
os-verify-lock.sh,VERDICT command-exit 0).Control characters —
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'over both files: nomatches.
pnpm check:nul-bytesexit 0.objectui pin check — read from
objectuiorigin/main:scripts/upstream-port-pin.json.It pins 11 upstream paths (the
guard-main-checkout*,guard-shared-stash*andguard-tree-enum*families pluscheck-half-states.mjs,invoked-as.mjs,check-bash32-floor.mjs).guard-process-killappears zero times. No downstream pinmoves, and the objectui parity check has nothing to flag.
changeset:
skip-changeset—AGENTS.md(repo-root instruction file) and.claude/**areboth on the fast track; nothing in any package's
files[]moves.维护者速读(草稿)
改了什么 —— 两处纯文字改动。
AGENTS.md第 534 行的「只杀你记录过的 PID,绝不按名字杀」规则,补上同样正向形式的另一半:等待也只等你记录过的 PID;
guard-process-kill.sh头部注释里那句「
pgrep -f只是读,随便看」保留它关于「信号」的原意,并补上关于「等待」的告诫。⛔ 没有改任何守卫逻辑,没有新增拦截。
为什么改 —— 卡片记录的是一次实测事故:三个后台等待进程死锁了五个小时。原因是
pgrep -f "$PAT"在一个自身命令行就包含
$PAT的 shell 里执行,匹配到了提问者自己,于是tail --pid在等自己的父进程、父进程在等它。这个错误会报成功 ——
pgrep返回 0,还打印出一个看起来很合理的 PID,所以没有任何信号提示出错。杀错进程毁的是别人的活、动静很大;等错 PID 静悄悄地烧掉的是你自己的时间,所以
它能跑满五个小时都没人发现。
风险与代价(含回滚) —— 风险极低:改的是说明文字,不是可执行逻辑。
bash -n通过,守卫自检69 项全绿(改动前后一致),钩子仍然挂载且可执行。代价是
AGENTS.md行数棘轮在 1075/1075 零余量,为了不抬上限,删掉了「Don't leave orphan servers behind.」这句话——它只是前面加粗那句「任务做完自己
关掉」的反面重复,规则内容没有任何损失。回滚就是
git revert这一个 commit,无迁移、无依赖。席位意见 —— (留空,待 skills 席位定稿)
你要做的 —— 这是受管面(
AGENTS.md+.claude/**),按 Prime Directive #14 由维护者手动合并;⛔ 不翻 ready、不进合并队列、不开 auto-merge。请确认一点:用「删掉那句重复话」来换这一行,是否认可
——若您认为那句话应当保留,替代方案是抬
AGENTS.md的行数上限(需要您的裁决),我没有自行抬。Acceptance notes
Out of scope, noted and not filed (neither is a reproducible defect, a declared-contract
violation, nor a metadata trap — the three filing classes):
is incomplete: a flat
MAX_LINE_BYTES = 120prose rule also applies, and it is what caps line534. Reported to the PM rather than filed — it is a fact about this dispatch's premise, already
corrected above. Carrier: the skills seat, on this PR.
measurement: Prime Directive Convert to monorepo with scoped packages #8 wraps at 84–90 bytes, and only the ⛔ line ran to 113. Same
carrier, same PR; no separate card.
Generated by Claude Code