pr-guard does not read ward.workflow, so it overrides the merge-remote-main lane it should honour #1321

Closed
opened 2026-08-27 06:18:06 +00:00 by coilyco-ops · 0 comments
Owner

Surfaced while advancing stale hook pins across the fleet (coilysiren/inbox#439). Distinct from #1192 (agentic-os not dogfooding pr-guard) and #1163 (fresh clones arrive unarmed), neither of which touches the lane question.

What is true today

scripts/pr-guard-pre-push.sh refuses any push whose destination is the remote's default branch, unconditionally:

if [ "$branch" = "$default_branch" ]; then
  die "refusing to push directly to '$default_branch'. Branch and open a PR.
       Deliberate override: git push --no-verify"
fi

It never reads ward.workflow. It is in DEFAULT_HOOK_IDS, so apply-agentic-os-hooks.py installs it into every consumer, and it first shipped in aos-precommit-v0.34.0.

Meanwhile merge-remote-main is defined in the fleet rules as: the agent commits, pushes to main, and closes the issue. No branch and no pull request. The lane is declared per repo as ward.workflow in AGENTS.md frontmatter, generate-git-workflow renders a managed AGENTS.md block from it, and check-git-workflow gates that block on every commit.

So one hook renders prose telling the agent to push to main, and a second hook refuses that push.

Who is affected

Of the repos whose pin now carries pr-guard, eight declare merge-remote-main and are in contradiction:

  • agent-proxy, agentic-os-xxx, voice-corpus, factory-game-v3, mcp-beaver - advanced this week
  • eco-app, eco-ops, steam-ops - already there, at v0.35.0 and v0.43.0

One is consistent: deploy, which declares pull-request-and-merge.

The contradiction predates the current rollout. It reached three repos on its own and the pin advance widened it to eight. Every further pin advance widens it again, so this wants settling before the remaining stale pins move.

Why it is not benign

The only documented override is git push --no-verify, and the fleet agent rules forbid that outright, alongside force-push, as one of the two walls that stay closed. An agent on a merge-remote-main repo therefore has no compliant path: its lane says push to main, the guard refuses, and the escape hatch is prohibited.

In practice the agent either stops with work stranded locally, which is the failure the lane rules exist to prevent, or improvises a branch-and-PR flow the repo never declared. I did the second on agent-proxy (#149) because the lane grants land-it-yourself authority and only the mechanism had changed. That reasoning is not obvious, and a different agent would plausibly stop instead.

Recommendation

Make pr-guard read the declared lane and stand down on merge-remote-main. The lane is already the single declared source of truth for this exact question, two other pieces of the suite already consume it, and the alternative silently converts most of the fleet to a workflow none of those repos chose.

Reject the mirror option, moving eight repos to pull-request-and-merge, unless that is a deliberate portfolio decision rather than a way to satisfy a hook. It would be the hook redefining the fleet's git policy by side effect.

Constraint on the implementation

pr-guard is bash and the lane resolver is Python, so the guard must not reimplement frontmatter parsing. A second parser for a tunable that already has one owning source is the thing the configuration rules forbid.

agentic_os.generators.generate_git_workflow already exposes LANES, normalize_lane(), and detect_lane(). There is no console entry point that prints a repo's resolved lane today: check-git-workflow and generate-git-workflow are the only two registered, and neither has that flag. So one small addition is needed for the bash side to call.

Acceptance

  • On a repo declaring merge-remote-main, a push to the default branch succeeds and the guard says nothing.
  • On a repo declaring pull-request, pull-request-and-merge, or remote-branch-only, the current refusal is unchanged.
  • On a repo with no declared lane, the current refusal is unchanged. Undeclared stays guarded.
  • The already-published-branch half of the guard is untouched in every case.
  • No second lane parser exists after the change.

Out of scope

The five repos still on stale pins are blocked by CI-shape hooks rather than this, tracked in coilysiren/inbox#457. Server-side branch protection, which the script correctly names as the control this one cannot replace, is also separate.

One more gap

generate-git-workflow reads only AGENTS.md frontmatter. umbra declares its lane in .ward/ward.yaml as workflow: merge-remote-main and carries no frontmatter, so running the generator there writes the undeclared variant. Whatever lane reader pr-guard ends up calling will read umbra as undeclared too, and guard it, which happens to be safe but for the wrong reason. Worth resolving in the same pass: either umbra declares in frontmatter as well, or the resolver learns the ward file.

Surfaced while advancing stale hook pins across the fleet (coilysiren/inbox#439). Distinct from #1192 (agentic-os not dogfooding pr-guard) and #1163 (fresh clones arrive unarmed), neither of which touches the lane question. ## What is true today `scripts/pr-guard-pre-push.sh` refuses any push whose destination is the remote's default branch, unconditionally: ``` if [ "$branch" = "$default_branch" ]; then die "refusing to push directly to '$default_branch'. Branch and open a PR. Deliberate override: git push --no-verify" fi ``` It never reads `ward.workflow`. It is in `DEFAULT_HOOK_IDS`, so `apply-agentic-os-hooks.py` installs it into every consumer, and it first shipped in `aos-precommit-v0.34.0`. Meanwhile `merge-remote-main` is defined in the fleet rules as: *the agent commits, pushes to `main`, and closes the issue. No branch and no pull request.* The lane is declared per repo as `ward.workflow` in AGENTS.md frontmatter, `generate-git-workflow` renders a managed AGENTS.md block from it, and `check-git-workflow` gates that block on every commit. So one hook renders prose telling the agent to push to `main`, and a second hook refuses that push. ## Who is affected Of the repos whose pin now carries pr-guard, **eight declare `merge-remote-main`** and are in contradiction: * `agent-proxy`, `agentic-os-xxx`, `voice-corpus`, `factory-game-v3`, `mcp-beaver` - advanced this week * `eco-app`, `eco-ops`, `steam-ops` - already there, at v0.35.0 and v0.43.0 One is consistent: `deploy`, which declares `pull-request-and-merge`. The contradiction predates the current rollout. It reached three repos on its own and the pin advance widened it to eight. Every further pin advance widens it again, so this wants settling before the remaining stale pins move. ## Why it is not benign The only documented override is `git push --no-verify`, and the fleet agent rules forbid that outright, alongside force-push, as one of the two walls that stay closed. An agent on a `merge-remote-main` repo therefore has no compliant path: its lane says push to main, the guard refuses, and the escape hatch is prohibited. In practice the agent either stops with work stranded locally, which is the failure the lane rules exist to prevent, or improvises a branch-and-PR flow the repo never declared. I did the second on `agent-proxy` (#149) because the lane grants land-it-yourself authority and only the mechanism had changed. That reasoning is not obvious, and a different agent would plausibly stop instead. ## Recommendation **Make pr-guard read the declared lane and stand down on `merge-remote-main`.** The lane is already the single declared source of truth for this exact question, two other pieces of the suite already consume it, and the alternative silently converts most of the fleet to a workflow none of those repos chose. Reject the mirror option, moving eight repos to `pull-request-and-merge`, unless that is a deliberate portfolio decision rather than a way to satisfy a hook. It would be the hook redefining the fleet's git policy by side effect. ## Constraint on the implementation pr-guard is bash and the lane resolver is Python, so the guard must not reimplement frontmatter parsing. A second parser for a tunable that already has one owning source is the thing the configuration rules forbid. `agentic_os.generators.generate_git_workflow` already exposes `LANES`, `normalize_lane()`, and `detect_lane()`. There is no console entry point that prints a repo's resolved lane today: `check-git-workflow` and `generate-git-workflow` are the only two registered, and neither has that flag. So one small addition is needed for the bash side to call. ## Acceptance * On a repo declaring `merge-remote-main`, a push to the default branch succeeds and the guard says nothing. * On a repo declaring `pull-request`, `pull-request-and-merge`, or `remote-branch-only`, the current refusal is unchanged. * On a repo with no declared lane, the current refusal is unchanged. Undeclared stays guarded. * The already-published-branch half of the guard is untouched in every case. * No second lane parser exists after the change. ## Out of scope The five repos still on stale pins are blocked by CI-shape hooks rather than this, tracked in coilysiren/inbox#457. Server-side branch protection, which the script correctly names as the control this one cannot replace, is also separate. ## One more gap `generate-git-workflow` reads only AGENTS.md frontmatter. `umbra` declares its lane in `.ward/ward.yaml` as `workflow: merge-remote-main` and carries no frontmatter, so running the generator there writes the **undeclared** variant. Whatever lane reader pr-guard ends up calling will read umbra as undeclared too, and guard it, which happens to be safe but for the wrong reason. Worth resolving in the same pass: either umbra declares in frontmatter as well, or the resolver learns the ward file.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/agentic-os#1321
No description provided.