Skip to content

[finding] guard-governed-enqueue's slug reader keeps a .git suffix — a sibling cloned with the conventional URL never resolves, and a bare gh pr merge N on such a clone derives a slug the API 404s on, so the guard fails OPEN #18366

Description

@os-zhuang

Filed by the domain:skills execution PM seat, session session_01HZfg2AwVX191qCizp88gQr, at 2026-09-16T04:27Z, from the #18321 dev's out_of_scope_findings (report 5691990182 on #18321; PR #18365), measured by that dev on its head and read by the seat.

Reading

.claude/hooks/guard-governed-enqueue.sh's slug_of reads a checkout's origin with sed -n 's#.*github\.com[:/]\([A-Za-z0-9._-]*/[A-Za-z0-9._-]*\)\(\.git\)\{0,1\}/*$#\1#p'. The path character class owns . and is greedy, so the optional \(\.git\) group matches EMPTY and https://github.com/objectstack-ai/cloud.git yields objectstack-ai/cloud.git — a slug that equals no owner/repo the guard is ever asked about. Measured on PR #18365's head: a throwaway sibling whose origin carries the .git suffix answers allow where the identical tree with the bare URL answers block. The real /home/user/cloud happens to carry the bare form, which is the only reason the #18321 reproduction worked at all; PR #18365's new self-test case uses the bare URL deliberately and says so in place.

The SAME expression is inlined a second time in the hook's segment_target to derive the slug for a bare gh pr merge NUMBER (no -R): a non-matching slug makes the API read 404 and the guard FAILS OPEN — on any clone made with the conventional .git URL, that spelling of the merge command is unguarded.

What is asked

Single-source the slug reader (one function, both call sites), make it strip a trailing .git before the path class can swallow it (or anchor the group so it cannot match empty), and pin both shapes in guard-governed-enqueue.selftest.sh: a sibling with the .git remote resolves (and is audited), and a bare gh pr merge N on a .git-URL clone derives the right slug (the guard reads the PR instead of 404-ing open). .claude/hooks/guard-governed-enqueue.sh + its selftest only; rules layer (.claude/hooks/**) ⇒ draft PR, four-piece + an authorized APPROVED; ⛔ never armed by the seat.

Grading (lane self-triage; class (a), a guard that fails open on the conventional clone URL): p2 · Bug · pm:queue · domain:skills. SERIAL behind PR #18365 (the same two files; its injection is what makes the new cases hermetic).

查重词

guard-governed-enqueue slug_of · .git suffix origin url · sibling checkout not resolved · gh pr merge slug derivation · fail open 404


Generated by Claude Code

Activity

  1. self-assigned this
    on Sep 16, 2026
  2. os-elon-musk commented on Sep 16, 2026

    @os-elon-musk
    Collaborator

    Claim: PM loop round 1
    Session: session_01Bz6hxDBqK62NP2W1LATvnt (skills seat; claimed at 2026-09-16T07:43Z)
    Branch: claude/issue-18366-governed-enqueue-slug-git-suffix
    Worktree: objectstack-issue-18366
    Domain: domain:skills
    File surface: .claude/hooks/guard-governed-enqueue.sh (the slug reader single-sourced at both call sites — the slug_of function and the inlined copy in the bare gh pr merge NUMBER segment — and made to strip or anchor the trailing .git) + .claude/hooks/guard-governed-enqueue.selftest.sh (two new cases: a sibling with the .git remote resolves and is audited; a bare gh pr merge NUMBER on a .git-URL clone derives the right slug) — nothing else (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: default tier (TIER_DEFAULT) — dispatch-gates.mjs --tier at 2026-09-16T07:29Z derives no path mandate for .claude/hooks/** (floor sonnet · default opus · ceiling fable); a bounded bash fix with one design choice (strip vs anchor) and two self-test cases ⇒ the default judgment tier, not the floor; seat review in-seat at CONTRACT_REVIEW_TIER
    Clause-②: no
    Thread-read: none (no comment on this card at claim time; the grading is the body's own 「Grading」 line)
    Fold-or-serial: NOT FOLDED with #18367 — gate ① fails (a slug-reader defect and an unpinned branch's missing case are different defect shapes with different fixes); #18367 is SERIAL behind this card's PR on the same selftest file; #18384 (finding, graded this round) touches the same hook and queues behind both. Rules layer (.claude/**) ⇒ draft PR, four-piece + an authorized APPROVED; ⛔ never armed by this seat.
    Serial constraints cleared: PR #18365 (the same two files) LANDED 8b81ab60 at 2026-09-16T07:05Z; none of the six open PRs touches either hook file (pulls/N/files read at 2026-09-16T07:41Z); no remote branch claude/issue-18366-* (ls-remote at 2026-09-16T07:28Z, 255 branches, control issue-17359 hit); verify lock free, queue empty at 2026-09-16T07:28Z; devs in flight after this claim 1/3


    Generated by Claude Code

  3. os-elon-musk commented on Sep 16, 2026

    @os-elon-musk
    Collaborator

    os-dev-report

    {
      "issue": 18366,
      "status": "done",
      "branch": "claude/issue-18366-governed-enqueue-slug-git-suffix",
      "worktree": "/home/user/objectstack-issue-18366",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/18390",
      "head_sha": "1696da61",
      "base": "8b81ab60d7b02e5540e8a36917cc86478bfbf899",
      "session": "session_01Bz6hxDBqK62NP2W1LATvnt (subagent; the dispatching seat's id)",
      "premise_still_valid": true,
      "claim_check": "the newest Claim: on #18366 (comment 5693845992, os-elon-musk) names claude/issue-18366-governed-enqueue-slug-git-suffix — it matches this branch. Assignee left untouched (os-elon-musk, set by the PM).",
      "files_changed": [
        ".claude/hooks/guard-governed-enqueue.sh — +28 / -4",
        ".claude/hooks/guard-governed-enqueue.selftest.sh — +82 / -2"
      ],
      "summary": "slug_of and the inlined copy of the same sed in segment_target's bare `gh pr merge N` target derivation are now ONE function called from both sites, and it strips a trailing .git after the match (the path character class owns the dot and is greedy, so the optional group matched empty). PM mechanism assumption 1 held: the two occurrences were byte-identical and content-located, not line-located. The strip route was chosen over anchoring the group because the class still has to own the dot (objectstack.ai is a legitimate repo name) and because GNU and BSD sed are free to settle the class-vs-group race differently, while ${slug%.git} is the same in bash 3.2 everywhere. The self-test pins both shapes the card asks for: four previously unpinned origin spellings of the sibling (https/ssh x with and without .git, plus a trailing slash) resolve and are audited as AGREEMENT with the register, and a bare `gh pr merge N` run from a checkout whose origin carries the suffix derives objectstack-ai/objectstack#13794.",
      "tests": "SELF-TEST BASELINE on origin/main 8b81ab60 (bash .claude/hooks/guard-governed-enqueue.selftest.sh): '56 passed, 0 failed', exit 0 — measured twice, before pnpm install and after (the regen branch flips from 'NOT lifted' to 'LIFTED' between them; the totals do not). PRE-FIX REPRODUCTION, the card's own two halves: the same fixture and payload with a sibling whose origin is https://github.com/objectstack-ai/cloud answers exit 2 (audited) and with .../cloud.git answers exit 0 (never resolved); a bare `gh pr merge 13794` run from a checkout whose origin carries .git printed 'Blocked: objectstack-ai/objectstack.git#13794' — the slug it would have put in the API URL. AFTER: '62 passed, 0 failed', exit 0 (six new rows). Identical under all three OS_GOVERNED_ENQUEUE_SIBLING_ROOT states (unset / an empty dir / a dir that does not exist): 62/0 each. Also 62/0 in a second worktree of this branch with NO node_modules, which is the file header's own claim about itself. ABLATION 1 (the pre-fix reader restored: old sed, no suffix strip): '57 passed, 5 failed', selftest exit 1 — FAIL rows: the two `gh pr merge N` text rows ('missing objectstack-ai/objectstack#13794', 'still says objectstack.git') and the three .git spelling rows (https .git, ssh .git, trailing slash), each want=block got=allow. ABLATION 2 (a colon dropped from the separator character class): '60 passed, 2 failed', exit 1 — exactly the two git@github.com: rows. Every one of the six new rows is red under one of the two controls; none is a phantom. ABLATION MECHANICS, both runs: absolute REPO_ROOT, a trap on EXIT INT TERM calling the restore function, mutation proven on disk before reading any result (grep counts strip=1/oldsed=0 became strip=0/oldsed=1; blob 3c25d9cfbd6bc12eb8e8183ce1b9a9ab36f3ce66 became 13e739e3e326e49d3facf0420c45b4be7acca452, and a no-op mutation exits 1 as FAILURE), restore by `git checkout HEAD -- path` and proven by blob-hash equality with the HEAD blob PLUS an empty `git diff HEAD` (3c25d9cfbd6bc12eb8e8183ce1b9a9ab36f3ce66 both times). Both controls ran from the COMMITTED state. No build or dist is involved (two bash scripts), so the dist preflight does not apply. Working tree clean after both; no temporary file left in the repo.",
      "gates": [
        "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",
        "pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 3 first (PREREQUISITE NOT MET — unbuilt @objectstack/formula and @objectstack/lint, 'Nothing was measured'), then exit 0 after `pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint --concurrency=2` under os-verify-lock.sh (VERDICT command-exit 0, waited 18s, held 169s, slot issue-18366)",
        "pnpm check:agent-test-spelling :: exit 0",
        "pnpm check:bash32-floor :: exit 0",
        "pnpm check:doc-authoring :: exit 0",
        "pnpm check:driver-memory-census :: exit 0",
        "pnpm check:nul-bytes :: exit 0",
        "pnpm check:pm-governed-merges :: exit 0",
        "pnpm check:refd-timer-probe :: exit 0",
        "pnpm check:skill-frame-sync :: exit 0",
        "pnpm check:watch-hint-literal :: exit 0",
        "RECONCILIATION — node scripts/pm/dispatch-gates.mjs --ran FILE --repo objectstack-ai/objectstack :: exit 0, '13 derived, 13 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero — all 13 recorded an exit code and none of them is 3)'. The derivation (--commands, no paths, --repo asserted and held) named the same 13 the dispatch did; nothing new appeared for the real diff.",
        "EXTRAS — bash -n on both files :: exit 0 / exit 0; the selftest under three injection states :: 0 / 0 / 0 (62 passed each); node scripts/pm/check-governed-merges.mjs --test .claude/hooks/guard-governed-enqueue.sh :: exit 3 (GOVERNED, as expected for this surface); node scripts/pm/check-clause2-carriers.mjs --pair 18390 :: exit 0 ('the clause-② declaration is readable in the fixed spelling and both carriers agree, and its diff carries no widening tell'); pnpm check:pm-settings-deny-roster :: exit 0 — run deliberately because the derivation marks its roster as living under .claude, where silence is evidence in neither direction; grep -naP for control bytes over both files :: no hit.",
        "NOT MEASURED — shellcheck: not present in this tree (nothing in package.json invokes it, none on PATH). `bash -n` on both files plus the 62-case matrix is what stands in for it; CI runs no shellcheck on these paths either.",
        "NOT MEASURED — CI on PR #18390: not awaited, per the dispatch (report at the end of local verification; CI convergence is the seat's read). No repo-wide scan (pnpm lint) was run: that is CI's run, and the two paths are .sh files eslint does not read."
      ],
      "labels": "skip-changeset applied by add-only POST /repos/objectstack-ai/objectstack/issues/18390/labels (HTTP 200) and read back: label set before = [], after = ['skip-changeset'] = union(read, target). needs:contract-review is NOT on the PR — neither applied nor removed by me, as dispatched. No changeset file: .claude/** publishes nothing from any released package (fast-track path, not a grep measurement).",
      "deviations": [
        "The bare `gh pr merge N` case asserts NO allow/block verdict, only the derived slug in the refusal text. Measured reason: OS_GOVERNED_ENQUEUE_FIXTURE answers every path, so the 404 that makes the real guard fail open cannot be reproduced without the network — under the fixture a wrong slug blocks exactly like a right one and a verdict row would be a phantom. The text rows do go red under ablation 1.",
        "That case runs a COPY of the hook (symlinked, with scripts/ symlinked too) from a throwaway checkout it builds, because the hook derives its repo root from BASH_SOURCE and nothing injects it. The selftest's run/stderr_of helpers now read $hook_under_test (defaulting to $hook), set for those two rows and restored on the next line.",
        "The two git@github.com: rows pass `-u GIT_CONFIG_COUNT -u GIT_CONFIG_PARAMETERS`. Measured: this container injects url.https://github.com/.insteadOf git@github.com: through GIT_CONFIG_KEY_n/VALUE_n, and `git remote get-url` honours it, so without the unset the ssh rows are silent duplicates of the https ones — they were, on the first revision, and stayed green under ablation 2 until the unset was added, which is how it was found. This is the #18365 lesson applied: the rows own their premise instead of inheriting the box's git config.",
        "The four spelling rows reuse $sibling_want from the neighbouring resolved-sibling case instead of asking the register four more times: every tree in the loop is built exactly as that one is (git init plus an origin), so the question and the content are identical — the verdict still comes from the register, not from a word copied into the file.",
        "The dead optional .git group was removed from the sed rather than left in place beside the strip; a group that can only ever match empty reads as a second, contradictory reader of the same suffix."
      ],
      "mcp_calls": "0 — no MCP GitHub tool was called, read or write. Every GitHub read and write went through the REST proxy with curl.",
      "api_writes": "3 REST writes: POST /repos/objectstack-ai/objectstack/pulls (HTTP 201, draft true, base main) · POST /repos/objectstack-ai/objectstack/issues/18390/labels (HTTP 200, skip-changeset) · POST /repos/objectstack-ai/objectstack/issues/18366/comments (this report). Plus git push on claude/issue-18366-governed-enqueue-slug-git-suffix three times (the empty-branch routing probe, then one per commit). No PATCH of any body; the PR body was final at creation and read back byte-identical (6394 sent / 6393 stored, one footer, no angle-bracket mutation).",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, already filed: #18367 — the hermetic case for the fail-open branch. Not touched here; it remains open and serial behind this PR on the same selftest file.",
        "noted, already filed: #18384 — the ccr auto_merge route arm on the same hook. Not touched here; it remains open.",
        "noted, not filed: the hook's repo_root is not injectable the way OS_GOVERNED_ENQUEUE_SIBLING_ROOT made the sibling root injectable, which is why the new bare-`gh pr merge N` case has to build a checkout and run a copy of the hook out of it. Class: none of the three (not a reproducible defect — the hook behaves correctly; not a declared-contract breach; not an AI-metadata trap) — a test-shape cost only. Who would hit it: #18367's dev, the next PR on this same selftest file, which is the PR that would carry it if the copy-and-symlink shape ever stops paying for itself.",
        "noted, not filed: this container rewrites git@github.com: to https:// for every git command through GIT_CONFIG_KEY_n/VALUE_n injection, so any test anywhere in this repo that asserts on an ssh remote URL is silently asserting on an https one. Class: none of the three (a property of the box, not of the tree). Who would hit it: any dev writing a git-remote fixture here; the one instance I could see is now pinned in place. No repo-wide sweep was run for other instances — that would be a second card's work."
      ]
    }

    Generated by Claude Code

  4. os-elon-musk commented on Sep 16, 2026

    @os-elon-musk
    Collaborator

    ACCEPT — skills seat, session session_01Bz6hxDBqK62NP2W1LATvnt, 2026-09-16T08:23Z. Report 5694306324 on PR #18390 (head 1696da61) reviewed by the checklist on GitHub readings and this seat's own re-runs: draft, base main, Fixes #18366 alone; changed files = the two hook files exactly (+110 / −6), the claim's surface; skip-changeset read back; mcp_calls 0; api_writes 3, budget met; path face GOVERNED (rules layer); --pair 18390 0; the diff read whole — one slug_of at both sites, the suffix stripped after the match, the dead optional group gone; selftest re-run by this seat 62 passed, 0 failed on the head and 56 / 0 on main; gates 13 / 13 / 0 by --ran; two declared deviations accepted with their measured reasons (the slug-text rows in place of a verdict the fixture cannot discriminate; the ssh rows unsetting the container's insteadOf injection); shellcheck NOT MEASURED (not wired). CI on the head: Lint & Repo Gates and Type Check · workspace in progress at the reading. Contract-review record + 速读终稿 on the PR; needs-user-decision on the PR; reviewers os-zhuang and hotlong requested. The card stays pm:dispatched until ruling C after an authorized APPROVED. Serial chain: #18367 (the fail-open case) and #18384 (the ccr arm) follow on the same files.


    Generated by Claude Code

  5. os-elon-musk commented on Sep 16, 2026

    @os-elon-musk
    Collaborator

    Landed — skills seat, session session_01Bz6hxDBqK62NP2W1LATvnt, 2026-09-16T09:36Z. PR #18390 (head 1696da61, .claude/hooks/guard-governed-enqueue.sh + .selftest.sh — rules layer) merged by the queue as 40eaf755be01410d4c0a9c61c3c7ea95981c542c (single-parent squash per git rev-list --parents, parent 7c8d6d98) at 2026-09-16T09:35Z — the merged_at instant. Readings: git log origin/main carries (#18390) at the tip; git log origin/main -- .claude/hooks/guard-governed-enqueue.sh names it as the file's latest touch and the hook's header now documents the .git-suffix slug reading it strips; the queue ref refs/heads/gh-readonly-queue/main/pr-18390-* is gone from origin. Governed landing by ruling C with the maintainer's own hand on the last three steps: four-piece at 2026-09-16T08:23Z (record + 速读终稿 5694395229, ACCEPT 5694396286, needs-user-decision on the PR, reviewers requested); os-zhuang APPROVED at 2026-09-16T09:14Z (review 5220756379, an authorized GOVERNED_APPROVERS account), then turned the PR ready, enabled auto-merge and it was added_to_merge_queue at 2026-09-16T09:14Z; this seat's part was clearing needs-user-decision on the PR at 2026-09-16T09:17Z. Fixes #18366 closed this card at the merge; residue (pm:dispatched) stripped through label-write.mjs and read back. Serial unlock: #18367 (the fail-open hermetic case) and #18384 (p2, the ccr enqueue spelling) on the same hook are now free — next in this lane's total order once a dev slot opens (3 / 3 in flight at this record).


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions