Record the Cluster for Documenting a Cross-Owner Write Grant - #629
Merged
Conversation
The write guard denies a `gh` write whose target is under an owner other than the checkout origin's, and the only way past it is a maintainer grant in `GH_WRITE_GUARD_ALLOW`. Nothing tells a reader how to give one. Read against develop at 92b9fc5, "Agent Write-Safety Kit" in docs/host-setup.md never names the variable, the kit's own README names it once inside the bullet listing what the hook denies and gives the token format without the mechanism, and README.md has no host-protection section at all, only a pointer to the first. The entry carries what a session had to work out by hand: the channel is an `env` block in the checkout's `.claude/settings.local.json`, which is per project rather than host wide, and the two forms a reader reaches for first, an inline variable prefix and an `export` in a shell call, cannot work, because the hook is its own process and reads only the environment the session was launched with. The raising case is a fork, where writes aimed at the fork are in scope and only the ones aimed at its upstream are not, so the grant names the upstream alone. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a new Work Clusters entry to TODO.md capturing the documentation gap and planned work for explaining how maintainers can grant cross-owner GitHub CLI write targets via GH_WRITE_GUARD_ALLOW.
Changes:
- Add a new cluster, “The Grant That Unblocks a Cross-Owner Write,” documenting the problem statement, checked evidence, and the intended documentation changes.
- Add new reference-style link definitions used by the entry.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Uh oh!
There was an error while loading. Please reload this page.
The new cluster wrote "host wide" unhyphenated. Every other occurrence across the Markdown in the tree is "host-wide", five of them, so the unhyphenated one was the outlier rather than a second convention. The neighboring "per project" stays unhyphenated, which is the majority form in the same corpus at five against two. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
ptr727 added a commit
that referenced
this pull request
Aug 8, 2026
Closes#578. Promotion of `develop` at `6df14c0`, three squashes since the last one. Merge commit only, no squash and no rebase, and **no `--delete-branch`**, since this pull request's head is `develop` itself. ## What is being promoted | Squash | Change | | --- | --- | | [`fdd1e25`](fdd1e25) (#627) | Widens three carried `GOVERNANCE.md` rules to state the recurring case, which is [#578](#578), plus the `WORKFLOW.md` cross-reference the same change took. | | [`92b9fc5`](92b9fc5) (#628) | Corrects the Copilot runbook to read the reviewer bot's node id repo-wide rather than from the pull request under review, and records the disproof of the claim raised against it. | | [`6df14c0`](6df14c0) (#629) | Records the backlog cluster for documenting a cross-owner write grant. | Four files, `.github/copilot-instructions.md`, `GOVERNANCE.md`, `TODO.md` and `WORKFLOW.md`, at 49 insertions against 73 deletions. ## The closing keyword `Closes#578` is on this pull request rather than on #627, because GitHub fires a closing keyword only when it merges into the default branch, and #627 merged into `develop`. Merging this closes that issue. The work itself landed in `fdd1e25`. ## What this owes downstream Two of the three carry a rule rather than a hash refresh, so a repository left on the old copy is wrong on the rule and not only on the bytes. Both are recorded in `TODO.md` under "Re-vendor the changed `verbatim` content": - The [#578](#578) widening touches three `verbatim` `GOVERNANCE.md` sections, and "Operational Repositories" matters most on the two `operational` repositories that can act on it. - The runbook correction is `intent` fidelity, so **no audit reports it**. A repository on the old copy reads its own runbook as requiring a review on the pull request before the reviewer bot's node id can be read, and hands round 1 back to the maintainer whenever auto-review-on-open does not fire. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
ptr727 added a commit
that referenced
this pull request
Aug 8, 2026
…631) The README opened straight into prose written for an agent, so a human arriving from GitHub had no path to what they actually ask: how to use this repo, how to deploy the host rules, how to diverge from one, and where to file an issue. It now forks by reader, and it carries the write-grant documentation that `TODO.md` recorded as ready in #629 without doing. ## The README, routed by reader A **Getting Started** table above the Table of Contents states the human/agent split outright, an agent starting at `AGENTS.md` and a human starting at the table, then routes six reader shapes to one destination each. Three new sections fill the gaps, placed in the slots [`spec/readme-structure.md`](./spec/readme-structure.md) already orders them in so the redesign does not fight the hub's own spec: - **Using This Repo** (Installation slot), covering the host guardrails, carrying the rules into a repository, and an honest note that the registry does not transfer, so adopting outside this fleet means running your own hub. - **Diverging From a Rule** (Configuration slot), which separates the two kinds of exception that get confused: a repository diverging from a carried unit, where the fidelity level usually already grants the freedom and the rest is recorded in `spec/divergences.json`, and a write the host guard denies. - **Questions or Issues**, previously absent entirely, saying what an issue needs and that a downstream agent filing here is the normal path rather than an exception. ## The cross-owner write grant The guard denies a `gh` write whose explicit target sits under another owner and names `GH_WRITE_GUARD_ALLOW` in the denial, and no document said how to give one. [`docs/host-setup.md`](./docs/host-setup.md) now carries it where the reader already is when the denial arrives: - The **fork asymmetry** that raises it, where `origin` is your fork and only the half of the work aimed at `upstream` denies. - The channel that works, an `env` block in the checkout's `.claude/settings.local.json`, which scopes the grant to that checkout rather than the host, and the **session restart** that loads it. - The value's exact shape: one string and never a JSON array, split on any run of whitespace or commas, with `owner/*` granting a whole owner and a token carrying no `/` silently granting nothing. Verified against `_granted_targets` and `_target_permitted`. - The **two forms that fail silently**, an inline `VAR=x gh ...` prefix and an `export` in a shell call, neither of which reaches a hook running as its own process. That is the property making a grant a deliberate act outside the session rather than something an agent does for itself once blocked. - A **confirmation that does not involve making the write**. Worth recording: feeding the hook a synthetic payload is not a usable probe from inside a session, because the payload text carries the very write shape the guard matches, so the guard denies the probe command itself. Observed twice while writing this. `host-setup/agent-safety/README.md` carries the mechanics beside the hook description and links across. ## Also here - The **license shield** moves into the closing License section, matching the form PlexCleaner carries. [`spec/readme-structure.md`](./spec/readme-structure.md) still states the old placement under Build and Distribution, so this README is deliberately ahead of the spec, and reconciling the two is the `decision`-state **README Structure Rework** cluster's edit rather than a loose end here. That cluster's `Settled` bullet asserting the old placement as fact is rewritten to record it. - **Proxmox** drops out of every supported-platform list, since it is Linux. The two remaining mentions name the actual Proxmox host in the rollout, which is correct. - `untriaged` joins the cspell word list, since the new Diverging section names the literal value the divergence report renders. - `TODO.md` loses the shipped cluster and its orphaned link definitions, and the stale "Refresh the README" chore becomes the open question this change leaves behind: whether these human entry points belong in the spec so fleet repos are measured on them. The per-section index alternative is recorded as declined, with the reason. ## Verification markdownlint, cspell, editorconfig-checker, `spec/validate.py`, and `scripts/prose_lint.py --diff develop` all clean locally. CRLF preserved in every Markdown file, LF preserved in `install.sh`. Every relative link target and every in-file anchor resolves, checked mechanically. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Disposition
New entry, as a new cluster under "Work Clusters". Nothing in the file reasons about the write-safety kit's documentation, so there was no entry to amend.No closing keyword, since a feature to
developpull request never fires one.The gap
The hook denies a
ghwrite whose explicit target is under an owner other than the checkout origin's, and the only way past it is a maintainer grant inGH_WRITE_GUARD_ALLOW. Read againstdevelopat92b9fc5:docs/host-setup.md"Agent Write-Safety Kit"host-setup/agent-safety/README.mdowner/repo,owner/*for a whole owner) and not how to set it.README.mddocs/host-setup.md.So a reader who hits a denial is told a grant exists and never how to make one.
What the entry carries
envblock in the checkout's.claude/settings.local.json. It is per project rather than host wide, so a grant made for one checkout does not follow the agent into another repository's sessions.originis under the maintainer's own owner andupstreamis the project it forked from. Filing an issue or pull request against the upstream is the cross-owner write and everything aimed at the fork is not, so the grant names the upstream alone. That asymmetry is what a reader meets first, and it is why the section wants a worked example rather than a definition.GH_WRITE_GUARD_ALLOW=owner/repo gh ...prefix, and anexportin a shell call. The hook runs as its own process and reads only the environment the session was launched with, which is exactly what makes a grant a deliberate act taken outside the session rather than something an agent can do for itself once blocked.gh-write-guard.pyalready asserts the inline prefix denies, so the behavior is settled and only the explanation is missing.This change records the work. It does not do it, so both documents are untouched here.
Verification
scripts/prose_lint.pyexit 0,markdownlint-cli20 issues,editorconfig-checkerexit 0,TODO.mdat 507 of 507 CRLF lines. The three new reference definitions sort by reference name,[host-setup]after[governance],[readme]before[readme-structure], and[write-guard-readme]after[write-guard].🤖 Generated with Claude Code