Skip to content

Commit 4c5bbe4

Browse files
author
Test
committed
Harden Sentry agent prompt and fix branch detection
The agent prompt was too verbose and the LLM ignored the 'do NOT use project_map' instruction, wasting 12 tool calls exploring the project instead of fixing the bug. The PR command also hardcoded --base master instead of detecting the default branch. Rewrote the prompt to be terser and imperative. Detects the default branch from the cloned repo via git rev-parse. Uses a unique branch name per run to avoid conflicts. The PR is the final mandatory step.
1 parent 6bc350c commit 4c5bbe4

File tree

1 file changed

+206
-0
lines changed

1 file changed

+206
-0
lines changed

cmd/gptcode/agent.go

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
"strings"
9+
10+
"github.com/spf13/cobra"
11+
12+
"gptcode/internal/live"
13+
"gptcode/internal/llm"
14+
"gptcode/internal/modes"
15+
)
16+
17+
var agentCmd = &cobra.Command{
18+
Use: "agent",
19+
Short: "Run gptcode as a centralized autofix agent",
20+
Long: `This command is intended to be run exclusively by the gptcode-live centralized agent system.
21+
It expects specific environment variables to authenticate and clone a shallow copy of the repository,
22+
fetch the CI logs, run the fix, and report back via WebSocket.`,
23+
RunE: runAgent,
24+
}
25+
26+
func init() {
27+
rootCmd.AddCommand(agentCmd)
28+
}
29+
30+
func runAgent(cmd *cobra.Command, args []string) error {
31+
repo := os.Getenv("REPO")
32+
branch := os.Getenv("BRANCH")
33+
sha := os.Getenv("SHA")
34+
runID := os.Getenv("RUN_ID")
35+
token := os.Getenv("INSTALL_TOKEN")
36+
liveURL := os.Getenv("GPTCODE_LIVE_URL")
37+
agentID := os.Getenv("AGENT_ID")
38+
39+
if repo == "" || branch == "" || sha == "" || runID == "" || token == "" {
40+
return fmt.Errorf("missing required agent environment variables (REPO, BRANCH, SHA, RUN_ID, INSTALL_TOKEN)")
41+
}
42+
43+
fmt.Printf("🚀 Starting AutoFix Agent for %s@%s (Run %s)\n", repo, branch, runID)
44+
if liveURL != "" {
45+
fmt.Printf("📡 Reporting progress to %s with agent_id %s\n", liveURL, agentID)
46+
}
47+
48+
// 1. Shallow clone the target repo using the installation token
49+
cloneDir := "/work"
50+
// Ensure directory doesn't exist from a previous run
51+
os.RemoveAll(cloneDir)
52+
53+
repoURL := fmt.Sprintf("https://x-access-token:%s@github.com/%s.git", token, repo)
54+
55+
fmt.Printf("📦 Cloning %s (branch: %s) into %s...\n", repo, branch, cloneDir)
56+
cmdClone := exec.Command("git", "clone", "--depth", "1", "--branch", branch, repoURL, cloneDir)
57+
cmdClone.Stdout = os.Stdout
58+
cmdClone.Stderr = os.Stderr
59+
err := cmdClone.Run()
60+
if err != nil {
61+
// Fallback: try 'master' if the specified branch doesn't exist
62+
fmt.Printf("⚠️ Branch '%s' not found, trying 'master'...\n", branch)
63+
os.RemoveAll(cloneDir)
64+
cmdClone2 := exec.Command("git", "clone", "--depth", "1", "--branch", "master", repoURL, cloneDir)
65+
cmdClone2.Stdout = os.Stdout
66+
cmdClone2.Stderr = os.Stderr
67+
err = cmdClone2.Run()
68+
if err != nil {
69+
// Final fallback: clone default branch
70+
fmt.Printf("⚠️ Branch 'master' not found either, cloning default branch...\n")
71+
os.RemoveAll(cloneDir)
72+
cmdClone3 := exec.Command("git", "clone", "--depth", "1", repoURL, cloneDir)
73+
cmdClone3.Stdout = os.Stdout
74+
cmdClone3.Stderr = os.Stderr
75+
err = cmdClone3.Run()
76+
if err != nil {
77+
fmt.Printf("❌ Failed to clone repository: %v\n", err)
78+
return err
79+
}
80+
}
81+
}
82+
defer os.RemoveAll(cloneDir) // Clean up if running locally, though ephemeral VMs will just die
83+
84+
// Set GH_TOKEN so the gh CLI can create PRs
85+
os.Setenv("GH_TOKEN", token)
86+
// Configure git identity for commits
87+
exec.Command("git", "config", "--global", "user.email", "gptcode-agent@zapfy.ai").Run()
88+
exec.Command("git", "config", "--global", "user.name", "GPTCode Agent").Run()
89+
90+
// Change to the working directory
91+
err = os.Chdir(cloneDir)
92+
if err != nil {
93+
return fmt.Errorf("failed to change to working directory %s: %w", cloneDir, err)
94+
}
95+
96+
// 2. Extract contextual instruction
97+
agentType := os.Getenv("AGENT_TYPE")
98+
if agentType == "" {
99+
agentType = "ci"
100+
}
101+
102+
// Skip validation for Sentry agents — CI/CD will handle compilation
103+
if agentType == "sentry" {
104+
os.Setenv("SKIP_VALIDATION", "1")
105+
}
106+
107+
var prompt string
108+
if agentType == "sentry" {
109+
sentryTitle := os.Getenv("SENTRY_TITLE")
110+
sentryStack := os.Getenv("SENTRY_STACKTRACE")
111+
112+
// Detect default branch from the cloned repo
113+
defaultBranch := "main"
114+
if out, err := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD").Output(); err == nil {
115+
if b := strings.TrimSpace(string(out)); b != "" {
116+
defaultBranch = b
117+
}
118+
}
119+
120+
branchName := fmt.Sprintf("fix/sentry-%s", runID)
121+
122+
prompt = fmt.Sprintf(`You are a production bug-fix agent. A runtime error was caught by Sentry.
123+
124+
ERROR: %s
125+
126+
STACKTRACE:
127+
%s
128+
129+
INSTRUCTIONS — follow EXACTLY in order:
130+
131+
1. Use search_code to find the file mentioned in the stacktrace.
132+
2. Use read_file to read that file.
133+
3. Identify the bug. Apply a MINIMAL fix using apply_patch or write_file.
134+
4. Use run_command to create a branch, commit, push, and open a PR:
135+
136+
git checkout -b %s && \
137+
git add -A && \
138+
git commit -m "fix: %s" && \
139+
git push origin %s && \
140+
gh pr create --title "fix: %s" --body "Automated fix for Sentry error: %s" --base %s
141+
142+
RULES:
143+
- Fix ONE file only. Do NOT refactor, do NOT write tests.
144+
- Do NOT run mix, npm, go build, or any build/test commands.
145+
- Do NOT explore the project. Go directly to the file from the stacktrace.
146+
- If you cannot identify the file from the stacktrace, use search_code with a key term from the error.
147+
- You MUST create the PR. The task is NOT complete until the PR is created.`,
148+
sentryTitle, sentryStack,
149+
branchName, sentryTitle, branchName,
150+
sentryTitle, sentryTitle, defaultBranch)
151+
} else {
152+
// Default to CI pipeline logic
153+
prompt = fmt.Sprintf(`The CI pipeline just failed for this repository on branch '%s'.
154+
The failing commit is %s.
155+
156+
Please discover what failed by examining the code, tests, and standard CI configurations
157+
(such as npm test, mix test, go test, etc.) and write a fix. Once fixed, create a PR.`, branch, sha)
158+
}
159+
160+
ctx := context.Background()
161+
162+
backendName := "openrouter"
163+
baseURL := "https://openrouter.ai/api/v1"
164+
provider := llm.NewChatCompletion(baseURL, backendName)
165+
166+
language := "elixir" // Most Zapfy repos are Elixir
167+
queryModel := os.Getenv("MODEL")
168+
if queryModel == "" {
169+
queryModel = "anthropic/claude-sonnet-4"
170+
}
171+
172+
liveClient := live.GetClient()
173+
var reportConfig *live.ReportConfig
174+
if liveURL != "" {
175+
reportConfig = live.DefaultReportConfig()
176+
reportConfig.SetBaseURL(liveURL)
177+
reportConfig.AgentID = agentID
178+
179+
// Register on the Live Dashboard BEFORE starting execution
180+
taskLabel := "Sentry Exception Repair"
181+
if agentType != "sentry" {
182+
taskLabel = "CI Breakdown Repair"
183+
}
184+
reportConfig.Model = queryModel
185+
if err := reportConfig.Connect(agentID, agentType, taskLabel); err != nil {
186+
fmt.Printf("⚠️ Live Dashboard connect failed (non-fatal): %v\n", err)
187+
} else {
188+
fmt.Printf("📡 Connected to Live Dashboard as %s\n", agentID)
189+
}
190+
} else if liveClient != nil {
191+
reportConfig = live.DefaultReportConfig()
192+
reportConfig.AgentID = live.GetAgentID()
193+
}
194+
195+
executor := modes.NewAutonomousExecutorWithLive(provider, cloneDir, queryModel, language, liveClient, reportConfig, backendName)
196+
197+
fmt.Println("🤖 Starting AutoFix process...")
198+
err = executor.Execute(ctx, prompt)
199+
if err != nil {
200+
fmt.Printf("❌ AutoFix failed: %v\n", err)
201+
return err
202+
}
203+
204+
fmt.Println("✅ AutoFix completed successfully.")
205+
return nil
206+
}

0 commit comments

Comments
 (0)