Skip to content

feat(hooks): SessionStart hook prints the pm-dispatch pointer, its SKILL.md touch sha and the lane’s seat-post line - #25386

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-06hewe
Oct 23, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-06hewe

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

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:

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: 8010b641 is this SHALLOW clone's boundary, not a measured touch of SKILL.md (HEAD e75a6b53); 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:

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 (docs(pm-dispatch): revoke every autonomous-adjudication channel — a seat analyses and recommends, every needs_decision goes to the maintainer's ruling batch #25348, which only rewords that line and moves no ceiling) and from 〈座位贴协议〉 (pm-dispatch: a project-run seat form — seat post as a stub, posture and ledger in the Claude Code project, dispatch order in the card thread, branch named before the thread opens; 项目总监 → 总监席 (maintainer's word 2026-10-23) #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 pm-dispatch: a project-run seat form — seat post as a stub, posture and ledger in the Claude Code project, dispatch order in the card thread, branch named before the thread opens; 项目总监 → 总监席 (maintainer's word 2026-10-23) #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.ai/code/session_01SSWEKCxzY8HYr8EsFpR8VG

…uch sha and the lane's seat-post line

A multi-repo project session has the parent of the checkouts as its working
directory, so a nested checkout's .claude/skills never reaches the session's
skill list and /pm-dispatch cannot be invoked there. session-start-fleet.sh
prints the pointer to SKILL.md into the session's first context, the latest
touch of SKILL.md on HEAD (a shallow clone's graft boundary is labelled as
one, never presented as a measured touch), and — when OS_FLEET_LANE names a
lane — the lane's seat-post number read from references/lanes/<lane>.md plus
the stub reminder. No network, no GitHub read, exit 0 always; located by its
own path, never the cwd. Registered beside the PreToolUse guards; its
self-test builds a hermetic fixture checkout. seat-lifecycle.md gains one
line in 〈开轮互斥〉 naming the hook, paid by the reader-routing line the
SKILL.md stage index already carries.

Claude-Session: https://claude.ai/code/session_01SSWEKCxzY8HYr8EsFpR8VG
Co-authored-by: Claude <noreply@anthropic.com>
…o the repo-wide gate

The selftest's own construct scan spelled the forbidden tokens into a grep
pattern and a message, which scripts/check-bash32-floor.mjs flags wherever
they stand (no filename allowlist, by design). The floor is already held
over .claude/hooks/** by that gate; the selftest keeps bash -n and the
executable bit and points at the gate instead.

Claude-Session: https://claude.ai/code/session_01SSWEKCxzY8HYr8EsFpR8VG
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added domain:skills skip-changeset PR has no user-facing published change; bypasses the changeset gate tooling labels Oct 23, 2026
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation labels Oct 23, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Verification (head e75a6b5)

bash .claude/hooks/session-start-fleet.selftest.sh:

  ok   says     and the pointer still prints
== syntax and mode ==
  ok   bash -n parses the hook
  ok   the hook is executable
== wiring: settings.json registers this hook on SessionStart ==
  ok   settings.json runs session-start-fleet.sh on SessionStart (/home/claude/objectstack/.claude/hooks/../settings.json)
  ok   the registration is spelled like its siblings ($CLAUDE_PROJECT_DIR/.claude/hooks/…)
  ok   the hook is not wired as a PreToolUse guard
== NON-VACUITY: the matrix can fail ==
  ok   a mutated pointer is caught (the mutation really applied and `says` sees the difference)
  ok   a copy that exits 1 is seen as exit 1 by `run` (so exit0 can fail)
  ok   exit 0   an unset-variable abort under set -u still exits 0 (the EXIT trap holds the contract)

session-start-fleet selftest: 58 passed, 0 failed

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths), every command run with its exit code captured before any pipe:

node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/pm/check-harness-current.mjs --self-test :: exit 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0
pnpm check:agent-test-spelling :: exit 0
pnpm check:bash32-floor :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:pm-governed-merges :: exit 0
pnpm check:pm-settings-deny-roster :: exit 0
pnpm check:pm-skill-id-lint :: exit 0
pnpm check:pm-skill-ratchet :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:skill-frame-sync :: exit 0
pnpm check:watch-hint-literal :: exit 0

--ran reconciliation: ✓ dispatch-gates --ran: 19 derived famil(ies) accounted for — 19 run, 0 NOT-MEASURED.

First sweep (head 857b804) had two reds, both resolved before this head: check:bash32-floor exit 1 (the selftest spelled the forbidden tokens into a grep pattern; that scan is removed — the floor is the repo-wide gate's) and check:doc-formula-expressions exit 3 PREREQUISITE NOT MET (@objectstack/formula / @objectstack/lint unbuilt; built, then exit 0).


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: e75a6b533b4b1838faa9cbed0868e954e21cbfa3
Local-runs: probe — ran the head's hook and its selftest from a scratch copy of the committed tree (not the derived gate families; those are read off the head's check-runs) to read the exact printed lines and exit codes under lane set / unset / refused value / no SKILL.md / no git work tree / CLAUDE_PROJECT_DIR pointing at the parent directory
Scope: .claude/hooks/session-start-fleet.sh, .claude/hooks/session-start-fleet.selftest.sh, .claude/settings.json, .claude/skills/pm-dispatch/references/seat-lifecycle.md + .claude/skills/pm-dispatch/SKILL.md (〈阶段细则索引〉 row for seat-lifecycle.md, read for the deleted routing sentence), .github/workflows/lint.yml (the find .claude/hooks -name '*.selftest.sh' discovery step and the lint-repo-tooling job that runs check:pm-skill-ratchet / check:pm-skill-id-lint / check:bash32-floor / check:doc-authoring / check:pm-settings-deny-roster), scripts/pm/check-skill-line-ratchet.mjs (ceiling row), .claude/hooks/guard-tree-enum.sh (sibling shape)

Reviewed 2026-10-23T05:28Z. Inputs: card #25380 body and both comments (the claim and the os-dev-report), PR #25386 body, file list and net diff against merge-base d12e650d, the 50 check-runs on the head, and the PR's verification comment. Spec read off the card: the maintainer's words 「包括Claude code projects 我打开之后怎么让他自动加载相关的技能要求。」 and 「同意,你现在就立卡派发处理。」, Wanted 1–3, Not wanted, Acceptance.

① Derived judgments

  1. File surface equals the claim. Four paths, all inside the claim's File surface; no Tier H path (AGENTS.md, CLAUDE.md, workflows untouched); SKILL.md and every other reference untouched; scripts/pm/check-skill-line-ratchet.mjs untouched (the claim allowed it only if a ceiling row were needed — none was). Right.
  2. Wanted 1 (pointer + touch sha). The hook prints the pointer sentence verbatim as the card spells it, then SKILL.md: PATH, then the latest touch via git log -1 --format=%h -- .claude/skills/pm-dispatch/SKILL.md on the checkout the hook lives in (resolved from BASH_SOURCE, never the cwd and never CLAUDE_PROJECT_DIR). A touch sha that sits in the git dir's shallow file is labelled as a boundary instead of a touch; the selftest flips that both ways on its fixture. Right, and the boundary labelling is a correct refinement the card did not ask for but check-harness-current.mjs documents the same artefact.
  3. Wanted 2 (lane line). OS_FLEET_LANE unset or empty ⇒ lane: not set …; set ⇒ validated against [A-Za-z0-9_-] before it is spelled into a path, then references/lanes/LANE.md is read for the first Seat post: #N line (digits only, any bullet prefix) and the stub reminder 「the seat post is a stub; posture and ledger live in the project」 rides the line in every lane branch. A refused value is never echoed (probe: OS_FLEET_LANE='$(id)' printed the refusal rule only). Right.
  4. Wanted 3 (nothing else). No network, no GitHub read, no file write: the only external commands are cat (drains stdin), git rev-parse / git log on the local checkout, sed, grep, head. set -u plus trap 'exit 0' EXIT makes every exit 0, including an unbound-variable abort (selftest NON-VACUITY case) and the paths probed here: no SKILL.md (prints SKILL.md: MISSING at PATH), not a git work tree (touch: unavailable), git absent from PATH (selftest), closed / empty / malformed stdin (selftest). Printed lines: 4 normally, 5 when CLAUDE_PROJECT_DIR is set and is not this checkout; selftest asserts ≤15. Right.
  5. No env value leaks. The only environment-derived strings printed are the validated lane name and the hook's own resolved path; CLAUDE_PROJECT_DIR is compared, never printed. Right.
  6. settings.json registration. The diff appends one hooks.SessionStart array holding one {"hooks":[{"type":"command","command":"\"$CLAUDE_PROJECT_DIR/.claude/hooks/session-start-fleet.sh\""}]} entry — valid Claude Code SessionStart syntax (no matcher ⇒ every source: startup, resume, clear, compact), spelled like the five sibling registrations. The two existing PreToolUse entries and the permissions block are byte-identical to origin/main; check:pm-settings-deny-roster ran green in Lint · repo & tooling gates. Right.
  7. Selftest covers the acceptance cases. Hermetic fixture (own git repo + SKILL.md + two lane files, one with Seat post: #19287, one without); cases pin the pointer verbatim, the touch sha against git log -1, the shallow-boundary label in both directions, lane set / unset / empty / no file / no number / path characters / shell metacharacters, CLAUDE_PROJECT_DIR = parent / = checkout / = checkout with trailing slash, no SKILL.md, no checkout, no git on PATH, closed / empty / malformed stdin, ≤15 lines, bash -n, the executable bit (mode 100755 in the committed tree, verified with git ls-tree), the settings.json wiring (SessionStart yes, PreToolUse no) and a NON-VACUITY tail (mutated pointer caught, exit-1 copy seen, set -u abort still 0). It is discovered by the existing find .claude/hooks -type f -name '*.selftest.sh' step in lint.yml with no workflow edit, so the head's green Lint · repo & tooling gates is the CI reading. Right. One case is vacuous but harmless: the line ( cd "$tmp" && run "$H" ); runs in a subshell, so its OUT/RC are discarded and nothing is asserted on the cwd-independence run itself; cwd-independence is nonetheless held by construction (BASH_SOURCE) and by the CLAUDE_PROJECT_DIR=parent case that follows. Noted, not a defect of the contract.
  8. seat-lifecycle.md. One line added in 〈开轮互斥〉 (L21): 「session-start-fleet.sh 钩子开会话即印 SKILL.md 触碰 sha 与车道席位贴行,供比对开轮标记。」 — 115 bytes, names the hook and what it prints, ⛔ does not copy the pointer text. One line deleted (the opening reader-routing sentence 「每个座位每次 fire 读本文:开轮互斥读数之前读〈开轮互斥〉,收班之前读其余各节。」): the SKILL.md 〈阶段细则索引〉 row for this file (L81) already carries the reader and both read moments (「每个座位每次 fire:开轮互斥读数前、收班前」), so no rule is lost. Line count 96 → 95 by wc -l on the committed blob; ratchet row stays ['.claude/skills/pm-dispatch/references/seat-lifecycle.md', 96] (headroom 1, no ratchet edit); every line ≤120 bytes (LC_ALL=C scan found none over); the placement is away from L69 (docs(pm-dispatch): revoke every autonomous-adjudication channel — a seat analyses and recommends, every needs_decision goes to the maintainer's ruling batch #25348) and from 〈座位贴协议〉 (pm-dispatch: a project-run seat form — seat post as a stub, posture and ledger in the Claude Code project, dispatch order in the card thread, branch named before the thread opens; 项目总监 → 总监席 (maintainer's word 2026-10-23) #25379). The card's Acceptance allows the paying deletion (「paid by one deletion or within ceiling」). Right.
  9. id-lint. The new reference line carries no issue number; check:pm-skill-id-lint scans references/, os-dev.md and AGENTS.md, not .claude/hooks/, so the selftest fixture's #19287 is outside its corpus — and the gate ran green on the head anyway. Right.
  10. Shape. Dependency-free bash with the bash-3.2 floor stated in the header, set -u, resolves itself from BASH_SOURCE, hermetic *.selftest.sh beside it, registered via $CLAUDE_PROJECT_DIR/.claude/hooks/… — the same shape as guard-*.sh; check:bash32-floor green on the head. Right.
  11. Check-runs on the head. 50 check-runs, all completed, none failure; Lint & Repo Gates and TypeScript Type Check both success; Governed Surface Queue Guard, Check Changeset, both claim guards and the single-writer-path guard success. Right.

② Semver level

Clause-②: no on the PR body is correct: no published package, schema or exports map is touched (.claude/** + one reference file). skip-changeset label present and Check Changeset green; no changeset file in the diff. Consistent. The face that made this review owed is the governed rules text (.claude/**), hence CONTRACT_REVIEW_TIER.

③ Boundary flags

  • A1 (does the nested objectstack/.claude/settings.json load in a project thread rooted at the parent directory?) — not settled by this PR, and the evidence stands on both sides. For the dev's reading: the card thread measured CLAUDE_PROJECT_DIR unset and guard-tree-enum.sh not blocking its own selftest's first block case (2026-10-23T05:22Z, the os-dev-report). This review's own session (a subagent of the seat session, working directory the parent of the three checkouts) read the same: CLAUDE_PROJECT_DIR unset in the Bash tool environment, and for f in .github/workflows/*.yml; do git show "origin/main:$f" | head -1; done ran and printed three workflow names instead of being blocked. For the card's premise: another card thread reported the guards blocking edits in its primary checkout (the card's 「measured on card threads 2026-10-22/23」). The two readings are reconcilable if the earlier thread was rooted in the objectstack checkout itself rather than the parent; nothing in the diff can tell them apart. The hook is correct wherever it IS run (it finds SKILL.md from its own path), and its registration is the only spelling that works if the file is loaded at all, so the dev's recommendation is adopted: merge, then take the acceptance reading (「verified once from a project thread」) from the first post-merge project thread and record it on the card; a pointer line in CLAUDE.md is Tier H and would be a separate card for the maintainer to rule on. Carrier: skills seat, as the dev wrote. Recorded for platform-readings.md, outside this card.
  • A2 (「verified once from a project thread」 not observable before merge) — accepted as stated; the PR body does not claim it, and the selftest plus the exact printed lines stand in. The acceptance reading is owed post-merge (see A1).
  • A3 (placement in seat-lifecycle.md) — verified, see ①.8.
  • A4 (OS_FLEET_LANE spelling) — no existing script reads a lane name from the environment under another spelling; OS_LANE_STEPS / OS_LANE_JOB_STATUS in lint.yml are CI lane-verdict inputs, a different thing. Accepted.
  • A5 (lane files carry no Seat post: #N line) — confirmed against the committed references/lanes/*.md (no file carries one), so the hook prints seat post: not recorded in …; read the pm:seat index for it for every lane until one line per lane file is written. That edit is outside this card's file surface (⛔ 「every other reference」); the hook's behaviour is right as specified. Carrier: skills seat; a follow-up card or a rider on pm-dispatch: a project-run seat form — seat post as a stub, posture and ledger in the Claude Code project, dispatch order in the card thread, branch named before the thread opens; 项目总监 → 总监席 (maintainer's word 2026-10-23) #25379's reference edit.
  • The shallow-clone touch line. Wording is accurate for the case it names: when the sha git log -1 returns is a graft boundary, the real touch is at or below the boundary, and the line says so instead of presenting the boundary as a reading. One nuance: 「the real touch is older than the clone holds」 over-states slightly — the boundary commit itself might be the genuine touch; 「at or older than the boundary」 would be exact. The line is 258 bytes, the longest the hook prints; the card's budget is lines (≤~15), not bytes, and the hook stays at 4–5 lines, so it is within spec. Both are optional polish for a later touch of the hook, not a FAIL. On this review's own clone the touch on the head is 345f0b15 and not a boundary, so the measured-touch wording was exercised too.
  • The cat stdin drain — correct for the harness (JSON on stdin); an operator running the hook by hand from a terminal would wait for EOF. The selftest always pipes. Noted only.
  • open_questions in the os-dev-report: the single question (A1) is answered above.

Implemented-by: claude/project-thread-06hewe
Reviewed-by: session_01REMuzhz6MWRjg8uohedMnM

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:skills#2 · os-elon-musk · session_01REMuzhz6MWRjg8uohedMnM · 2026-10-23T06:18Z · head e75a6b533b4b1838faa9cbed0868e954e21cbfa3 · Tier S (check-governed-merges.mjs --pr 25386: .claude/** ×4, no Tier H path, 390 changed lines ≤ 3000)

Landing authority: the maintainer's own words in the seat thread of the Claude Code project "Objectstack" (os-elon-musk), 2026-10-23T06:16Z: 「#25348 #25254 #25386 可以落地」.

  1. Shape: draft, base main, Fixes #25380 the only closing keyword, Clause-②: no, skip-changeset; files = the claim's surface (.claude/settings.json, the new hook and its selftest, one seat-lifecycle.md line).
  2. Premise measured: the card's premise that repo hooks fire in project threads is contradicted by two independent readings (card thread and reviewer: CLAUDE_PROJECT_DIR unset, guard-tree-enum.sh did not block its own first block case). The hook is correct and self-tested; whether a project thread runs it is a platform reading, taken from the first post-merge project thread and recorded on pm-dispatch: a SessionStart hook prints the lane's seat-post stub and the SKILL.md pointer, because a multi-repo project's working directory does not surface the repo's .claude/skills (maintainer's word 2026-10-23) #25380.
  3. Gates (check-runs pinned to this head): 50 completed, none failed per the review record; selftest 58/58; dev's 19 dispatch gates: 19 run, all exit 0.
  4. Contract review: CONTRACT_REVIEW_TIER PASS on this head, record 6358135938.
  5. Accepted deviations: seat-lifecycle.md pays its line by deleting the reader-routing sentence the SKILL.md index row already carries (96 → 95); lane files carry no Seat post: #N line yet, so the hook prints "not recorded" (follow-up for the seat); the shallow-clone touch wording is slightly strong (reviewer note, not a defect).
  6. Out of scope and landing: the acceptance reading from a project thread and the lane-file seat numbers are follow-ups. This seat flips ready and arms auto-merge now.

Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation domain:skills size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tooling

Projects

None yet

2 participants