Skip to content

Commit 6df14c0

Browse files
ptr727claude
andauthored
Record the Cluster for Documenting a Cross-Owner Write Grant (#629)
**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 `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`: | Document | What 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.md` | Names 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.md` | No 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](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 92b9fc5 commit 6df14c0

1 file changed

Lines changed: 18 additions & 0 deletions

File tree

TODO.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,21 @@ The review loop ends by replying on a thread and resolving it, and both halves f
318318
- **Checked** - `develop` at `0e4a1c2` on 2026-08-08, reading the exit-code table in the `scripts/pr_review.py` module docstring.
319319
- **Detail** - This is the failure the suppressed-findings count already exists for, where a step that stopped running reads exactly like a step that passed.
320320

321+
### The Grant That Unblocks a Cross-Owner Write
322+
323+
One pull request documenting how a maintainer grants a write target the guard denies. 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 grant in `GH_WRITE_GUARD_ALLOW`, which no document tells a reader how to give.
324+
325+
**State** `ready`. **Touches** [`docs/host-setup.md`][host-setup] and [`host-setup/agent-safety/README.md`][write-guard-readme]. **Cost** one pull request, prose only, and hub-only, since the kit is host state rather than carried repo content.
326+
327+
- **Document the grant where a reader already is when the denial arrives, which is the install section rather than the hook's description.** A denial names the variable, and the two documents that could explain it either never mention it or mention it while describing what the hook refuses, so the reader is told a grant exists and never how to make one.
328+
- **Blocked by** - Nothing.
329+
- **Issue** - None filed.
330+
- **Checked** - `develop` at `92b9fc5` on 2026-08-08, where "Agent Write-Safety Kit" in [`docs/host-setup.md`][host-setup] never names the variable at all, [`host-setup/agent-safety/README.md`][write-guard-readme] names it once inside the bullet listing what the hook denies, giving the token format and not how to set it, and [`README.md`][readme] carries no host-protection section, only a pointer to the first.
331+
- **Detail** - The channel that works is an `env` block in the checkout's `.claude/settings.local.json`, holding `GH_WRITE_GUARD_ALLOW` set to the target. It is per project rather than host-wide, which is the property worth stating, since a grant made for one checkout does not follow the agent into another repository's sessions.
332+
- **Detail** - The case that raised it is a fork, where `origin` is under the maintainer's own owner and `upstream` is the project it forked from. Filing an issue or a 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 and the denial appears only on the half that leaves the owner. That asymmetry is the part a reader hits first and the reason a worked example beats a definition here.
333+
- **Detail** - The two forms a reader reaches for first both fail, silently in the sense that the write simply stays denied: 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. The behavior is settled and tested, since [`gh-write-guard.py`][write-guard] already asserts the inline prefix denies, so what is missing is only the explanation.
334+
- **Detail** - Two things worth carrying beside the example: that a session restart is what 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.
335+
321336
## Standalone Chores
322337

323338
Small work with no research to preserve, selectable one bullet at a time.
@@ -468,12 +483,14 @@ Each was checked against the tree and has nothing left to do anywhere. Closing i
468483
[divergences-report]: ./reports/divergences.md
469484
[files]: ./spec/files.json
470485
[governance]: ./GOVERNANCE.md
486+
[host-setup]: ./docs/host-setup.md
471487
[markdownlint]: ./.markdownlint-cli2.jsonc
472488
[matrix]: ./reports/conformance-matrix.md
473489
[merge-bot]: ./.github/workflows/merge-bot-pull-request.yml
474490
[operations]: ./OPERATIONS.md
475491
[project-types]: ./spec/project-types.json
476492
[prose-gate]: ./.github/actions/prose-gate/action.yml
493+
[readme]: ./README.md
477494
[readme-structure]: ./spec/readme-structure.md
478495
[reports]: ./reports/
479496
[repos]: ./registry/repos.json
@@ -487,3 +504,4 @@ Each was checked against the tree and has nothing left to do anywhere. Closing i
487504
[workflow]: ./WORKFLOW.md
488505
[workflows]: ./catalog/snippets/workflows/
489506
[write-guard]: ./host-setup/agent-safety/gh-write-guard.py
507+
[write-guard-readme]: ./host-setup/agent-safety/README.md

0 commit comments

Comments
 (0)