GOVERNANCE.md is carried by every fleet repository. It names a script that exists in none of them.
$ git show origin/develop:GOVERNANCE.md | grep -c 'scripts/pr_review.py'
1
$ git show origin/develop:spec/files.json | grep -c '"scripts/'
0
The line is GOVERNANCE.md "PR Review Etiquette" at line 301, instructing the reader to run scripts/pr_review.py status <n> --repo <owner>/<name> to read every review round rather than only the head. The instruction is correct and the tool works. A downstream agent following it finds no such path, because scripts/ is hub-only and appears in no spec/files.json baseline entry.
TODO.md already carries an entry asking where a carried file may name hub-only machinery. What that entry lacked, and what this issue adds, is evidence that the cost is being paid rather than anticipated.
What downstream agents do instead
They build their own. Over the past two days of onboarding, several sessions had to be redirected by hand to scripts/pr_review.py and scripts/prose_lint.py after producing worse ad-hoc versions of both. None of those agents did anything wrong. They were pointed at a tool absent from their tree by a document that does not say how to reach it, and reconstructing it is the only remaining move.
The second-order cost is worse than the duplicated effort. A rule that names a tool its reader cannot run teaches the reader that a pointer in carried text is decorative, and that lesson generalizes to the pointers that do resolve.
The same gap makes a safety rule harder to follow than it should be
Some operations fail the same way repeatedly regardless of how plainly the rule is stated. Two are on record from this fleet's own sessions:
- A command run from the wrong working directory, which does not error. It returns a confident answer about a different tree.
- A GitHub node id typed by hand into a mutation, which the
gh-write-guard hook correctly refuses because the id did not come from a query in that same command.
The second is the instructive one. The rule against hand-typing an id is stated plainly in GOVERNANCE.md and again in the host-level guidance, and an agent that had read both still reached for the literal. That says the shape of the operation is what fails, not the agent's knowledge of the rule, and a rule restated more firmly does not fix a shape.
The fix is a shape that cannot be performed wrongly. A reply-and-resolve helper in scripts/pr_review.py would take a pull request number and a finding, query the thread ids itself, and never put an id anywhere a hand could type one. That removes the failure instead of detecting it after the fact.
It also depends entirely on this issue, because a helper only helps where it can be run.
Two resolutions, and they are not equivalent
The script joins the carried set. Every repository holds a copy, the pointer resolves, and every fix to the tool becomes a fleet re-vendor. This is the model repo-config/configure.sh already runs, and its cost is visible in the re-vendor entries in TODO.md.
The rule states the behavior and drops the tool name, the way the coordination-reference rule already requires of this repository. The pointer stops lying, and the tool stays unreachable, so the agents keep rebuilding it.
Neither is satisfying, which is why the TODO.md entry on reducing the copied surface proposes the third: the hub hosts the tool once and a downstream repository calls it, so there is no copy to go stale and no re-vendor to schedule. That option needs a pinning policy and an answer for an unreachable hub, and it is the same decision as the reusable-workflow question, so the three want settling together.
Related: #559 is the same class from the other direction, a carried file whose link definitions point into hub-only catalog/, dead in every downstream copy.
Filed from the onboarding-feedback pass in #576, where both halves are recorded as TODO.md entries.
GOVERNANCE.mdis carried by every fleet repository. It names a script that exists in none of them.The line is
GOVERNANCE.md"PR Review Etiquette" at line 301, instructing the reader to runscripts/pr_review.py status <n> --repo <owner>/<name>to read every review round rather than only the head. The instruction is correct and the tool works. A downstream agent following it finds no such path, becausescripts/is hub-only and appears in nospec/files.jsonbaseline entry.TODO.mdalready carries an entry asking where a carried file may name hub-only machinery. What that entry lacked, and what this issue adds, is evidence that the cost is being paid rather than anticipated.What downstream agents do instead
They build their own. Over the past two days of onboarding, several sessions had to be redirected by hand to
scripts/pr_review.pyandscripts/prose_lint.pyafter producing worse ad-hoc versions of both. None of those agents did anything wrong. They were pointed at a tool absent from their tree by a document that does not say how to reach it, and reconstructing it is the only remaining move.The second-order cost is worse than the duplicated effort. A rule that names a tool its reader cannot run teaches the reader that a pointer in carried text is decorative, and that lesson generalizes to the pointers that do resolve.
The same gap makes a safety rule harder to follow than it should be
Some operations fail the same way repeatedly regardless of how plainly the rule is stated. Two are on record from this fleet's own sessions:
gh-write-guardhook correctly refuses because the id did not come from a query in that same command.The second is the instructive one. The rule against hand-typing an id is stated plainly in
GOVERNANCE.mdand again in the host-level guidance, and an agent that had read both still reached for the literal. That says the shape of the operation is what fails, not the agent's knowledge of the rule, and a rule restated more firmly does not fix a shape.The fix is a shape that cannot be performed wrongly. A reply-and-resolve helper in
scripts/pr_review.pywould take a pull request number and a finding, query the thread ids itself, and never put an id anywhere a hand could type one. That removes the failure instead of detecting it after the fact.It also depends entirely on this issue, because a helper only helps where it can be run.
Two resolutions, and they are not equivalent
The script joins the carried set. Every repository holds a copy, the pointer resolves, and every fix to the tool becomes a fleet re-vendor. This is the model
repo-config/configure.shalready runs, and its cost is visible in the re-vendor entries inTODO.md.The rule states the behavior and drops the tool name, the way the coordination-reference rule already requires of this repository. The pointer stops lying, and the tool stays unreachable, so the agents keep rebuilding it.
Neither is satisfying, which is why the
TODO.mdentry on reducing the copied surface proposes the third: the hub hosts the tool once and a downstream repository calls it, so there is no copy to go stale and no re-vendor to schedule. That option needs a pinning policy and an answer for an unreachable hub, and it is the same decision as the reusable-workflow question, so the three want settling together.Related: #559 is the same class from the other direction, a carried file whose link definitions point into hub-only
catalog/, dead in every downstream copy.Filed from the onboarding-feedback pass in #576, where both halves are recorded as
TODO.mdentries.