Skip to content

chore(ui/.claude): delete the nested guard hooks that no session loads - #27923

Merged
os-zhuang merged 1 commit into
mainfrom
claude/project-thread-u9pgdn
Oct 30, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/project-thread-u9pgdn

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

Fixes #27260
Part of #26080
Clause-②: no
Metadata-protocol: none

What

After the monorepo merge, ui/ is no session's project root: Claude Code reads the root .claude/settings.json, never ui/.claude/settings.json. So the four nested ui guard hooks never fire, yet they read as live guardrails and cost a governed review on every change.

  • Delete ui/.claude/hooks/* (8 files: guard-main-checkout{,-bash}, guard-shared-stash, guard-tree-enum, each .sh + .selftest.sh).
  • Delete the hooks block of ui/.claude/settings.json that wired them (old L37–61). The permissions block stays; the file is still valid JSON.
  • Move the ui-claude-tree specimen in scripts/pm/check-governed-merges.mjs self-test from ui/.claude/hooks/guard-main-checkout.sh to ui/.claude/skills/verify/SKILL.md, a surviving file under the same row, so the row's Tier H assertions (the-ui-claude-tree-specimen-is-governed-by-its-own-row-alone, every-ui-row-lands-Tier-H-the-ui-claude-tree-included) keep their teeth.

Out of scope and untouched: ui/.claude/launch.json, ui/.claude/skills/**, the permissions block, and the synthetic .claude/hooks/* fixtures in ui/scripts/__tests__/*.

Per-hook coverage (measured on 8c7021dfb)

Each ui hook carried a # copied from objectstack@… header. Compared with comment lines stripped, the verdict logic is identical to the root copy; only issue references and the refusal message wording differ.

ui hook (deleted) Root replacement Root wiring Verdict
guard-main-checkout.sh (+ selftest) .claude/hooks/guard-main-checkout.sh .claude/settings.json PreToolUse Edit|Write|NotebookEdit Covered by root. Code identical once comments are stripped
guard-main-checkout-bash.sh (+ selftest) .claude/hooks/guard-main-checkout-bash.sh root PreToolUse Bash Covered by root. Only the refusal text's issue ref differs (objectui#3435 vs #5790)
guard-shared-stash.sh (+ selftest) .claude/hooks/guard-shared-stash.sh root PreToolUse Bash Covered by root. Root refusal text is a superset (adds the commit-first reverse-verification recipe)
guard-tree-enum.sh (+ selftest) .claude/hooks/guard-tree-enum.sh root PreToolUse Bash Covered by root. Only one message line's wording differs

The root guards protect the whole checkout, ui/** included, because ui/ sits inside it. The root self-tests keep running in lint.yml (find .claude/hooks -name '*.selftest.sh', root only), so CI loses no self-test.

Readers swept before the edit

  • ui/.claude/settings.json L44/L53/L57/L61: edited (block removed).
  • scripts/pm/check-governed-merges.mjs:4519: specimen moved; :4525 reads it through UI_SPECIMENS.
  • Not readers of these files, left as they are: ui/scripts/check-bash32-floor.mjs:242 declares .claude/hooks/** as a population root (it is now an empty root; pnpm --dir ui run check:bash32-floor still finds 5 shell files and passes); ui/scripts/check-governed-queue-guard.mjs:1326 (synthetic path string); ui/scripts/dependabot-merge-gate.mjs:232 (prose about objectui's old workflow); ui/scripts/__tests__/bash32-floor-wiring.test.ts:126 and check-shell-escape-residue.test.ts (synthetic fixtures).

Gates

node scripts/pm/check-governed-merges.mjs --self-test: 537 assertions before and after, exit 0. Every family dispatch-gates --commands derives (36) ran green; see the card report. ui bash32-floor --self-test and run: exit 0.

维护者速读

  • 改了什么:删掉 ui/.claude/hooks/ 下 8 个守卫脚本,以及 ui/.claude/settings.json 里接线它们的 hooks 段;治理自测的 ui-claude-tree 样本改指 ui/.claude/skills/verify/SKILL.md。
  • 为什么改:合仓后 ui/ 不是任何会话的项目根,这些嵌套 hook 从不触发,却看起来像在保护,每次改动还要走受管评审。
  • 风险与回滚:根目录 .claude/hooks/ 有同名四个守卫,判定逻辑一致,已在根 settings 接线,保护不减;回滚即 revert 本 PR。
  • 先看:PR 正文的逐 hook 覆盖表,和 check-governed-merges.mjs 那一行样本改动。
  • 两分钟验证:node scripts/pm/check-governed-merges.mjs --self-test 仍是 537 条全过;ui/.claude/settings.json 仍是合法 JSON。
  • 需要你做的:欠一次契约复审(fable);落地是 Tier H,需受管审批人批准。

🤖 Generated with Claude Code

https://claude.ai/code/session_016WV4cxp4Fjwh9pEpn19fBR

After the monorepo merge `ui/` is no session's project root, so Claude Code
reads the root `.claude/settings.json` and never the nested one. The four ui
guard hooks (guard-main-checkout, guard-main-checkout-bash,
guard-shared-stash, guard-tree-enum, each with its self-test) never fire, yet
they read as live guardrails. The root `.claude/hooks/` carries the same four
guards with identical verdict logic and wires them in the root settings.

Delete the eight files and the `hooks` block of `ui/.claude/settings.json`
that wired them; the file keeps its permissions block and stays valid JSON.

Move the `ui-claude-tree` specimen in check-governed-merges' self-test to
`ui/.claude/skills/verify/SKILL.md`, a surviving file under the row, so the
row's Tier H assertions keep their teeth.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cxp4Fjwh9pEpn19fBR
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 30, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#2 · os-elon-musk · session_016WV4d1QkPVVzNz3FJqb2iS

Contract-review-claim: session_016WV4d1QkPVVzNz3FJqb2iS · head 28502c6 · 2026-10-30T04:04Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#2 · os-elon-musk · session_016WV4d1QkPVVzNz3FJqb2iS

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 28502c678479e95831baee8ed47693fbccd398af
Local-runs: probe — ui node scripts/check-bash32-floor.mjs --self-test (160 cases, exit 0) and the run (5 tracked shell files, exit 0) on a detached worktree of the head, to confirm the population root .claude/hooks/** going empty leaves the gate green; the worktree was removed afterwards. node scripts/pm/check-governed-merges.mjs --self-test was attempted on the same worktree and exited 3 before measuring anything (PREREQUISITE NOT MET: no node_modules in this checkout), so it is not a local measurement; the CI job Lint · repo & tooling gates, which runs pnpm check:pm-governed-merges, is green on this head.
Scope: ui/.claude/settings.json, ui/.claude/hooks/guard-main-checkout.sh, ui/.claude/hooks/guard-main-checkout-bash.sh, ui/.claude/hooks/guard-shared-stash.sh, ui/.claude/hooks/guard-tree-enum.sh, ui/.claude/hooks/guard-main-checkout.selftest.sh, ui/.claude/hooks/guard-main-checkout-bash.selftest.sh, ui/.claude/hooks/guard-shared-stash.selftest.sh, ui/.claude/hooks/guard-tree-enum.selftest.sh + scripts/pm/check-governed-merges.mjs (the ui-claude-tree specimen at :4519 and the assertions at :4523, :4525, :4530–:4532 that read UI_SPECIMENS), .claude/settings.json (root hook wiring, read), .claude/hooks/guard-main-checkout.sh, .claude/hooks/guard-main-checkout-bash.sh, .claude/hooks/guard-shared-stash.sh, .claude/hooks/guard-tree-enum.sh and their four .selftest.sh (root counterparts, read), .github/workflows/lint.yml:1874 (hook self-test discovery, read), ui/scripts/check-bash32-floor.mjs:242 (population root, read), ui/.claude/skills/verify/SKILL.md (the new specimen, read)

① Derived judgments

Diff on origin/main...28502c678: +1/−2846 over 10 files, one commit. Governed paths: 9 of 10 under ui/.claude/** (register row ui-claude-tree, Tier H, CODEOWNERS /ui/.claude/ @os-zhuang @hotlong); the tenth is scripts/pm/check-governed-merges.mjs, the register's own self-test. Mixed diff, one hit is enough: CONTRACT_REVIEW_TIER.

  1. Deleted ui/.claude/hooks/*, 8 files. Correct: git ls-tree of the head shows ui/.claude/ holding only launch.json, settings.json, skills/objectui-contributor/** (4 files) and skills/verify/SKILL.md. The deletion changes no published package surface and no runtime; the files were PreToolUse hooks that only a session rooted at ui/ would load, and after the merge no session is.
  2. Per-hook coverage, verified by diffing each ui file (base) against its root counterpart (head) with comment and blank lines stripped:
    • guard-main-checkout.sh and its selftest: byte-identical logic (diff empty). Root wiring: .claude/settings.json PreToolUse matcher Edit|Write|NotebookEdit, the same event the ui settings used.
    • guard-main-checkout-bash.sh: the only difference is three lines of the refusal message (issue ref objectui#3435 vs #5790, re-wrapped). Selftest: expectations differ only in repo-specific example strings (@object-ui/app-shell vs @objectstack/spec, issue refs, one non-ASCII glyph case). Root wiring: PreToolUse Bash.
    • guard-shared-stash.sh: logic identical; the root refusal text is a superset (adds the commit-first reverse-verification recipe and the Prime Directive Migrate documentation site to Fumadocs with monorepo structure and shared content #11 cite). Selftest: root has the same cases plus two extra expect block cases (guard-shared-stash.sh has the same split_segments() backslash fail-open that #11131 closed in the bash checkout guard #11738), i.e. the root self-test is stricter, not looser. Root wiring: PreToolUse Bash.
    • guard-tree-enum.sh: one message line differs (Measured HERE vs Measured on objectui). Selftest: repo-specific example strings only. Root wiring: PreToolUse Bash.
      Root .claude/settings.json wires all four on the same tool events the ui settings did (and adds guard-process-kill.sh and a SessionStart hook that ui never had). The root guard-main-checkout.sh verdict is keyed on the git common dir of the edited file's directory, not on a path subset, so a write under ui/** on the shared checkout is blocked exactly as one under packages/**. No ui guard protection is lost in the merged repo. The PR body's per-hook table states the same four verdicts and I found them accurate.
  3. ui/.claude/settings.json: only the hooks block (old L37–61) is removed; the permissions.allow (12 rows) and permissions.deny (17 rows) blocks are untouched, and the head file parses (JSON.parse succeeds). ui/.claude/launch.json and ui/.claude/skills/** have an empty git diff --stat against origin/main.
  4. scripts/pm/check-governed-merges.mjs: a one-token change at :4519, 'ui-claude-tree': 'ui/.claude/hooks/guard-main-checkout.sh' becomes 'ui/.claude/skills/verify/SKILL.md'. The register row (:1209, prefix: 'ui/.claude/', Tier H) and the matcher (governedPathsIn :1508, p.startsWith(surface.prefix)) are unchanged, so the new path is governed by ui-claude-tree alone (the root claude-tree row's prefix .claude/ does not match it, and no skills/-prefixed row does either). The row assertions at :4523 (the-ui-claude-tree-specimen-is-governed-by-its-own-row-alone), :4525 (every-ui-row-lands-Tier-H-the-ui-claude-tree-included, including the mixed-diff pair with .claude/agents/os-dev.md), :4530, :4531 and :4532 all read UI_SPECIMENS and keep exercising the row with a path that exists on the head tree. The thread reports 537 assertions before and after; CI Lint · repo & tooling gates (which runs this self-test) is green on the head.
  5. Remaining readers of the deleted paths on the head tree: git grep for ui/.claude/hooks returns nothing; git grep for the four guard names outside .claude/hooks/ returns only the root wiring (.claude/settings.json:106–127), root-hook prose (AGENTS.md:236–337, docs/audits/gate-census-2026-09.md, lint.yml:1816, dispatch-gates.mjs:642), root self-test fixtures in check-governed-merges.mjs (:4453, :4572, :4612) and check-governed-queue-guard.mjs:4646, console CHANGELOG history, and the synthetic ui strings the thread listed (ui/scripts/check-governed-queue-guard.mjs:1326, dependabot-merge-gate.mjs:232, __tests__/bash32-floor-wiring.test.ts:126, check-shell-escape-residue.test.ts:271–547). No test, workflow, CODEOWNERS row, or doc references the deleted files. ui/.github/workflows/** has no .claude/hooks or selftest mention. lint.yml:1874 discovers self-tests with find .claude/hooks from the root only, so CI never ran the ui copies and loses nothing.
  6. ui/scripts/check-bash32-floor.mjs:242 keeps .claude/hooks/** as a population root; on the head its tracked population is 5 shell files (e2e/live/ci/start-backend.sh, stop-backend.sh, scripts/ci-setup-pnpm.sh, ensure-chromium-ready.sh, setup.sh), down from 13, and the gate exits 0 with its self-test (160 cases) green. The gate only fails on an empty total population, so the empty root is harmless.

② Semver level

No published package changes; skip-changeset label present and Check Changeset is green. Clause-② no, Metadata-protocol none, both declared on the PR body and consistent with the diff.

③ Boundary flags

④ Design judgment

hits: ui/.claude/settings.json, the 8 ui/.claude/hooks/* files.

  • The deletion holds against the scenario the card accepts it on: Claude Code resolves hooks from $CLAUDE_PROJECT_DIR/.claude/settings.json, and the merged repository's project root is the repository root, so the nested ui/.claude/settings.json hook rows were never read and the eight scripts never executed. Removing them takes away no enforcement (② above shows the root copies are the same logic on the same events, in one case stricter) and removes a governed surface that misled readers into believing a second guard set was live, which is exactly the card's business reason.
  • Keeping the permissions block of ui/.claude/settings.json is the right scope: the card names only the hook rows; whether the nested permissions block is itself dead is [Decision] ui/ merge leftovers that no aftercare card names: six nested top-level docs, a second copy of four .claude hooks, an independently numbered ui/docs/adr, and a dead ui/.github/dependabot.yml #26080's remaining question, not this card's.
  • The specimen edit in check-governed-merges.mjs is the rule and its gate read as one change: the row text is untouched, only the self-test's probe path moves to a file that exists, keeping ui-claude-tree under Tier H assertions. A synthetic path would also have satisfied triage; a real surviving file is the better choice because ids() is path-string based and a real file also keeps the Tier H human-approval scenario (governedTierFor with a root .claude/agents path mixed in) tied to something a PR can actually touch.
  • Nothing here changes the governed register, AGENTS.md or any published skill; no ADR is cited or owed.

Implemented-by: claude/project-thread-u9pgdn
Reviewed-by: session_016WV4d1QkPVVzNz3FJqb2iS

VERDICT: PASS

Must-fix: none.

Should-fix (not blocking):

  1. The thread report's other_readers_left entry for ui/scripts/check-bash32-floor.mjs:242 says the .claude/hooks/** root is "now empty"; the PR body says the same. Both are accurate, but a one-line follow-up (devx, not this PR) could drop the dead root from POPULATION_ROOTS in a later ui tooling pass so the gate's message stops naming a directory that no longer exists.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#2 · os-elon-musk · session_016WV4d1QkPVVzNz3FJqb2iS

维护者速读

  • 改了什么:删掉 ui/.claude/hooks/ 下 8 个文件(guard-main-checkout、guard-main-checkout-bash、guard-shared-stash、guard-tree-enum,各一个 .sh 加一个 .selftest.sh),以及 ui/.claude/settings.json 里挂这些 hook 的那一段(permissions 不动)。check-governed-merges.mjs 的 ui-claude-tree 自测样本从被删的 hook 改指 ui/.claude/skills/verify/SKILL.md。10 个文件 +1/−2846。
  • 为什么改:合仓之后,ui/.claude/ 不再是任何会话的项目根,这 4 个 hook 永远不会触发,看上去却像有效的护栏,而且每次改动都要走一次受管复核。根目录 .claude/hooks/ 已有同名的 4 个 guard,判定逻辑相同,并按相同事件挂在根 settings.json 上。
  • 风险与回滚:没有丢失任何保护:逐个 hook 比对过,只有提示文案不同;根的 main-checkout 按 git common dir 判定,覆盖 ui/**。治理自测仍覆盖 ui-claude-tree 这一行(Tier H)。revert 即回滚。
  • 先看:PR 正文的逐 hook 覆盖表,再看 check-governed-merges.mjs 里 UI_SPECIMENS 那一行。
  • 两分钟验证:git grep -n "ui/.claude/hooks" 应无命中;pnpm check:pm-governed-merges 应 exit 0。
  • 需要你做的:ui/.claude/** 在登记册上是 ui-claude-tree Tier H,请批准本 PR(契约复核 PASS 6514785826,Head-sha: 28502c678479e95831baee8ed47693fbccd398af)。

席位意见:ACCEPT。契约复核 PASS,没有 must-fix。唯一的 should-fix 是从 ui/scripts/check-bash32-floor.mjs:242 去掉已经空了的 .claude/hooks/** 根,不在认领文件面内,下面写明去向。CI 全绿。

席位核对

  • 形态: one PR, Fixes #27260, Part of #26080, ten files (+1/−2846): the 8 deleted hooks, ui/.claude/settings.json (hooks block only), scripts/pm/check-governed-merges.mjs (one specimen). skip-changeset. Governed ui/.claude/**: check-governed-merges.mjs reads Tier H (ui-claude-tree); 2847 changed lines, under the 3000 threshold.
  • 前提实测: per hook, the root guard's verdict logic matches the ui copy (main-checkout byte-identical; the other three differ only in message lines), and root .claude/settings.json wires all four on the same events (review ①). No reader of ui/.claude/hooks remains on the head; ui/.claude/launch.json and ui/.claude/skills/** are unchanged.
  • 门禁 check-runs: head 28502c678479e95831baee8ed47693fbccd398af: 61 check-runs, 47 success, 14 skipped, 0 failed, 0 pending; Lint · repo & tooling gates (runs check:pm-governed-merges) success; mergeable_state clean. Locally 36 derived gates green; governed-merges self-test 537 before and after.
  • 接受的偏差: none. The synthetic .claude/hooks strings in ui/scripts/* are left, as the card says.
  • 范围外去向: review should-fix 1, the empty .claude/hooks/** population root in ui/scripts/check-bash32-floor.mjs:242, is noted and not filed: the gate still has a non-empty population (5 tracked shell files) and tolerates an empty root. The next devx ui/scripts change picks it up.
  • 落地条款: Tier H: a GOVERNED_APPROVERS approval, then the seat lands it under the maintainer's landing grant for this term (seat thread, 2026-10-29 22:04Z).

@os-zhuang
os-zhuang marked this pull request as ready for review October 30, 2026 07:24
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit 427c943 Oct 30, 2026
70 checks passed
@os-zhuang
os-zhuang deleted the claude/project-thread-u9pgdn branch October 30, 2026 07:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xl skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[skills] ui/ merge leftover: delete the nested ui/.claude/hooks/* (8 files) and the ui/.claude/settings.json hook rows that wire them (#26080 item 1)

2 participants