Public: anyone can read this; the crew can change it.

One trunk again: bring Forgejo's 4 grokbot commits into main, and land outside agents only through the forge's pulls #2615

Merged opened this in answer to

Check · Review · Land · Live done — Landed and live in production.

Merged

This merges grokbot's two Forgejo pulls into main and adds a push rule. With that rule, a protected default branch takes a direct `git push` only from its admins; every other writer has to open a pull. Two parts of the issue are not done and need the owner, listed at the end.

shipped production is serving it deploy 652cd9c

opened this

This merges grokbot's two Forgejo pulls into main and adds a push rule. With that rule, a protected default branch takes a direct git push only from its admins; every other writer has to open a pull. Two parts of the issue are not done and need the owner, listed at the end.

Merge (commit 1). This merges upstream-main (6c30ec09/5881c081 and a97b8e6b/68084231) into main. Both changes were already on main by content from grokbot's direct pushes. So the two conflicts, in src/crew/parks.ts and src/actions/issues.ts, were only main's later additions: the alreadyDone import and the limb re-plan's replanSince. I kept both as main has them. The merge commit changes no files; it only makes Forgejo's main an ancestor of main, so the mirror can fast-forward again.

Push rule (commit 2). Before this, the pre-receive hook in src/http/server.ts refused only agent accounts, force pushes and deletions. grokbot's account is a user, not an agent, which is how it pushed straight to main. With protection on, the hook now refuses a push to the default branch from anyone who isn't an admin, with the line main is protected: only its admins push it; open a pull from a branch. That is the same rule the web editor already applies in src/actions/contents.ts.

  • Merges are unaffected: they move the ref directly (src/git/merge.ts) and never run the hook.
  • An unprotected default branch behaves as before.
  • The settings page text and docs/THREATS.md now describe the rule.

Test. test/protected-default-push.test.ts sets up an org repo with an owner and a writer who isn't an admin. It checks four things:

  • With protection off, the writer's push to main still lands.
  • With protection on, the writer's push is refused with that line and main doesn't move.
  • The writer's branch lands through a pull that an admin merges.
  • The admin can still push to main directly.

Without the hook change, the refusal and pull cases fail. The new test and the existing protection, branch, editor, settings and hollow tests all pass, and the type check is clean.

Not done:

  • Protection is off on trevato/forge (protectDefault: false), so the rule does nothing there until the owner ticks "Protect the default branch" in settings. I'm not allowed to change repository settings.
  • grokbot must not be an admin of the repo or its owner's teams, because admins can still push. I couldn't check its role from here.
  • Part (3), locking Forgejo main to the steward's mirror push, is Forgejo's own branch-protection setting. Nothing in this repository controls it, so I didn't do it.

To try it: bun test test/protected-default-push.test.ts

🤖 Generated with Claude Code

Closes #2614

commented

This merges grokbot's two Forgejo pulls into main and adds a push rule. With that rule, a protected default branch takes a direct git push only from its admins; every other writer has to open a pull. Two parts of the issue are not done and need the owner, listed at the end.

Merge (commit 1). This merges upstream-main (6c30ec09/5881c081 and a97b8e6b/68084231) into main. Both changes were already on main by content from grokbot's direct pushes. So the two conflicts, in src/crew/parks.ts and src/actions/issues.ts, were only main's later additions: the alreadyDone import and the limb re-plan's replanSince. I kept both as main has them. The merge commit changes no files; it only makes Forgejo's main an ancestor of main, so the mirror can fast-forward again.

Push rule (commit 2). Before this, the pre-receive hook in src/http/server.ts refused only agent accounts, force pushes and deletions. grokbot's account is a user, not an agent, which is how it pushed straight to main. With protection on, the hook now refuses a push to the default branch from anyone who isn't an admin, with the line main is protected: only its admins push it; open a pull from a branch. That is the same rule the web editor already applies in src/actions/contents.ts.

  • Merges are unaffected: they move the ref directly (src/git/merge.ts) and never run the hook.
  • An unprotected default branch behaves as before.
  • The settings page text and docs/THREATS.md now describe the rule.

Test. test/protected-default-push.test.ts sets up an org repo with an owner and a writer who isn't an admin. It checks four things:

  • With protection off, the writer's push to main still lands.
  • With protection on, the writer's push is refused with that line and main doesn't move.
  • The writer's branch lands through a pull that an admin merges.
  • The admin can still push to main directly.

Without the hook change, the refusal and pull cases fail. The new test and the existing protection, branch, editor, settings and hollow tests all pass, and the type check is clean.

Not done:

  • Protection is off on trevato/forge (protectDefault: false), so the rule does nothing there until the owner ticks "Protect the default branch" in settings. I'm not allowed to change repository settings.
  • grokbot must not be an admin of the repo or its owner's teams, because admins can still push. I couldn't check its role from here.
  • Part (3), locking Forgejo main to the steward's mirror push, is Forgejo's own branch-protection setting. Nothing in this repository controls it, so I didn't do it.

To try it: bun test test/protected-default-push.test.ts

🤖 Generated with Claude Code

Approved

Approved. The change is small and correct.

  • Merge (ed8d2a63). It changes no content: the diff from cfa8955b is empty. Both of grokbot's Forgejo merges (5881c081, 68084231) are now ancestors, so the mirror can fast-forward. test/crew-parks.test.ts and test/tty-stop-tree.test.ts pass at this head.
  • Hook rule. The new check sits inside the existing protectDefault && ref === default branch of pre-receive. It comes after the proposal-namespace, refs/pull, suspended-account and agent checks, and it uses canAdmin, the same rule actions/contents.ts already applies to the web editor. Both pushing doors, smart HTTP (server.ts receive-pack) and SSH (tui/ssh.ts), send the same signed hook request, so one check covers both. The actor comes from FORGE_ACTOR_ID, which the server sets from the authenticated user, and the hook request is gated by the constant-time hook secret. Nothing in it comes from the client. Merges move the ref directly and are unaffected. No new dependency, no user text reaches shell or SQL, and the refusal line contains only repo.defaultBranch.
  • Test. test/protected-default-push.test.ts fails 2/4 when I put back base's server.ts, and passes 4/4 at head. test/protection.test.ts passes too, and tsc --noEmit is clean.

Not verified by me: the preview host and the /data/looks screenshots are both blocked by my sandbox. That is acceptable for a pre-receive rule that has no page of its own beyond one settings sentence.

For the owner, not the builder:

  1. Part (2) of #2614 only takes effect once "Protect the default branch" is ticked on trevato/forge. It is off today.
  2. The rule lets every repo admin push, which includes site admins and org-team admins. That is wider than the issue's "the owner". Check that grokbot holds none of those roles.
  3. Part (3), Forgejo's own branch protection, lives outside this repository.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs