feat: Block internal PRs via pre-push hook and GitHub Action

Gas Town agents must push directly to main, not create PRs.
This adds defense-in-depth:

1. .githooks/pre-push - Blocks pushes to non-main branches locally
2. .github/workflows/block-internal-prs.yml - Auto-closes PRs from
   the same repo (forks/contributors can still create PRs)
3. internal/git/git.go - Auto-configures core.hooksPath on clone

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gus
2026-01-04 23:28:11 -08:00
committed by Steve Yegge
parent e0ba057821
commit ff670c5bd4
3 changed files with 104 additions and 2 deletions

View File

@@ -5,7 +5,9 @@ import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
)
@@ -102,7 +104,8 @@ func (g *Git) Clone(url, dest string) error {
if err := cmd.Run(); err != nil {
return g.wrapError(err, stderr.String(), []string{"clone", url})
}
return nil
// Configure hooks path for Gas Town clones
return configureHooksPath(dest)
}
// CloneWithReference clones a repository using a local repo as an object reference.
@@ -114,7 +117,8 @@ func (g *Git) CloneWithReference(url, dest, reference string) error {
if err := cmd.Run(); err != nil {
return g.wrapError(err, stderr.String(), []string{"clone", "--reference-if-able", url})
}
return nil
// Configure hooks path for Gas Town clones
return configureHooksPath(dest)
}
// CloneBare clones a repository as a bare repo (no working directory).
@@ -129,6 +133,25 @@ func (g *Git) CloneBare(url, dest string) error {
return nil
}
// configureHooksPath sets core.hooksPath to use the repo's .githooks directory
// if it exists. This ensures Gas Town agents use the pre-push hook that blocks
// pushes to non-main branches (internal PRs are not allowed).
func configureHooksPath(repoPath string) error {
hooksDir := filepath.Join(repoPath, ".githooks")
if _, err := os.Stat(hooksDir); os.IsNotExist(err) {
// No .githooks directory, nothing to configure
return nil
}
cmd := exec.Command("git", "-C", repoPath, "config", "core.hooksPath", ".githooks")
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("configuring hooks path: %s", strings.TrimSpace(stderr.String()))
}
return nil
}
// CloneBareWithReference clones a bare repository using a local repo as an object reference.
func (g *Git) CloneBareWithReference(url, dest, reference string) error {
cmd := exec.Command("git", "clone", "--bare", "--reference-if-able", reference, url, dest)