Skip to content

Commit 4a87d09

Browse files
docs(os-dev, pm-dispatch): a dev, a seat and its subagents never print an environment variable's value; presence is tested in a form that prints none (#23049)
Fixes #23040 Clause-②: no A dispatched dev printed the fleet relay's private-key variable into its session transcript by running `env | grep` to read the relay's routing variables. Neither the dev-agent definition nor the PM skill said a word about it. This PR adds the rule to both, in the triage grade's direction: a dev, a seat and every subagent a seat spawns never print an environment variable's value, and test presence only in a form that prints none. No mechanical guard, hook or `settings` deny is added — a new gate defaults to no; if the maintainer wants one, it is a new card. ## What changed | File | Added | Deleted | Lines before → after | Ceiling | |:--|:--|:--|:--|:--| | `.claude/agents/os-dev.md` | 3 lines in basic rule 3's GitHub-write cluster | 3 restatements (homes below) | 402 → 402 | 402 (headroom 0) | | `.claude/skills/pm-dispatch/SKILL.md` | 1 line in 〈全体座位的不变量〉 | 1 restatement (home below) | 319 → 319 | 319 (headroom 0) | Both files sit at ratchet headroom 0, so every added line is paid by a same-file deletion. No re-wrap, no ceiling moved, no cross-file move declared. Every added line is under the ratchet's 120-byte line cap (115 / 102 / 114 / 113 bytes). ### Lines added, verbatim `.claude/agents/os-dev.md`, after the line `归属 = 文本里的 session ID …` in basic rule 3: ```text - 写工具自读所需变量;⛔ 永不打印环境变量的值,转录是 AGENTS.md 禁落令牌的日志行。 - 拼法不限于 `env`、`printenv`、`set`、`export -p`、`echo $VAR`、`cat /proc/*/environ`。 - 判存在只用不印值形式 `[ -n "${X+x}" ] && echo set`;已印出 ⇒ `deviations` 只点名变量名。 ``` `.claude/skills/pm-dispatch/SKILL.md`, after the `写侧恒为 objectstack-fleet[bot]` line in 〈全体座位的不变量〉: ```text - 席位及其子代理 ⛔ 永不打印环境变量的值;判存在只用不印值的形式 `[ -n "${X+x}" ]`。 ``` Why these words: the rule is a flat one — never print any environment variable's value — rather than "only the ones that look like credentials", because the flat form is the one an agent cannot misjudge (the measured instance was a dev reading *routing* variables). The os-dev line anchors on the rule AGENTS.md already states in its write-gate paragraph (never a token in a remote URL, a `git config` value on disk or a log line): a session transcript is a log line, so this is that rule applied, not a second vocabulary. The write tools read the variables they need themselves (`fleet-token.mjs`, `fleet-write/dispatch.mjs` derive the session and the route from the container), so a dev never needs a value. The presence form uses `${X+x}`, which answers set-versus-unset without expanding the value; the card body's `[ -n "$VAR" ]` form conflates unset with empty and expands the value into the test, so it is not the form taught. ### Lines deleted, and where each one lives | File | Deleted line (verbatim) | Its home | |:--|:--|:--| | os-dev.md | `失效同签名:操作看着本地、报成功,唯一症状是 `git status` 里出现他人文件。` | AGENTS.md, Multi-agent working discipline, the stash paragraph (`pop` reports success — the only symptom is another agent's files in your `git status`) and the shared-ref paragraph beneath it; os-dev.md's own line 19 binds AGENTS.md in full. The two neighbours that carry the rule (what a worktree shares; `git reset --soft origin/main` stages others' work) stay. | | os-dev.md | `恢复机制与字节一致性证明规则见 AGENTS.md;从已 commit 的状态重跑,红/绿数字才可信。` | AGENTS.md, Multi-agent working discipline, the reverse-verification paragraph (commit the fix first … then re-run from the committed state so your red/green numbers are trustworthy); in os-dev.md itself the commit-first line directly above it and hard lines ①–④ (restore from `HEAD`, prove by `git diff HEAD`, absolute trap paths, blob-hash comparison) remain. | | os-dev.md | `trap 触发只证明 shell 跑了一个函数,不证明那个函数的效果。` | The line directly above it, which states the trust order (trap is a convenience on the crash path; the byte/hash comparison is the proof); the deleted line was that order's rationale restated. | | SKILL.md | `路径面干净的才转 ready → 入队;队列是唯一被认可的落地路径,⛔ 永不队列外合并。` | Red line 「永不绕过合并队列」 (SKILL.md 红线); the Tier S line directly above it (`S 经达档复核 PASS 后 ready → 入队`) and the landing line in 路径分叉 (`席位落地 = 过落地前检、清标、ready、auto-merge`); `references/landing-operations.md` 入队与落地 (ready/queue only after the diff read, PASS ⇒ ready + auto-merge, the governed-merges pre-check); AGENTS.md Multi-agent discipline §7 (land through the merge queue, never straight to `main`). | ### Line budget | File | before | after | |:--|--:|--:| | `.claude/agents/os-dev.md` | 402 | 402 | | `.claude/skills/pm-dispatch/SKILL.md` | 319 | 319 | ## Premise check — the dispatch's H1–H5, measured on `origin/main` at `38d98d8d8` - **H1 holds.** `grep -c -i -E "secret|env |printenv|private.key|credential" .claude/agents/os-dev.md` → 0 (control `worktree` → 8). `grep -c -E "secret|printenv|private.key|credential|凭据" .claude/skills/pm-dispatch/SKILL.md` → 0 (control `worktree` → 3). The pm-dispatch references carry no env-printing rule either: a grep for `printenv`, `env |`, `echo $`, `环境变量` over `.claude/skills/pm-dispatch/` finds only unrelated readings (`platform-readings.md`, `rest-channel.md`). - **H2 adopted.** The os-dev line anchors on AGENTS.md's write-gate sentence (the transcript is the log line it names); AGENTS.md is untouched (Tier H, outside this card's file surface). - **H3 adopted, with the reason.** The second half lands in SKILL.md 〈全体座位的不变量〉, not in `references/dispatch-runbook.md`: `references/execution-duties.md` states 「无条件条款只住角色文件」 and 「标准非协商条款 ⛔ 不抄进派发词」, so the os-dev constraint lives only in os-dev.md and the dispatch prompt carries no second copy; what the dispatch template cannot reach is the seat itself and the non-os-dev subagents it spawns (a contract-review general-purpose subagent reads neither os-dev.md nor a dispatch prompt), and the one text every seat and every subagent is bound by is the all-seat invariants. That line is the "dispatch template" half of the triage direction. - **H4 partly falsified.** SKILL.md is at 319/319 as the dispatch said — and `.claude/agents/os-dev.md` is at 402/402 too (`CEILINGS` row in `scripts/pm/check-skill-line-ratchet.mjs`), so the dispatch's "≤ +3 lines" for os-dev.md also had to be paid by three same-file deletions, which the dispatch did not budget. The same ratchet caps every line at 120 bytes (`MAX_LINE_BYTES`), which bounded the wording. `check:pm-skill-id-lint` holds: no issue number in either file's operative text. - **H5 adopted; no exception needed.** The one non-credential variable a flow reads (the session id) is read by the write tools themselves: `fleet-write/dispatch.mjs --route` derives it from the container and reports the derived `session_…` id, which is already the attribution text written to GitHub. No current step needs a dev or a seat to print a value. ## Gates — every command `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived (no paths passed; the tool took the change set from git), run on `6aeede187`, exit code captured before any pipe, reconciled with `--ran` `--ran` verdict: `✓ dispatch-gates --ran: 22 derived famil(ies) accounted for — 21 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)` — then the exit-3 family was re-run after its stated prerequisite build; its final line is in the table. | Command | exit | Gate's own verdict line | |:--|--:|:--| | `pnpm check:pm-skill-ratchet` | 0 | `✓ check-skill-line-ratchet: .claude/agents/os-dev.md is 402 lines (ceiling 402; headroom 0).` · `✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/SKILL.md is 319 lines (ceiling 319; headroom 0).` | | `pnpm check:pm-skill-id-lint` | 0 | `✓ check-skill-id-lint: 87 file(s) clean (pattern /#[0-9]{3,}/g).` | | `pnpm check:agent-model-declared` | 0 | `✓ check-agent-model-declared: 1 agent definition(s) under .claude/agents/ all declare a model` | | `pnpm check:skill-frame-sync` | 0 | `✓ check-skill-frame-sync: the one declared copy of the decision frame is internally coherent (.claude/skills/pm-dispatch/SKILL.md …)` | | `pnpm check:nul-bytes` | 0 | `check-nul-bytes: OK (scanned 11047 text file(s) … no raw ASCII control bytes).` | | `pnpm check:pm-governed-prose` | 0 | `✓ check-governed-prose: 2 instruction surface(s) name all 6 registered governed surfaces` | | `pnpm check:pm-governed-merges` | 0 | `live: the real generator declared 9 output(s) and certified this tree` | | `pnpm check:pm-expected-skips` | 0 | `✓ check-expected-skips self-test: 154 cases pass` (+ roster truth on this checkout) | | `pnpm check:commit-card-trailers` | 0 | `✓ check-commit-card-trailers self-test: 81 cases pass.` | | `pnpm check:agent-test-spelling` | 0 | green (sweep over the tree) | | `pnpm check:cross-package-test-inputs` | 0 | `OK: 30 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob` | | `pnpm check:doc-authoring` | 0 | `✓ doc authoring guard: sibling-package prose ids hold the baseline` | | `pnpm check:driver-memory-census` | 0 | `check-driver-memory-census: OK — every declaration is ledgered, every ledger entry is live` | | `pnpm check:gitlink-declared` | 0 | `check-gitlink-declared: OK (11056 index entries -- 0 gitlink(s) at mode 160000 …)` | | `pnpm check:refd-timer-probe` | 0 | `OK check-refd-timer-probe: 8856 source file(s) swept` | | `pnpm check:watch-hint-literal` | 0 | `✓ check-watch-hint-literal: 73 declaration(s) across 4 rostered name(s)` | | `node scripts/check-closing-keyword-parity.mjs` | 0 | `check-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators …)` | | `node scripts/check-closing-keyword-parity.mjs --self-test` | 0 | `✓ check-closing-keyword-parity --self-test: 40 assertions, 5 mutations of the shipped parsers each driven to red.` | | `node scripts/check-comment-mask-corpus.mjs` | 0 | `✓ comment-mask corpus sweep [scripts/js-comment-mask.mjs]: 8862 files, 0 disagree, 0 unparseable` | | `node scripts/pm/check-governed-queue-guard.mjs --self-test` | 0 | `✓ check-governed-queue-guard self-test: 296 cases pass` | | `node scripts/pm/check-harness-current.mjs --self-test` | 0 | `check-harness-current --self-test: all 26 cases passed.` | | `pnpm --filter @objectstack/lint run check:doc-formula-expressions` | 0 (after `pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint` under the verify lock; first run exit 3, PREREQUISITE NOT MET) | `✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 473 files / 1388 TS blocks judged clean by @objectstack/formula.` | Not run locally, by design: no package is touched (the diff is `.claude/**` only), so there is no ① dependency-closure build and no ② package test/typecheck; the repo-wide scans (`pnpm lint` first among them), the 51 artifact-roster families, the 11 wide-population families, the 15 pending-changeset families and the type-check lanes the derivation lists are CI's runs. No changeset: `.claude/**` publishes nothing, so `skip-changeset` is the declaration (applied through `scripts/pm/label-write.mjs`). ## 维护者速读(草稿) **改了什么**:两份 agent 指令文本各加一条规则。`.claude/agents/os-dev.md`(派发的开发 agent 定义)在「GitHub 写」那一簇加三行:写工具自己读所需变量;永不打印任何环境变量的值(列出 `env`、`printenv`、`set`、`export -p`、`echo $VAR`、`cat /proc/*/environ` 这些拼法);判断变量是否设置只用不印值的形式;万一已印出,报告里只点名变量名。`.claude/skills/pm-dispatch/SKILL.md`(席位技能)在「全体座位的不变量」加一行:席位及其子代理永不打印环境变量的值,判存在只用不印值的形式。两份文件都在只降不升的行数棘轮顶上,所以每加一行就同文件删一行在别处有家的复述:os-dev.md 删三行,SKILL.md 删一行,行数前后不变(402 / 319)。不加任何门禁、钩子或 settings 拒绝项。 **为什么改**:一次实测事故——一个派发的开发 agent 为了看中继的路由变量跑了 `env | grep`,把中继私钥的值印进了它的会话转录(GitHub 上的报告与 PR 正文都没带值)。此前这两份文本对「不要打印环境变量」零字,一个要判断变量是否设置的 agent 没有被告知安全写法,自然伸手去拿 `env`。分诊定级给的方向就是:两处各加一条规则,不加机械门禁。 **风险与代价(含回滚)**:纯指令文本,零运行时影响、零发布面。代价是删掉的四行复述——每一行在 AGENTS.md 或同文件相邻行里都有家,正文表格逐行列出了出处。残余风险:这是约定不是机械强制;分诊方向是新门禁默认不加,要加另立卡。回滚 = revert 本 PR,无副作用。 **席位意见**:(留空,席位定稿) **你要做的**:一个动作——决定那把已暴露的中继私钥是否轮换;这不在本 PR 范围内,也不由任何席位决定。本 PR 本身是 Tier S(`.claude/**` 全树),由席位在达档契约复核 PASS 在案后落地,不需要您合并。 ## Acceptance notes - Observed while opening this PR, noted and not filed (no class a/b/c: not a credential, no declared contract): `scripts/pm/fleet-write/dispatch.mjs --route` — and every tool that re-execs itself with `--use-env-proxy` — prints the value of `HTTPS_PROXY` (a loopback proxy URL with a port) in its ℹ️ re-exec notice. Under the flat rule this PR adds, a tool that echoes a variable's value is the next thing a transcript picks up; the value here is not secret. Carrier: none; a candidate for the lane if the maintainer wants tools held to the same rule. Dedupe words: `use-env-proxy`, `HTTPS_PROXY`, `re-exec notice`, `prints value`. - `pnpm --filter @objectstack/lint run check:doc-formula-expressions` exited 3 (PREREQUISITE NOT MET: `@objectstack/formula` and `@objectstack/lint` not built) on the first run; the build it prescribes was run under the shared verify lock and the gate re-run — the table above carries the final reading. Not a finding about the tree. - `check:pm-skill-ratchet` prints a pre-existing ℹ️ about `.claude/skills/spec-property-retirement/SKILL.md` (table-row pin headroom 2, could be lowered to 324). Not this card's file; untouched. - The card body's suggested presence form `[ -n "$VAR" ] && echo set` conflates unset with empty and expands the value into the test; the rule teaches `[ -n "${X+x}" ]` instead. A wording note, not a finding. --- _Generated by [Claude Code](https://claude.ai/code/session_012dGTuJen61ECNHXYXKChoC)_ Co-authored-by: os-dev <noreply@anthropic.com>
1 parent dc08c60 commit 4a87d09

2 files changed

Lines changed: 4 additions & 4 deletions

File tree

‎.claude/agents/os-dev.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ model: opus
5555
- dev ⛔ 不查重、不扫 open issues、不拉板,只附 3–5 个查重词;查询与命中数由立卡席写。
5656
- GitHub 写一律经 `scripts/pm/` 写工具;云容器里经 `fleet-write` 中继落为 `objectstack-fleet[bot]`。
5757
- 归属 = 文本里的 session ID,非 `user.login`;`git push` 不是 REST 写,不走中继。
58+
- 写工具自读所需变量;⛔ 永不打印环境变量的值,转录是 AGENTS.md 禁落令牌的日志行。
59+
- 拼法不限于 `env`、`printenv`、`set`、`export -p`、`echo $VAR`、`cat /proc/*/environ`。
60+
- 判存在只用不印值形式 `[ -n "${X+x}" ] && echo set`;已印出 ⇒ `deviations` 只点名变量名。
5861
- 写预算四笔:`git push`、`pr_create`(draft,含 assignee)、`label-write`(仅标签)、`os-dev-report` 评论。
5962
- 卡与线程只走 payload 档(公开仓单卡网页内嵌 JSON,拼写住 platform-readings)或单卡 REST 读。
6063
- 三类发现进报告交席位代立,不 `POST /issues`、⛔ 不静默弃报;PM 去重读数当既有事实用。
@@ -200,7 +203,6 @@ model: opus
200203
- 那样门禁全绿而已落地的推进被吞掉;先 commit merge 再重生成:`bash scripts/pm/os-regen-merge.sh`。
201204
- `git worktree` 只隔离工作树与 HEAD;`.git/` 下其余一切全 worktree 共享。
202205
- 共享的含 refs(含 `refs/remotes/*`)、stash 栈、config、hooks;配方不点名共享态才 worktree-safe。
203-
- 失效同签名:操作看着本地、报成功,唯一症状是 `git status` 里出现他人文件。
204206
- `git reset --soft origin/main` 会把你分支点之后他人已合并的文件整批 stage 成你的。
205207
- 起点在哪的 reset/diff/log/rebase 一律锚基本规则 1 记录的 `"$BASE"`。
206208
- `log -S/--follow/blame` 判日期或先后前,先 `git rev-parse --is-shallow-repository`,true 就加深申报。
@@ -214,15 +216,13 @@ model: opus
214216
- 反向验证与消融是一次性证明:前后运行引在 PR 正文与报告里;⛔ 不留永久测试文件。
215217
- 两者都先 commit 修复再回退或变异:恢复腿指向 `HEAD`,`HEAD` 必须先装着你的实现。
216218
- 未提交时 `git checkout origin/main -- <path>` 不留恢复点,`git checkout HEAD -- <path>` 删的正是实现。
217-
- 恢复机制与字节一致性证明规则见 AGENTS.md;从已 commit 的状态重跑,红/绿数字才可信。
218219
- 通则:失效形态是 exit 0 且什么都没做的清理步骤,只能靠观察状态验证,永不靠读退出码。
219220
- 恢复腿与变异腿完全对称,四条硬线如下。
220221
- ① 恢复写 `git checkout HEAD -- <path>`,⛔ 永不裸 `git checkout -- <path>`:裸恢复从索引取。
221222
- 取回的正是变异本身,exit 0、无异常输出;点名 `HEAD` 绕开被污染的索引。
222223
- ② 恢复由 `git diff HEAD` 为空来证明(`git status` 干净是同一句话的另一半),不由退出码证明。
223224
- ③ trap 里的路径一律绝对:变异前先 `REPO_ROOT="$(git rev-parse --show-toplevel)"`。
224225
- 恢复针对 `"$REPO_ROOT/<path>"`;信任顺序:trap 只是崩溃路径上的便利,字节/哈希比对才是证明。
225-
- trap 触发只证明 shell 跑了一个函数,不证明那个函数的效果。
226226
- ④ 空哈希读作 FAILURE,不是没得比:`git hash-object` 对解析不到的路径输出的是空。
227227
- 比对对象是该路径的 HEAD blob 哈希:空或缺失 ⇒ 失败;不匹配 ⇒ 响亮地非零退出并停下。
228228
- 拒收类用例断言信封,不断言 throw 本身;最小断言集是错误的 `code` 与 `status`(ADR-0112 信封)。

‎.claude/skills/pm-dispatch/SKILL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
9191
- 用户账号仅三用:assignee、授权批准、维护者亲手;批准账号永不跑席位或作其关联用户。
9292
- 内容写只走 REST 代理,⛔ 无 MCP 写,唯一例外 `rerun_failed_jobs`(限 CI 红单次重跑,先核 head)。
9393
- 写侧恒为 `objectstack-fleet[bot]`;`user.login` 记令牌不记席位,归属 = 文本里的 session ID。
94+
- 席位及其子代理 ⛔ 永不打印环境变量的值;判存在只用不印值的形式 `[ -n "${X+x}" ]`。
9495
- GitHub 上一切新内容用英文;中文只留四通道(维护者速读、轮报、裁决引文、四维分析)。
9596
- 裁决引文照抄不译;四维中文只管新记录,存量英文块 ⛔ 不迁移;存量中文 ⛔ 不追溯改写。
9697
- 一 PM 恰管一车道,一车道由一组座位管,一席一贴;PM Y 管什么恰一个答案。
@@ -201,7 +202,6 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
201202
- 已入队 ⇒ 转 draft 并 disable,即请维护者手动出队(席位无实测手段);出队以阳性探针答。
202203
- skills 车道自有 PR:纯代码面如 `scripts/pm/` 由本席按达档自审(清单不减)后落地。
203204
- 受管面两层:Tier S = `.claude/**` 全树,余皆 Tier H 等人批;S 经达档复核 PASS 后 ready → 入队。
204-
- 路径面干净的才转 ready → 入队;队列是唯一被认可的落地路径,⛔ 永不队列外合并。
205205
- 入队资格:每 check 绿或预期 skip,⛔ 非必查子集;名单 check-expected-skips.mjs 只判 objectstack。
206206
- 非必查红是真缺陷或坏门,归 PM 入队前处置;第三种按设计而红,三条全立才可带红入队:
207207
- 源码自述 pushed 分支上按设计而红、不跑 `merge_group`、PR 评论记明门与因,缺一即否。

0 commit comments

Comments
 (0)