Skip to content

Record the Cluster for Documenting a Cross-Owner Write Grant - #629

Merged
ptr727 merged 2 commits into
developfrom
docs/document-write-guard-grant
Aug 8, 2026
Merged

Record the Cluster for Documenting a Cross-Owner Write Grant#629
ptr727 merged 2 commits into
developfrom
docs/document-write-guard-grant

Conversation

@ptr727

Copy link
Copy Markdown
Owner

DispositionNew 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 develop pull request never fires one.

The gap

The hook denies a gh write whose explicit 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. Read against develop at 92b9fc5:

DocumentWhat it says about the variable
docs/host-setup.md "Agent Write-Safety Kit"Nothing. This is the section a developer follows to install the kit.
host-setup/agent-safety/README.mdNames it once, inside the bullet describing what the hook denies. Gives the token format (owner/repo, owner/* for a whole owner) and not how to set it.
README.mdNo host-protection section, only a pointer to docs/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

  • The channel that works is an env block 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.
  • The raising case is a fork, where origin is under the maintainer's own owner and upstream is 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.
  • The two forms a reader reaches for first cannot work: an inline GH_WRITE_GUARD_ALLOW=owner/repo gh ... prefix, and an export in 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.py already asserts the inline prefix denies, so the behavior is settled and only the explanation is missing.
  • Two things to carry beside the example: that a session restart loads the grant, and a way to confirm one took, since inferring it from a write that no longer denies means learning the answer by making the write.

This change records the work. It does not do it, so both documents are untouched here.

Verification

scripts/prose_lint.py exit 0, markdownlint-cli2 0 issues, editorconfig-checker exit 0, TODO.md at 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

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>
CopilotAI lite review requested due to automatic review settings August 8, 2026 19:31

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment threadTODO.md Outdated
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>
CopilotAI review requested due to automatic review settings August 8, 2026 19:34

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@ptr727
ptr727 merged commit 6df14c0 into developAug 8, 2026
7 checks passed
@ptr727
ptr727 deleted the docs/document-write-guard-grant branch August 8, 2026 19:38
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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@ptr727