Fixes#188. Stacked on #191 and #190, whose findings it describes; the change itself is one section of prose.
docs/providers.md listed gitea and github in one table with no caveat, which reads as two forges that got the same consideration. They did not. The doc already makes exactly this point for the harnesses, one section further down, and the forges need it more: the less-exercised one is the public forge an operator is more likely to already be on.
The new section says which forge this project is developed against, what has actually been run on the other, and what is known to differ rather than what might. Its evidence is the audit that produced #184 through #189: forge = github could not start a container at all under the only workflow that opens PRs, ten tests exercised the forge without reaching the entrypoint, and fixing that immediately turned up a merge block a single flag walked past. It names the two differences that remain open - #186's thinner review-feedback loop, and #187's --repo reaching past the sandboxed repository - and says what makes the second one moot, which is a per-repository fine-grained PAT.
It closes the way the harness section does: a recommendation for someone with no other reason to prefer one, and an honest statement that choosing the other means being the person who finds the next crack. Choosing github because that is where your repositories are is a good reason, and the section says so.
Testing
test/run-confined.sh green: audit 158, config 141, conversation 12, project 137, providers 79, proxy 32, remote 108, static 342, zero failures. test/providers.bats is what holds this file to documenting every manifest key and every shipped provider, so a doc change that contradicted the manifests would fail there.
Prose, so the real check is reading it. Nothing here asserts a capability that does not exist: every claim about GitHub's state is either from the issues filed today or from running the code.
**Summary generated by LLM agent**
Fixes #188. Stacked on #191 and #190, whose findings it describes; the change itself is one section of prose.
`docs/providers.md` listed `gitea` and `github` in one table with no caveat, which reads as two forges that got the same consideration. They did not. The doc already makes exactly this point for the harnesses, one section further down, and the forges need it more: the less-exercised one is the public forge an operator is more likely to already be on.
The new section says which forge this project is developed against, what has actually been run on the other, and what is known to differ rather than what might. Its evidence is the audit that produced #184 through #189: `forge = github` could not start a container at all under the only workflow that opens PRs, ten tests exercised the forge without reaching the entrypoint, and fixing that immediately turned up a merge block a single flag walked past. It names the two differences that remain open - #186's thinner review-feedback loop, and #187's `--repo` reaching past the sandboxed repository - and says what makes the second one moot, which is a per-repository fine-grained PAT.
It closes the way the harness section does: a recommendation for someone with no other reason to prefer one, and an honest statement that choosing the other means being the person who finds the next crack. Choosing `github` because that is where your repositories are is a good reason, and the section says so.
### Testing
`test/run-confined.sh` green: audit 158, config 141, conversation 12, project 137, providers 79, proxy 32, remote 108, static 342, zero failures. `test/providers.bats` is what holds this file to documenting every manifest key and every shipped provider, so a doc change that contradicted the manifests would fail there.
Prose, so the real check is reading it. Nothing here asserts a capability that does not exist: every claim about GitHub's state is either from the issues filed today or from running the code.
branderson
changed target branch from main to development2026-08-22 23:54:13 -07:00
A forge CLI has to sit behind the shadow whether or not this repo wrote
it: that is the fixed path forge-guard.sh execs the real command
through, and the entrypoint refuses to start when the command a workflow
blocks is not there. A forge whose CLI comes from a package had no way
to say so.
Declared in the manifest rather than special-cased in the image build,
so it stays true that reading a forge's manifest tells you everything
that forge puts on PATH.
forge = github with workflow = worktree-pr never got past the
entrypoint: worktree-pr is the only workflow that blocks a merge, the
block names gh, and the entrypoint refuses to start when the command it
is meant to shadow is not behind the shadow. That refusal is right - a
merge block nobody can enforce must not look enforced - but gh had no
way to get there, so github failed in the one mode its capabilities
advertise and worked only in the modes where it never opens a PR.
The invariant #184 broke: a forge naming a command nothing installs is
not a weaker guard, it is a container that does not start. Checked
against install-forge-bins.sh's real output rather than against the
manifest, because what matters is where a command ends up.
The two tests that changed had encoded the old fact - that github ships
nothing - and are updated to the new one rather than relaxed. gh is
stubbed in both, so neither says anything about the machine it runs on.
gh --repo owner/name pr merge N reached the real gh. The match was
anchored at the first non-flag argument, which for that command is
owner/name, so the guard gave up there and handed the merge over. That
is the ordinary way to name a PR from outside its checkout, and it
mattered the moment gh got behind the shadow at all.
The sequence is now looked for as a run of consecutive non-flag
arguments, anywhere in them. Knowing which flags take a separate value
would mean knowing each CLI, which is the coupling this guard exists to
avoid. The cost is that a command passing the blocked words as
consecutive values is refused; that is loud, and the other way round is
silent.
hooks/merge-guard.sh has the same hole and is not fixed here: it is
handed a command string rather than argv. It is also redundant now -
the shadow is linked whenever anything is blocked, so every session has
it, and only a harness with hooks has both. See #189.
The marker itself stays core's: a forge says which commands carry a body
a person did not write, and what kind of artifact each one is. What
dejima then says about agent authorship is one wording in one place,
rather than one per forge that happens to have a CLI of its own.
dejima wrote two of the three forge CLIs, so a disclosure that lives in
the tools is one that depends on which forge an operator picked. The
shadow is the one place they all pass through, and it already compares
argv, which is what finding a body argument needs.
Handles the four spellings both CLIs accept, including --body-file -,
where reading stdin here is the only chance to prepend to it at all.
Rewritten into a temp file rather than turned into --body, which would
put a whole body in argv against a 128KB per-argument limit. A command
with no body argument is passed through: there is nothing to mark.
Not conditional on the workflow, unlike the merge block: a block is a
policy about what a workflow permits, this is a statement about who
wrote the text, which holds for every session whatever it may do.
The entrypoint now shadows every CLI named rather than the single one
whose merge is blocked - a forge can have a CLI per artifact, and one
with nothing blocked can still have something to disclose.
The disclosure existed because this repo wrote two of the three forge
CLIs, not because anything had decided which artifacts get marked. So a
session under one forge opened PRs, filed issues and left comments that
said an agent wrote them, and under another it opened, filed and left
exactly the same things saying nothing - with the public forge being the
one that said nothing.
The wording is unchanged, and so is what gets marked. This only changes
where it happens, which is now somewhere both forges pass through.
Two behaviours follow from that and are intended. A command carrying no
body has none invented for it. And running a forge CLI on the host marks
nothing, because there the person running it wrote the body.
Two forges in one table reads as two forges that got the same
consideration. The harness section already makes this point for the
harnesses; the forges need it more, because the untested one is the
public one an operator is more likely to already be on.
The tests that run install-forge-bins.sh took the machine's word for
whether a declared system: command exists. Inside a dejima container gh
does, on the CI runner it does not, so every one of these passed here
and failed there.
Two of the five expected a nonzero exit, which is worse than the one
that failed: they would have passed on the runner for entirely the
wrong reason. Both now check what the failure actually says.
The stub names come from the manifests rather than a list here, so a
forge declaring another one later needs no change to this.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary generated by LLM agent
Fixes #188. Stacked on #191 and #190, whose findings it describes; the change itself is one section of prose.
docs/providers.mdlistedgiteaandgithubin one table with no caveat, which reads as two forges that got the same consideration. They did not. The doc already makes exactly this point for the harnesses, one section further down, and the forges need it more: the less-exercised one is the public forge an operator is more likely to already be on.The new section says which forge this project is developed against, what has actually been run on the other, and what is known to differ rather than what might. Its evidence is the audit that produced #184 through #189:
forge = githubcould not start a container at all under the only workflow that opens PRs, ten tests exercised the forge without reaching the entrypoint, and fixing that immediately turned up a merge block a single flag walked past. It names the two differences that remain open - #186's thinner review-feedback loop, and #187's--reporeaching past the sandboxed repository - and says what makes the second one moot, which is a per-repository fine-grained PAT.It closes the way the harness section does: a recommendation for someone with no other reason to prefer one, and an honest statement that choosing the other means being the person who finds the next crack. Choosing
githubbecause that is where your repositories are is a good reason, and the section says so.Testing
test/run-confined.shgreen: audit 158, config 141, conversation 12, project 137, providers 79, proxy 32, remote 108, static 342, zero failures.test/providers.batsis what holds this file to documenting every manifest key and every shipped provider, so a doc change that contradicted the manifests would fail there.Prose, so the real check is reading it. Nothing here asserts a capability that does not exist: every claim about GitHub's state is either from the issues filed today or from running the code.