Skip to content

[finding] guard-governed-enqueue.sh reads only 'filename' from the changed-files API — a PR that renames a governed file OFF the governed surface reads as NOT governed #17503

Description

@os-litant

Filed unassigned by the domain:skills execution seat while landing #17003 (session_01YKEjmbYNvYWJvWGSWx26zK, GitHub os-litant), mechanizable-item class. Unassigned, no domain:*, no priority — routing and grading are the triage seat's.

The measurement

.claude/hooks/guard-governed-enqueue.sh is the pre-enqueue governance guard. It reads a PR's changed files from the API and feeds them to check-governed-merges.mjs --test --json. Its reader takes exactly one field per entry (OS_GUARD_MODE=filenames, around line 403):

else if (mode === "filenames") { if (!Array.isArray(d)) process.exit(1); for (const f of d) if (f && f.filename) console.log(f.filename); }

f.previous_filename is never read. Measured on GET /repos/objectstack-ai/objectstack/pulls/17372/files (2026-09-10): 12 of the 100 entries on page 1 carry status: renamed with previous_filename naming the OLD path, e.g. packages/spec/src/cloud/marketplace.zod.ts moving to packages/spec/src/marketplace/marketplace.zod.ts. The old path is data the endpoint hands out and this reader drops.

Why it is worth a card

The register carries two EXACT repo-root entries, AGENTS.md and CLAUDE.md. A rename is the one edit that moves such a path off the register entirely:

$ node scripts/pm/check-governed-merges.mjs --test docs/AGENTS.md
governed-surface predicate: 0 of 1 path(s) hit the register — NOT governed   (exit 0)

$ node scripts/pm/check-governed-merges.mjs --test docs/AGENTS.md AGENTS.md
governed-surface predicate: 1 of 2 path(s) hit the register — GOVERNED       (exit 3)

So a PR whose diff renames AGENTS.md to anything outside the register reaches the guard as a one-path list the register does not cover, the guard's case 0) exit 0 branch allows it, and a diff that edits a governed surface is queueable without the maintainer's hand. The prefix surfaces have the same shape wherever a rename crosses their boundary (skills/x.md moving to docs/x.md).

The direction is the unsafe one: a dropped path can only REMOVE governance, never add it. That is the opposite of #17003's superset, which could only add.

The fix is one field. The remedy already exists as a reading to copy: #17003's --pr derivation in check-governed-merges.mjs reads filename and previous_filename and pins the case, on the argument that a rename OUT of a governed path is still a change to that path.

Dedup

The open domain:skills lane read whole over REST on 2026-09-10 (GET /repos/objectstack-ai/objectstack/issues?state=open&labels=domain:skills&per_page=100, 49 cards, no pagination), title and body scanned locally for previous_filename, renam, guard-governed-enqueue: zero hits. Positive control in the same read: three-dot hits #17003, so the channel is a reading and not a silent zero. Nearest neighbour is #17003 itself, which is about the file list being a SUPERSET before the tool runs; this is the same authority being fed a SUBSET, in the direction that can hide a hit.

Not measured

Whether the seat's own landing pre-check has the same hole. SKILL.md :605-606 takes the path face through MCP get_files; whether that wrapper surfaces previous_filename was not measured. Nothing is asserted about it here.

Whether any PR has actually been queued this way. This is a code reading plus an API reading, not an incident.

Landing surface

.claude/hooks/guard-governed-enqueue.sh — a governed surface, so authoring is open to any seat and the landing is the maintainer's.


Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions