Repository navigation
Commit 9c79d92
feat(hooks): SessionStart hook prints the pm-dispatch pointer, its SKILL.md touch sha and the lane’s seat-post line (#25386)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DM24jRuzJ1rYKwcydPYYtYc)_
Fixes #25380
Clause-②: no
Metadata-protocol: none
## 维护者速读(草稿)
- **改了什么**:新增一个 `SessionStart` 钩子
`.claude/hooks/session-start-fleet.sh`,会话一开始就把三行话印进上下文:① 「先读
`.claude/skills/pm-dispatch/SKILL.md` 与阶段 references 再派发/认领/复核/落地」的指针;②
本检出上 SKILL.md 最近一次触碰的 sha(供席位与开轮标记比对;浅克隆边界会被明确标出、不冒充真触碰);③ 车道行:环境里有
`OS_FLEET_LANE` 就印该车道的席位贴号与「席位贴只是存根,姿态与台账住项目里」的提醒,没有就印 `lane: not
set`。配套 selftest(58 例)、`settings.json` 注册一条、`seat-lifecycle.md`
加一行点名钩子(删一行已由 SKILL.md 索引承载的读者路由句付账,96→95 行,上限不动)。
- **为什么改**:您 2026-10-23 的原话:「包括Claude code projects
我打开之后怎么让他自动加载相关的技能要求。」「同意,你现在就立卡派发处理。」多仓项目线程的工作目录是三个检出的父目录,嵌套检出的
`.claude/skills` 进不了技能列表,两天来每条席位线程都是靠项目指令手读 SKILL.md。规则仍只住仓库里一份,钩子只印指针。
- **风险与代价(含回滚)**:钩子不联网、不读 GitHub、不写文件,恒 exit 0(连 `set -u` 中断都被 trap 收成
0),失败最多少印几行,不会挡会话;每次会话开始多 3–5 行上下文。一次 revert 可回(删钩子两文件 + settings 一段 +
seat-lifecycle 一行)。两处实测要您知道:(a) 本卡线程里 `CLAUDE_PROJECT_DIR` 未设、已有的
PreToolUse 守卫对一条已知应拦的命令没有拦——即 objectstack 的 `settings.json`
在这种「父目录开会话」的线程里很可能根本没被加载(或 `$CLAUDE_PROJECT_DIR`
指向父目录),那样本钩子在项目线程里同样不会触发;单仓会话与 CI 自检不受影响。(b) `references/lanes/*.md`
目前没有任何一份写了席位贴号,钩子如实印「not recorded」;补号是各车道文件各加一行 `Seat post: #N`,不在本卡范围。
- **席位意见**:(席位复核后填)
- **你要做的(一个动作)**:席位复审 PASS 后,一句「可以落地」;若要让项目线程真的触发它,还需确认平台是否加载嵌套检出的
`settings.json`(见 A1)。
## What changed
| File | Change |
|:--|:--|
| `.claude/hooks/session-start-fleet.sh` (new) | `SessionStart` hook.
Prints the pointer line, the SKILL.md path and its latest touch on HEAD,
an optional one-line note when `CLAUDE_PROJECT_DIR` is not this
checkout, and the lane line. Dependency-free bash (3.2 floor), no jq, no
network, no GitHub read, no write; `trap 'exit 0' EXIT` so it exits 0
under every input measured (closed/empty/malformed stdin, no git on
PATH, no checkout, no SKILL.md, a refused lane value, an `-u` abort).
Everything is resolved from the hook's own path, never the cwd. |
| `.claude/hooks/session-start-fleet.selftest.sh` (new) | 58 cases on a
hermetic fixture checkout (git repo + SKILL.md + two lane files, one
with `Seat post: #19287`, one without): pointer verbatim, touch sha =
`git log -1 -- SKILL.md`, shallow-boundary labelling (the fixture's
touch commit written into the git dir's `shallow` file flips the wording
and removing it flips it back), lane set / unset / empty / no file / no
number / path characters / shell metacharacters, cwd and
`CLAUDE_PROJECT_DIR` not being inputs, ≤15 lines, `bash -n` and the
executable bit (the bash-3.2 floor itself is `check:bash32-floor`'s,
repo-wide), the `settings.json` wiring (registered on `SessionStart`,
sibling spelling, not also a `PreToolUse` guard), and a NON-VACUITY tail
(a mutated pointer, an `exit 1` copy and a `set -u` abort are each
caught or held). Discovered by the existing `find .claude/hooks -name
'*.selftest.sh'` step in `lint.yml`, so CI runs it with no workflow
edit. |
| `.claude/settings.json` | one `hooks.SessionStart` entry:
`"$CLAUDE_PROJECT_DIR/.claude/hooks/session-start-fleet.sh"`, the
sibling spelling. `permissions` untouched
(`check:pm-settings-deny-roster` green). |
| `.claude/skills/pm-dispatch/references/seat-lifecycle.md` | +1 line in
〈开轮互斥〉 under the harness-current line: `- \`session-start-fleet.sh\`
钩子开会话即印 SKILL.md 触碰 sha 与车道席位贴行,供比对开轮标记。` (115 bytes). −1 line: the
opening reader-routing sentence 「每个座位每次 fire 读本文:…」, whose content the
SKILL.md 〈阶段细则索引〉 row for this file already carries (reader + both read
moments). 96 → 95 lines; the ceiling row stays 96 (headroom 1, no
ratchet edit). Placed away from L69 (PR #25348) and from 〈座位贴协议〉
(#25379). ⛔ Not a second copy of the pointer text. |
## The exact lines the hook prints
On this branch's checkout (HEAD `e75a6b53`, a shallow clone), no
`OS_FLEET_LANE`:
```text
Fleet protocol: read .claude/skills/pm-dispatch/SKILL.md (and the stage references its index names) before any dispatch, claim, review or landing act.
SKILL.md: /home/claude/objectstack/.claude/skills/pm-dispatch/SKILL.md
touch: 8010b64 is this SHALLOW clone's boundary, not a measured touch of SKILL.md (HEAD e75a6b5); the real touch is older than the clone holds — deepen (git fetch --deepen=N origin) or read it from a full checkout before comparing with an opening mark.
lane: not set (OS_FLEET_LANE names the lane; set it in the environment this hook runs in — the project's instructions or the agent's launch environment).
```
With `OS_FLEET_LANE=skills`, the last line becomes:
```text
lane: skills · seat post: not recorded in .claude/skills/pm-dispatch/references/lanes/skills.md (no `Seat post: #N` line); read the pm:seat index for it; the seat post is a stub; posture and ledger live in the project.
```
On a full clone the touch line reads `touch: SHA — latest commit
touching SKILL.md on HEAD SHA; compare with the opening mark
(seat-lifecycle.md 〈开轮互斥〉) before the round.`; with a lane file carrying
`Seat post: #19287` the lane line reads `lane: skills · seat post #19287
(.claude/skills/pm-dispatch/references/lanes/skills.md); the seat post
is a stub; posture and ledger live in the project.` (both pinned by the
self-test). When `CLAUDE_PROJECT_DIR` is set and is not this checkout,
one more line is printed: ` note: this session's project dir is not this
checkout, so the harness's skill list does not include this checkout's
.claude/skills — hence this pointer.`
The shallow-boundary wording is not decoration: `8010b64` is in this
clone's `.git/shallow`, and `git log -1 -- SKILL.md` names it only
because the graft makes the boundary look like it touched every path —
the exact fabrication `check-harness-current.mjs`'s header documents. On
`origin/main` from a full checkout the real touch is older; the hook
refuses to present the boundary as a reading.
## Mechanism assumptions, measured (dispatch A1–A4)
- **A1 — does a hook registered in `objectstack/.claude/settings.json`
fire in a project thread, and what does it see?** Measured from this
card thread (working directory `/home/claude` at start, the parent of
the three checkouts): `CLAUDE_PROJECT_DIR` is **unset** in the Bash
tool's environment, and the registered PreToolUse guard
`guard-tree-enum.sh` did **not** block `for f in
.github/workflows/*.yml; do git show "origin/main:$f" | head -1; done` —
its own self-test's first measured block case — the command ran and
printed. So in this thread shape either the nested checkout's
`settings.json` is not loaded at all, or `$CLAUDE_PROJECT_DIR` resolves
to the parent, where `$CLAUDE_PROJECT_DIR/.claude/hooks/…` does not
exist and the hook command fails non-blocking. This contradicts the
card's premise that the guards fire in card threads; it may differ by
how a thread is rooted. Consequence: the hook is correct and
self-tested, but whether a project thread ever runs it is a platform
question, not something this PR can settle. The hook locates SKILL.md
from its own path, so wherever it IS run it prints the right checkout. A
registration that also looks under `$CLAUDE_PROJECT_DIR/objectstack/…`
would not help if the file is not loaded, so the sibling spelling is
kept rather than guessing.
- **A2 — "verified once from a project thread".** Not observable before
merge: the harness reads `settings.json` from the primary checkout at
session start and does not reload it, and the measured thread does not
run the registered hooks at all (A1). ⛔ Not claimed. The self-test
evidence and the exact lines above stand in; the first post-merge
project thread that shows the pointer in its first context is the
acceptance reading, recorded by the seat.
- **A3 — `seat-lifecycle.md` placement.** One line in 〈开轮互斥〉 (L21 after
the edit), away from L69 (#25348, which only rewords that line and moves
no ceiling) and from 〈座位贴协议〉 (#25379). `origin/main` merged when the
seat says so.
- **A4 — `OS_FLEET_LANE` spelling.** `git grep` over `scripts/`,
`.claude/`, `.github/`: no script reads a lane name from the environment
under any spelling (`OS_LANE_STEPS` / `OS_LANE_JOB_STATUS` in `lint.yml`
are the CI lane-verdict inputs, a different thing). `OS_FLEET_LANE` is
new and collides with nothing; it follows the `OS_FLEET_*` family
(`fleet-token.mjs`, `fleet-write/**`).
- **A5 (found, not assumed) — the seat-post number is not in any lane
file.** `references/lanes/*.md` carry no `#N` at all (`grep -rn
'19287\|6023\|1353' .claude/` is empty); seat posts are found by the
`pm:seat` label index. The hook reads an optional line `Seat post: #N`
from the lane file as the card specifies and says `not recorded` when
absent. Writing those lines is one line per lane file, outside this
card's file surface — carrier: the skills seat (a follow-up card or a
rider on #25379's reference edit).
## Verification
- `bash .claude/hooks/session-start-fleet.selftest.sh` → `58 passed, 0
failed`, exit 0 (log in the PR's verification comment). Hook exits 0
with and without `OS_FLEET_LANE`, on a checkout with no SKILL.md, in a
directory that is no checkout, with no git on PATH, with closed, empty
and malformed stdin.
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` on the actual changed set (4 paths, 392
changed lines, under the 3000 threshold): 19 commands derived
(`--commands`, no paths — the tool took the change set off the merge
base itself), every one run on head `e75a6b53` with its exit code
captured before any pipe and reconciled with `--ran`: `19 derived
famil(ies) accounted for — 19 run, 0 NOT-MEASURED`, all exit 0. The
list: check-closing-keyword-parity (+ --self-test),
check-comment-mask-corpus, check-harness-current --self-test, lint
`check:doc-formula-expressions` (exit 3 PREREQUISITE NOT MET on the
first sweep until `@objectstack/formula` and `@objectstack/lint` were
built; exit 0 on the final sweep), check:agent-test-spelling,
check:bash32-floor (red on the first sweep — the selftest had spelled
the forbidden tokens into a grep pattern; fixed in the second commit,
green since), check:cross-package-test-inputs, check:doc-authoring,
check:driver-memory-census, check:gitlink-declared, check:nul-bytes,
check:pm-governed-merges, check:pm-settings-deny-roster,
check:pm-skill-id-lint, check:pm-skill-ratchet, check:refd-timer-probe,
check:skill-frame-sync, check:watch-hint-literal. Repo-wide scans (`pnpm
lint`, the hook self-test discovery step, the type-check lanes) are
CI's; this PR's check-runs are the reading for those.
- `pnpm check:pm-skill-ratchet`: green — `seat-lifecycle.md` 95 lines
against its ceiling 96 (headroom 1; the ceiling row is not moved), every
edited line ≤120 bytes (the new line is 115), no cross-file move
declared.
- The hook's own text carries no issue number (`check:pm-skill-id-lint`
covers the skill files; the hook is read by `check:doc-authoring` and
`check:bash32-floor`, both in the derived list).
## Acceptance notes
- `references/lanes/*.md` carry no `Seat post: #N` line (A5) — carrier:
skills seat; noted, not filed.
- The project-thread hook question (A1): this thread's registered
PreToolUse guards do not fire, with `CLAUDE_PROJECT_DIR` unset in the
tool environment — a platform reading for the seat's
`platform-readings.md` fact table (reference edits are outside this
card). Carrier: skills seat; noted, not filed.
Governed surface `.claude/**` (Tier S): draft, awaiting the seat's
`CONTRACT_REVIEW_TIER` review and the maintainer's word; ⛔ not to be
flipped ready, queued or armed by this thread. `skip-changeset`: no
published package is touched (`.claude/**` and a reference file only).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01SSWEKCxzY8HYr8EsFpR8VG
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 119a338 commit 9c79d92
4 files changed
Lines changed: 388 additions & 2 deletions
File tree
- .claude
- hooks
- skills/pm-dispatch/references
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
0 commit comments