Skip to content

skills(hooks): remove the client-side enqueue guard guard-governed-enqueue.sh — the merge_group queue guard is the line, the hook only ever bought a cycle (ruling B on PR #18447) #18470

Description

@hotlong

Filed by the director seat (summon #24, session_01Wj1HUjzyeiBQ8atRf1ZhaL) executing the maintainer's ruling B on PR #18447 — reply verbatim 「b」, 2026-09-16 ~14:2xZ, on the director's direct channel. ⛔ No domain:* set by the filer; the surface is .claude/hooks/** + .claude/settings.json + PM gate rosters, which the lane table anchors to domain:skills — triage confirms.

Dedup terms: guard-governed-enqueue · enqueue guard removal · PreToolUse hook · ccr/auto_merge route · check-governed-queue-guard

Ruling being executed (PR #18447, batch #143 item 4)

The hard line for 「受管面 PR 无授权批准时永不入队」 is scripts/pm/check-governed-queue-guard.mjs on the merge_group build (reads reviews live, refuses; measured 2026-09-01 and held) plus the approver's click. Main's ruleset requires no review on its own (required_approving_review_count: 0, require_code_owner_review: false). The client-side hook guard-governed-enqueue.sh existed only to save one queue cycle and one red entry per violation, watches three spellings no compliant seat uses, parses shell text (its own header lists the wrappers that walk past it), and cost three governed PRs today. The maintainer chose removal over completion.

Deliverable

  1. Delete .claude/hooks/guard-governed-enqueue.sh and .claude/hooks/guard-governed-enqueue.selftest.sh.
  2. Remove both settings.json hook registrations that name it ("$CLAUDE_PROJECT_DIR/.claude/hooks/guard-governed-enqueue.sh", two entries on origin/main).
  3. Update every roster / gate family that names the hook or its self-test (scripts/pm/dispatch-gates.mjs families, check:pm-dispatch-gates, any check-* that enumerates .claude/hooks/* or the settings hook list) so the gates are green by design — ⛔ not by adding an exemption for a missing file.
  4. Prose that describes the hook as a live control (SKILL.md / references / AGENTS.md, if any line does) is re-keyed in the same PR; grep before claiming there is none.
  5. ⛔ Not touched: scripts/pm/check-governed-queue-guard.mjs, the settings.json deny roster (MCP enqueue class stays denied), CODEOWNERS.

Acceptance

  • git ls-files .claude/hooks/guard-governed-enqueue* → 0; grep -rn guard-governed-enqueue .claude scripts AGENTS.md → 0 outside CHANGELOG / .changeset/.
  • node scripts/pm/dispatch-gates.mjs --commands .claude/settings.json lists no family that runs the deleted self-test; every listed command exits 0 on the branch.
  • Governed surface (.claude/**): draft PR, 维护者速读(草稿), four-piece by the seat, maintainer's approval lands it.

Serialisation

objectstack#18468 (dispatched, SKILL.md:92 + check-harness-current.mjs) is in flight on a disjoint file surface; the two PRs are independent. Land whichever is approved first.

Refs PR #18447 · #18468 · the 2026-09-01 incident the hook's header cites


Generated by Claude Code

Activity

  1. self-assigned this
    on Sep 16, 2026
  2. hotlong commented on Sep 16, 2026

    @hotlong
    ContributorAuthor

    Claim: PM loop round 1 — maintainer direct dispatch to the director seat
    Session: session_01Wj1HUjzyeiBQ8atRf1ZhaL
    Branch: claude/issue-18470-remove-enqueue-guard-hook
    Worktree: objectstack-issue-18470
    Domain: domain:skills
    File surface: .claude/hooks/guard-governed-enqueue.sh + .selftest.sh (deleted) · .claude/settings.json (two hook registrations removed; ⛔ deny roster untouched) · the rosters that name the hook on origin/main: scripts/pm/check-governed-merges.mjs, scripts/pm/check-governed-queue-guard.mjs, scripts/pm/check-settings-deny-roster.mjs, and scripts/pm/dispatch-gates.mjs families if any names the self-test · any prose line in .claude/skills/** / AGENTS.md that describes the hook as a live control (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: claude-fable-5-1 — dispatch-gates.mjs --tier on this surface: 「no path-derived mandate」; the director dispatches at the session's own tier (a governed .claude/** change whose review the director takes in-seat at that tier), no downgrade exit taken
    Clause-②: no
    Thread-read: none
    Serial constraints cleared: objectstack#18468 (dispatched by this seat, branch claude/issue-18468-harness-current-report-only) touches SKILL.md:92 and scripts/pm/check-harness-current.mjs only — disjoint from this surface; PR #18447 (the last PR on these hook files) is CLOSED unmerged at 14:22Z under ruling B; os-verify-lock.sh --status reads no holder, no tickets; last landings on this surface #18423 / #18390 / #18365 / #18317 all MERGED

    Authority for a director claim: the maintainer's direct-dispatch order on this session's channel, verbatim — 「b」 (ruling B on PR #18447) then 「这个卡你也直接派发」 (2026-09-16 ~14:2xZ). domain:skills set in the same write under that order (surface = .claude/** + PM gate rosters, anchored to domain:skills by the lane table).


    Generated by Claude Code

  3. hotlong commented on Sep 16, 2026

    @hotlong
    ContributorAuthor

    os-dev-report

    {
      "issue": 18470,
      "status": "done",
      "branch": "claude/issue-18470-remove-enqueue-guard-hook",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/18478",
      "session": "session_01Wj1HUjzyeiBQ8atRf1ZhaL",
      "premise_still_valid": true,
      "summary": "Executed ruling B on PR #18447 (verbatim 「b」): deleted .claude/hooks/guard-governed-enqueue.sh and .selftest.sh, removed both settings.json registrations (the standalone MCP-enqueue matcher entry whose only command was this hook, and the one command block in the Bash matcher; permissions incl. the 17-entry deny roster byte-identical to origin/main, other hooks unchanged and in order), and re-keyed the four comment sites in check-governed-merges.mjs (2), check-governed-queue-guard.mjs (1, refusal logic untouched) and check-settings-deny-roster.mjs (2 paragraphs, roster untouched) so each gate is green by design with no exemption and no weakened assertion. Premise verified on origin/main at BASE 8fe5cb8e: git grep -l guard-governed-enqueue origin/main = exactly the six files the card names; dispatch-gates.mjs and package.json name neither the hook nor its self-test; lint.yml discovers *.selftest.sh with find at run time (now 5). Step 4 prose grep over .claude/skills, .claude/agents, AGENTS.md, CLAUDE.md, docs/adr found zero lines describing the hook as a live control, so no SKILL.md/AGENTS.md/ADR line changed (SKILL.md:92 and check-harness-current.mjs untouched). Claim comment verified naming this branch; assignee was already set by the dispatch. Draft PR #18478 opened against main, first line Fixes #18470, skip-changeset applied and read back, 维护者速读(草稿) present; governed surface, left DRAFT, nothing flipped or armed. Deviation from the dispatch's Writes list: the standard clauses' POST /issues/NUMBER/labels write (skip-changeset) was made and is reported below.",
      "tests": "Gates derived with node scripts/pm/dispatch-gates.mjs --commands (no paths; from merge base): 40 commands at 9784cd23, identical list at merged head afd9fcb2; every command run with exit captured before any pipe (cmd > log 2>&1; ec=$?). 39/40 exit 0 first pass; pnpm --filter @objectstack/lint run check:doc-formula-expressions exit 3 = PREREQUISITE NOT MET (formula/lint unbuilt, nothing measured) then exit 0 after turbo run build --filter=@objectstack/formula --filter=@objectstack/lint under os-verify-lock.sh (VERDICT command-exit 0, cache hit). dispatch-gates --ran with per-row exit codes on afd9fcb2: '40 derived, 40 run, 0 NOT-MEASURED, 0 UNRUN — a DERIVED zero — all 40 recorded an exit code and none of them is 3'. Named: check:pm-settings-deny-roster '36 cases pass' + '17 content-write tool(s) declared = enforced' (exit 0); check:pm-governed-merges '328 assertions' (exit 0); check-governed-queue-guard.mjs --self-test '238 cases pass' (exit 0); check:pm-dispatch-gates 0; check:nul-bytes 0; check-harness-current.mjs --self-test 0; check:cross-package-test-inputs 0; check:bash32-floor 0 (re-run on afd9fcb2 after main moved that script: '31 tracked shell file(s)'). Hook self-tests (lint.yml's find set): 5 discovered, all exit 0 — guard-main-checkout-bash '130 passed, 0 failed', guard-main-checkout '120 passed, 0 failed', guard-process-kill '69 passed, 0 failed', guard-shared-stash '53 passed, 0 failed', guard-tree-enum '38 passed, 0 failed'. After merging origin/main 85c6d76e (check-bash32-floor.mjs +41, two SKILL.md lines): check:skill-frame-sync, check-skills-token-ratchet.mjs and --self-test, check:pm-dispatch-gates re-run exit 0 on afd9fcb2. Not run: check:pm-skill-ratchet / check:pm-skill-id-lint (no .claude/skills line changed). Lint proven narrowing at afd9fcb2: population from eslint.config.mjs base block **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} minus NEVER_LINTED; .claude/settings.json is 'File ignored because no matching configuration was supplied' and the deleted .sh files match no glob, so this diff's population is exactly the 3 .mjs files; --format json count 3 files, 0 errors, 0 warnings; invariance: config line 327 'never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file'. Repo-wide pnpm lint is CI's. Acceptance greps: git ls-files .claude/hooks/guard-governed-enqueue* = 0; grep -rn guard-governed-enqueue .claude scripts AGENTS.md CLAUDE.md docs .github package.json = 0 hits. Control-char scan on the 4 edited files clean. No ablation: no new assertion was added (removal + comment re-keys only). Not measured locally: CI's Test Core shard job and the 11 wide-population / 48 artifact-roster families dispatch-gates names as CI-owned; CI status at report time in_progress.",
      "gates": "check:pm-settings-deny-roster=0, check:pm-governed-merges=0, check-governed-queue-guard.mjs --self-test=0, check:pm-dispatch-gates=0, check:nul-bytes=0, check:bash32-floor=0 (merged head), check-harness-current.mjs --self-test=0, check:cross-package-test-inputs=0, check:doc-formula-expressions=3 then 0 after prerequisite build, remaining 31 derived commands=0, hook self-tests 5/5=0, dispatch-gates --ran 40/40/0 NOT-MEASURED; narrowed eslint=0 (3 files)",
      "files_changed": ".claude/hooks/guard-governed-enqueue.sh (deleted), .claude/hooks/guard-governed-enqueue.selftest.sh (deleted), .claude/settings.json (-13, hook registrations only), scripts/pm/check-governed-merges.mjs (comments), scripts/pm/check-governed-queue-guard.mjs (docblock), scripts/pm/check-settings-deny-roster.mjs (header)",
      "grep_step3": "git grep -l guard-governed-enqueue origin/main = .claude/hooks/guard-governed-enqueue.selftest.sh, .claude/hooks/guard-governed-enqueue.sh, .claude/settings.json, scripts/pm/check-governed-merges.mjs, scripts/pm/check-governed-queue-guard.mjs, scripts/pm/check-settings-deny-roster.mjs (6); scripts/pm/dispatch-gates.mjs and package.json: 0 hits",
      "grep_step4": "grep -rn -iE 'enqueue[ -]guard|governed[ -]enqueue|guard.governed.enqueue|GOVERNED_ENQUEUE|enqueue hook|seat-side hook|PreToolUse|client-side|客户端|钩子' .claude/skills .claude/agents AGENTS.md CLAUDE.md docs/adr = 0 lines describing the hook as a live control (AGENTS.md:221/:323 and os-dev.md:27 PreToolUse = worktree and stash guards; platform-readings.md:395 = the CI context Governed Surface Queue Guard, server-side; check-half-states.mjs 9377/9557/29094 'governed enqueue' = the queue guard's refusal)",
      "deviations": "1) dispatch's Writes list omitted the labels POST; the standard clauses require it and the dispatch's PR section requires skip-changeset, so POST /issues/18478/labels was made (1 write) and read back. 2) origin/main moved during the run (8fe5cb8e to 85c6d76e); merged it (afd9fcb2, clean, no regen debt) and re-ran the moved families on the merged head. 3) Second half of the gate sweep exceeded the foreground cap and was backgrounded by the harness; its per-command exit codes were still captured to file and read from there.",
      "mcp_calls": "0 — no MCP GitHub tools called; all GitHub reads and writes via REST curl with GITHUB_TOKEN",
      "api_writes": "4 — git push ×3 on the branch (empty-branch probe, 9784cd23, afd9fcb2); POST /repos/objectstack-ai/objectstack/pulls ×1 (draft #18478); POST /repos/objectstack-ai/objectstack/issues/18478/labels ×1 (skip-changeset, read back present); POST /repos/objectstack-ai/objectstack/issues/18470/comments ×1 (this report); PATCH /pulls ×0",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: scripts/pm/check-half-states.mjs lines 9377/9557/29094 say 'governed enqueue' about the queue guard's refusal — still true after the removal, no edit; 承接者:无"
      ]
    }

    Generated by Claude Code

  4. hotlong commented on Sep 16, 2026

    @hotlong
    ContributorAuthor

    ACCEPT — director seat, session_01Wj1HUjzyeiBQ8atRf1ZhaL, served tier claude-fable-5-1 · 2026-09-16T15:03Z

    Verified against GitHub (PR #18478, head afd9fcb2, draft, 6 files +24/−1288), ⛔ not against the report:

    • Scope = ruling B, nothing wider. Deleted .claude/hooks/guard-governed-enqueue.sh and .selftest.sh. .claude/settings.json −13: the standalone PreToolUse matcher mcp__github__enable_pr_auto_merge|mcp__github__merge_pull_request (whose only command was this hook) and the hook's entry in the Bash matcher — hook commands 7 → 5 (guard-main-checkout, guard-shared-stash, guard-process-kill, guard-main-checkout-bash, guard-tree-enum remain, in order). permissions incl. the 17-entry deny roster byte-identical to origin/main (compared as parsed JSON); every other top-level key identical.
    • scripts/pm/check-governed-merges.mjs (2 comment sites), check-governed-queue-guard.mjs (docblock only; the merge_group refusal logic untouched), check-settings-deny-roster.mjs (header) re-keyed to say the client hook is gone and the roster + queue guard are the line — read whole; no assertion weakened, no exemption added.
    • Prose grep for the hook as a live control across .claude/skills, .claude/agents, AGENTS.md, CLAUDE.md, docs/adr = 0 (the remaining PreToolUse mentions are the worktree/stash guards; platform-readings.md:395 names the server-side CI context) — accepted as the reading; no SKILL.md line changed ([Decision] check-harness-current's prescription is all-or-nothing — one reworked hook forced every live PM session to re-seat three times in nine hours; narrow it by which path the harness actually loads at start #18468's :92 and skills(pm-dispatch): flip the gate asymmetry — adding a required gate / hook / ratchet joins the human floor; retiring a zero-catch, no-contract gate becomes class-1 (maintainer ruling F) #18473's floor lines untouched).
    • Gates: 40 derived / 40 run / 0 unrun (--ran, DERIVED zero), all exit 0 after one prerequisite build; check:pm-settings-deny-roster 36 cases + 17 declared = enforced; check-governed-queue-guard --self-test 238; hook self-tests 5/5 (lint.yml discovers them by find, now 5); mcp_calls 0; api_writes 4 REST + 3 pushes, read back; skip-changeset read back; main moved mid-run and was merged (85c6d76e), moved families re-run on the merged head.
    • CI on afd9fcb2 at review: 17 success · 11 skipped · 5 in progress · 0 failed.
    • Deviation answered: the labels POST is the dev file's standard budget — accepted.

    Landing (governed, ruling C): four-piece hung on PR #18478 — this record, 维护者速读(终稿), needs-user-decision, review requested from an approver account; draft until an authorized APPROVED, then the seat flips ready and arms auto-merge via the ccr routes. Card stays pm:dispatched until MERGED. Sequencing: independent of PR #18476 (disjoint files); land whichever is approved first.

    Implemented-by: claude/issue-18470-remove-enqueue-guard-hook (mode:subagent)
    Reviewed-by: session_01Wj1HUjzyeiBQ8atRf1ZhaL


    Generated by Claude Code

  5. removed their assignment
    on Sep 16, 2026
  6. hotlong commented on Sep 16, 2026

    @hotlong
    ContributorAuthor

    Landed — director seat, session_01Wj1HUjzyeiBQ8atRf1ZhaL · 2026-09-16T15:58Z

    PR #18478 (head afd9fcb2) merged by the queue as 674872a5b7 at 2026-09-16T15:57:59Z (merged_by os-zhuang, the approver who flipped ready and enqueued); git log origin/main carries (#18478). Verified on origin/main: .claude/hooks/ now lists ten files — the five remaining guards and their self-tests — and guard-governed-enqueue.* is gone; the settings.json deny roster is unchanged. Governed landing by ruling C: APPROVED 5224805455 on that head, in-seat ACCEPT (stamped 2026-09-16T15:03Z), provenance on the PR. Ruling B on PR #18447 is executed in full. Same act: pm:dispatched removed and the assignee cleared on this auto-closed card; domain:skills stays.


    Generated by Claude Code

  7. added a commit that references this issue on Sep 17, 2026
    674872a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions