Skip to content

Record the two write-guard limits a live sweep exposed - #539

Merged
ptr727 merged 3 commits into
developfrom
write-guard-known-limits
Aug 3, 2026
Merged

Record the two write-guard limits a live sweep exposed#539
ptr727 merged 3 commits into
developfrom
write-guard-known-limits

Conversation

@ptr727

@ptr727 ptr727 commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Two limits of the gh-write-guard hook, both found while driving seventeen pull requests through the review loop in one session, and neither written down anywhere. They go in the kit's own "Scope and Limits" rather than the review runbook, since they are properties of the hook rather than of the Copilot workflow.

A write inside a script file is unseen

The hook reads the command the agent runs. For bash sweep.sh that is one string, so a git push or a gh mutation inside the script reaches the server without the hook ever parsing it.

This is the wider of the two and the one worth acting on. It is a hole an agent opens by accident: collecting fifteen repetitive pushes into a loop reads as tidiness, not as switching a guard off. I nearly did exactly that in this sweep, and stopped only because the reason to script it and the reason not to were the same fact.

The rule that follows is short: issue each write as its own command. The boundary is the write and not the script, so a script that reads, computes, or prepares local commits is unaffected, which is how the sweep's fifteen commits were actually prepared.

A git push followed by a newline over-blocks

The push argument list terminates at && but not at a newline, so every token on a later line of the same command is read as a refspec. Measured against the installed hook:

'git push -u origin revendor/x'
  -> [('update', 'revendor/x')]
'git push -u origin revendor/x\ngh pr create --base develop'
  -> [('update','revendor/x'), ('update','gh'), ('update','pr'), ('update','create'), ('update','develop')]
'git push -u origin revendor/x && gh pr create --base develop'
  -> [('update', 'revendor/x')]

The push then denies as a direct push to a protected branch it never targeted. The direction is safe, since it blocks rather than admits, but the denial tells the agent it is bypassing a branch rule with admin power when it is pushing an ordinary feature branch. A guard that cries wolf is one an agent starts working around, which is the actual risk. The fix is tracked in TODO.md; this records the limit and the workaround meanwhile, which is the same one-write-per-command rule as above.

Not added, because it is already there

The same session hit gh pr edit --body failing on this host's gh 2.46.0 with a Projects-classic deprecation error. That is already documented in the review runbook, including the exact gh api -X PATCH ... -F body=@file workaround, so nothing is added for it. Checked before writing rather than after, since duplicating a rule into a rules file is a known past failure here.

Verification

  • prose_lint.py: clean
  • markdownlint-cli2: 0 issues
  • Both behaviours measured against the installed hook, not inferred from reading it

Both were found while driving seventeen pull requests through the review
loop, and neither was written down.

A write inside a script file is unseen. The hook reads the command the agent
runs, so for a script invocation that is one string and every mutation inside
it reaches the server unparsed. This is the wider of the two, and it is one an
agent opens by accident: batching fifteen repetitive pushes into a loop looks
like tidiness rather than like switching a guard off. The boundary is the
write, not the script, so a script that only reads or prepares local commits
is unaffected.

A push followed by a newline over-blocks. The argument list ends at && and not
at a newline, so a later line's tokens are read as refspecs and the push
denies as a direct push to a protected branch it never targeted. Safe in
direction, but the denial names a bypass that was not attempted, and a guard
that cries wolf is one an agent starts working around.

The gh pr edit breakage found in the same session is already documented in
the review runbook, workaround included, so nothing is added for it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 3, 2026 18:05

Copilot AI 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

Documents two newly observed limitations of the gh-write-guard hook in the agent-safety host setup docs, so future sweeps and agent sessions avoid inadvertently bypassing or being misled by the guard.

Changes:

  • Add a limitation noting that writes executed inside a shell script are not visible to the hook, with a one-write-per-command workaround.
  • Add a limitation noting that a git push followed by a newline can be mis-parsed and over-blocked, with the same workaround.

Comment thread host-setup/agent-safety/README.md Outdated
The bullet read as though the shell carried newlines into the push argument
list and as though git denied the push. Neither is true: the hook splits the
command string itself, and its split ends an argument list at && but not at a
newline, so it is the hook that mis-parses and the hook that denies. Naming
the wrong actor in a limits section is worse than saying nothing, since a
reader debugging it would go looking at git.

Also drops the unexplained "hub" qualifier on TODO.md, which reads oddly in
the hub's own tree where TODO.md is simply TODO.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 3, 2026 18:17

Copilot AI 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.

Suppressed comments (1)

host-setup/agent-safety/README.md:74

  • In this bullet header, "never batch writes into one" is ambiguous (one what: one script, one command, one line). Since this section is documenting guard limits, tighten the wording to explicitly say "one script" (the actual limitation described).
- **A write inside a script file is unseen, so never batch writes into one.** The hook reads the command the agent runs, which for `bash deploy.sh` is that one string, so a `git push` or a `gh` mutation inside the script reaches the server without the hook ever parsing it. This is the widest hole in the kit and it is one an agent opens by accident, since collecting fifteen repetitive pushes into a loop looks like tidiness rather than like disabling a guard. Issue each write as its own command. A script that only reads, computes, or prepares local commits is fine, because the boundary is the write and not the script.

The bullet header read 'never batch writes into one', where one could be a
script, a command, or a line. It is a script, which is the limitation the
bullet then describes, so the header now says so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 3, 2026 18:45

Copilot AI 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 530dc0d into develop Aug 3, 2026
7 checks passed
@ptr727
ptr727 deleted the write-guard-known-limits branch August 3, 2026 19:05
ptr727 added a commit that referenced this pull request Aug 3, 2026
…ndor groundwork (#542)

Six commits, all from one session. Merged as a **merge commit**, never
squashed, per the branching model.

| Commit | PR | What it does |
| --- | --- | --- |
| `1ab9b32` | #537 | Deletes three stale PhotoCleaner `driftNotes` that
described finished work |
| `bf746aa` | #535 | Buckets `TODO.md` by the surface each entry
changes, records five issues verified complete, refreshes the divergence
ledger |
| `69276d1` | #536 | Adds the byte-locked `AGENTS.md` "Fleet Bootstrap"
section, the host-wide `fleet-bootstrap` block, and the `AUDIT.md`
routing that sends an un-stood-up repo to `STANDUP.md` first |
| `530dc0d` | #539 | Records two `gh-write-guard` limits found by
running it: a write inside a script is unseen, and a push followed by a
newline over-blocks |
| `5f4c17f` | #540 | Makes `configure.sh check` verify what the payloads
declare, closing a false clean where a `jq` failure left every setting
reported as passing while nothing was compared |
| `a549572` | #541 | Reports a verbatim section that never arrived as
**absent** rather than as hand-modified |

## Why this promotion matters more than most

Two of these change what the fleet is measured against, so until they
reach `main` every downstream audit compares against ground truth that
predates them.

**#536 is the one with reach.** A downstream agent had no way to
discover this repository. `AGENTS.md` is carried at `intent`, and the
hub's only self-reference described *the hub*, which is false about a
downstream repo, so an agent adapting the file correctly deleted it.
Four repos now hold no hub reference at all and one holds no `AGENTS.md`
either. The replacement is `verbatim` so adaptation cannot remove it,
declared in `spec/files.json` and classified in `spec/section-model.md`
so the audit reports its absence as drift rather than losing it
silently.

**#540 closes a false clean.** `check_settings` streamed its payload
from a process substitution, and a `jq` failure there leaves the loop
body unexecuted without tripping `set -e`. Every static setting reported
as checked and passing while nothing was compared. Verified by negative
test rather than by a clean pass: a malformed `settings.json` now fails
with exit 1, an extra live rule fails the rule-set assertion, and a
changed `copilot_code_review` parameter fails the parameters assertion.

**#541 corrects a report that was actively misleading.** It accused
seventeen repos of hand-editing `AGENTS.md` sections they had never been
given. They still hold the pre-split monolithic file, so the sections
are simply absent. A hand-edit is reconciled against a local decision,
an absence is carried, and the report was naming the wrong one.

## Fleet state behind this

Seventeen downstream re-vendor pull requests merged alongside this work,
each gate-checked at merge time and each verified after. Four repos had
`configure.sh` withdrawn from their re-vendor when review found they
carry `ruleset-*.json` payload names the canonical does not resolve,
which would have left `apply` and `check` aborting on files that do not
exist. That payload migration is still owed and is tracked in `TODO.md`.

## Expected immediately after this merges

The divergence ledger currently reports the hub itself as not carrying
"Fleet Bootstrap" and as owing a `configure.sh` re-vendor. That is the
ledger reading each repo's `main`, honestly, before this promotion.
Regenerating after the merge is what makes the report meaningful again,
and the `configure.sh` re-vendor list will collapse as repos take the
corrected script.

## Verification

`spec/validate.py` OK (21 cataloged), diff-scoped `prose_lint` clean,
`markdownlint-cli2` clean, editorconfig clean, `gh-write-guard
--selftest` PASS, 157 `prose_lint` unit tests pass.
Sign up for free to 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