fix. harden Go review flow
ci / test (pull_request) Successful in 21s
ci / publish (pull_request) Has been skipped

Fix runner bootstrap and auth handling, preserve queued SHAs, make event/job acceptance atomic, fence stale runs, correct retries and prompts, add fake end-to-end coverage, and fix deployment defaults.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Space-Banane
2026-07-12 22:20:48 +02:00
parent f19b271642
commit 85c0e735dc
19 changed files with 539 additions and 142 deletions
+24 -11
View File
@@ -10,6 +10,7 @@ import (
"os/exec"
"path/filepath"
"strings"
"sync"
"time"
"gitea-codex-bot/internal/config"
@@ -33,17 +34,17 @@ func (r *DockerRunner) Run(parent context.Context, pr domain.PullRequestContext,
nonce := fmt.Sprintf("%d", time.Now().UnixNano())
begin, end := startMarker+"_"+nonce, endMarker+"_"+nonce
prompt := review.BuildPrompt(cmd, cfg, pr)
script := r.script(pr, prompt, begin, end)
script := r.script(pr, prompt, begin, end, cfg.MaxDiffBytes)
name := "codex-review-" + nonce
args := []string{"run", "--rm", "-i", "--name", name, "--cap-drop=ALL", "--security-opt", "no-new-privileges", "--read-only", "--tmpfs", "/tmp:rw,noexec,nosuid,size=512m", "--tmpfs", "/work:rw,nosuid,size=1g", "-e", "CODEX_DISABLE_TELEMETRY=1"}
args := []string{"run", "--rm", "-i", "--name", name, "--cap-drop=ALL", "--security-opt", "no-new-privileges", "--tmpfs", "/tmp:rw,noexec,nosuid,size=512m", "--tmpfs", "/work:rw,nosuid,size=1g", "-e", "CODEX_DISABLE_TELEMETRY=1"}
if r.settings.CodexAuthMode == "chatgpt" {
args = append(args, "-e", "CODEX_AUTH_JSON_B64")
args = append(args, "--tmpfs", "/root/.codex:rw,nosuid,size=16m", "-e", "CODEX_AUTH_JSON_B64")
} else {
args = append(args, "-e", "OPENAI_API_KEY")
}
args = append(args, "-e", "GITEA_TOKEN", "-e", "GITEA_GIT_USERNAME", r.settings.RunnerImage, "bash", "-lc", script)
args = append(args, "-e", "OPENAI_ORG_ID", "-e", "OPENAI_PROJECT_ID", "-e", "GITEA_TOKEN", "-e", "GITEA_GIT_USERNAME", r.settings.RunnerImage, "bash", "-lc", script)
cmdExec := exec.CommandContext(ctx, "docker", args...)
cmdExec.Env = append(os.Environ(), "OPENAI_API_KEY="+r.settings.OpenAIAPIKey, "GITEA_TOKEN="+r.settings.GiteaToken, "GITEA_GIT_USERNAME="+r.settings.GiteaBotUsername)
cmdExec.Env = append(os.Environ(), "OPENAI_API_KEY="+r.settings.OpenAIAPIKey, "OPENAI_ORG_ID="+r.settings.OpenAIOrgID, "OPENAI_PROJECT_ID="+r.settings.OpenAIProjectID, "GITEA_TOKEN="+r.settings.GiteaToken, "GITEA_GIT_USERNAME="+r.settings.GiteaBotUsername)
if r.settings.CodexAuthMode == "chatgpt" {
data, err := readAuthJSON(r.settings.CodexAuthJSONPath)
if err != nil {
@@ -62,6 +63,9 @@ func (r *DockerRunner) Run(parent context.Context, pr domain.PullRequestContext,
}
return domain.ReviewResult{}, fmt.Errorf("review runner failed: %w", err)
}
if output.Truncated() {
return domain.ReviewResult{}, fmt.Errorf("review runner output exceeded %d bytes", maxRunnerOutput)
}
text := output.String()
start := strings.Index(text, begin)
endPos := strings.LastIndex(text, end)
@@ -81,12 +85,12 @@ func (r *DockerRunner) Run(parent context.Context, pr domain.PullRequestContext,
func readAuthJSON(rawPath string) ([]byte, error) {
path := os.ExpandEnv(rawPath)
if strings.HasPrefix(path, "~/") {
if strings.HasPrefix(path, "~") && len(path) > 1 && (path[1] == '/' || path[1] == '\\') {
home, err := os.UserHomeDir()
if err != nil {
return nil, err
}
path = filepath.Join(home, strings.TrimPrefix(path, "~/"))
path = filepath.Join(home, strings.TrimLeft(path[1:], "/\\"))
}
data, err := os.ReadFile(filepath.Clean(path))
if err != nil {
@@ -98,7 +102,7 @@ func readAuthJSON(rawPath string) ([]byte, error) {
return data, nil
}
func (r *DockerRunner) script(pr domain.PullRequestContext, prompt, begin, end string) string {
func (r *DockerRunner) script(pr domain.PullRequestContext, prompt, begin, end string, maxDiffBytes int) string {
auth := base64.StdEncoding.EncodeToString([]byte(r.settings.GiteaBotUsername + ":" + r.settings.GiteaToken))
schema := `{"type":"object","additionalProperties":false,"required":["verdict","confidence","summary","findings","markdown_comment"],"properties":{"verdict":{"type":"string","enum":["correct","has_issues"]},"confidence":{"type":"number"},"summary":{"type":"string"},"markdown_comment":{"type":"string"},"findings":{"type":"array"}}}`
quote := func(value string) string { return "'" + strings.ReplaceAll(value, "'", "'\"'\"'") + "'" }
@@ -112,9 +116,10 @@ func (r *DockerRunner) script(pr domain.PullRequestContext, prompt, begin, end s
if r.settings.CodexAuthMode == "chatgpt" {
authSetup = "mkdir -p /root/.codex; printf '%s' \"$CODEX_AUTH_JSON_B64\" | base64 -d > /root/.codex/auth.json; chmod 600 /root/.codex/auth.json"
}
bootstrap := "if ! command -v git >/dev/null 2>&1; then apt-get update >/tmp/apt-update.log 2>&1 && apt-get install -y --no-install-recommends ca-certificates git >/tmp/apt-install.log 2>&1; fi; if ! command -v codex >/dev/null 2>&1; then npm install -g @openai/codex@latest >/tmp/codex-install.log 2>&1; fi; command -v git >/dev/null 2>&1; command -v codex >/dev/null 2>&1"
fetchHead := "git -c http.extraHeader=" + quote("Authorization: Basic "+auth) + " fetch --no-tags origin " + quote(pr.HeadRef) + " || git -c http.extraHeader=" + quote("Authorization: Basic "+auth) + " fetch --no-tags origin " + quote(pr.HeadSHA)
fetchBase := "git -c http.extraHeader=" + quote("Authorization: Basic "+auth) + " fetch --no-tags " + baseRemote + " " + quote(pr.BaseRef) + " || git -c http.extraHeader=" + quote("Authorization: Basic "+auth) + " fetch --no-tags " + baseRemote + " " + quote(pr.BaseSHA)
steps := []string{"set -eu", "printf '%s' " + quote(schema) + " > /tmp/schema.json"}
steps := []string{"set -eu", "printf '%s' " + quote(schema) + " > /tmp/schema.json", bootstrap}
if authSetup != "" {
steps = append(steps, authSetup)
}
@@ -122,17 +127,24 @@ func (r *DockerRunner) script(pr domain.PullRequestContext, prompt, begin, end s
if remoteSetup != "" {
steps = append(steps, remoteSetup)
}
steps = append(steps, fetchHead, fetchBase, "git checkout --detach "+quote(pr.HeadSHA), "test \"$(git rev-parse HEAD)\" = "+quote(pr.HeadSHA), "unset GITEA_TOKEN", "codex exec --sandbox danger-full-access --json --output-schema /tmp/schema.json -o /tmp/result.json -m "+quote(r.settings.OpenAIReviewModel)+" "+quote(prompt), "test -s /tmp/result.json", "printf '%s\\n' "+quote(begin), "cat /tmp/result.json", "printf '%s\\n' "+quote(end))
steps = append(steps, fetchHead, fetchBase, "git checkout --detach "+quote(pr.HeadSHA), "test \"$(git rev-parse HEAD)\" = "+quote(pr.HeadSHA))
if maxDiffBytes > 0 {
steps = append(steps, "diff_bytes=$(git diff --binary "+quote(pr.BaseSHA)+" "+quote(pr.HeadSHA)+" | wc -c); test \"$diff_bytes\" -le "+fmt.Sprintf("%d", maxDiffBytes))
}
steps = append(steps, "unset GITEA_TOKEN", "codex exec --sandbox danger-full-access --json --output-schema /tmp/schema.json -o /tmp/result.json -m "+quote(r.settings.OpenAIReviewModel)+" "+quote(prompt), "test -s /tmp/result.json", "printf '%s\\n' "+quote(begin), "cat /tmp/result.json", "printf '%s\\n' "+quote(end))
return strings.Join(steps, "; ")
}
type limitedBuffer struct {
mu sync.Mutex
buffer bytes.Buffer
limit int
truncated bool
}
func (b *limitedBuffer) Write(p []byte) (int, error) {
b.mu.Lock()
defer b.mu.Unlock()
remaining := b.limit - b.buffer.Len()
if remaining <= 0 {
b.truncated = true
@@ -145,6 +157,7 @@ func (b *limitedBuffer) Write(p []byte) (int, error) {
}
return b.buffer.Write(p)
}
func (b *limitedBuffer) String() string { return b.buffer.String() }
func (b *limitedBuffer) String() string { b.mu.Lock(); defer b.mu.Unlock(); return b.buffer.String() }
func (b *limitedBuffer) Truncated() bool { b.mu.Lock(); defer b.mu.Unlock(); return b.truncated }
var _ domain.ReviewRunner = (*DockerRunner)(nil)
+3 -3
View File
@@ -14,7 +14,7 @@ func samplePR() domain.PullRequestContext {
func TestScriptChecksExactHeadAndBase(t *testing.T) {
r := NewDockerRunner(config.Settings{GiteaBotUsername: "bot", GiteaToken: "token", OpenAIReviewModel: "model", CodexAuthMode: "api_key"})
script := r.script(samplePR(), "review prompt", "BEGIN_nonce", "END_nonce")
script := r.script(samplePR(), "review prompt", "BEGIN_nonce", "END_nonce", 200000)
for _, fragment := range []string{"git checkout --detach", "git rev-parse HEAD", "fetch --no-tags origin 'feature'", "fetch --no-tags origin '" + strings.Repeat("b", 40) + "'", "BEGIN_nonce", "END_nonce", "--output-schema", "-o /tmp/result.json"} {
if !strings.Contains(script, fragment) {
t.Fatalf("script missing %q: %s", fragment, script)
@@ -32,7 +32,7 @@ func TestForkScriptUsesUpstreamBaseRemote(t *testing.T) {
pr := samplePR()
pr.BaseCloneURL = "https://gitea.test/base/repo.git"
r := NewDockerRunner(config.Settings{GiteaBotUsername: "bot", GiteaToken: "token", OpenAIReviewModel: "model", CodexAuthMode: "api_key"})
script := r.script(pr, "prompt", "BEGIN", "END")
script := r.script(pr, "prompt", "BEGIN", "END", 200000)
if !strings.Contains(script, "git remote add upstream") || !strings.Contains(script, "fetch --no-tags upstream") {
t.Fatalf("fork base remote was not configured: %s", script)
}
@@ -40,7 +40,7 @@ func TestForkScriptUsesUpstreamBaseRemote(t *testing.T) {
func TestChatGPTScriptWritesAuthFile(t *testing.T) {
r := NewDockerRunner(config.Settings{GiteaBotUsername: "bot", GiteaToken: "token", OpenAIReviewModel: "model", CodexAuthMode: "chatgpt"})
script := r.script(samplePR(), "prompt", "BEGIN", "END")
script := r.script(samplePR(), "prompt", "BEGIN", "END", 200000)
if !strings.Contains(script, "CODEX_AUTH_JSON_B64") || !strings.Contains(script, "chmod 600 /root/.codex/auth.json") {
t.Fatalf("chatgpt auth setup missing: %s", script)
}