From 73939e278b4294a880080eaa36a87e098a25a683 Mon Sep 17 00:00:00 2001 From: Abir Abbas Date: Fri, 24 Jul 2026 15:50:40 -0400 Subject: [PATCH 1/4] fix(go): make resolve writable and verify remote pushes --- go/internal/orch/resolve.go | 63 ++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/go/internal/orch/resolve.go b/go/internal/orch/resolve.go index 571c6bf..f76f337 100644 --- a/go/internal/orch/resolve.go +++ b/go/internal/orch/resolve.go @@ -52,6 +52,10 @@ func ResolveHandler(ctx context.Context, deps *Deps, input map[string]any) (any, return nil, err } cfg.EnableGithubPR = false // the PR already exists — never create + permissionMode := cfg.PermissionMode + if permissionMode == "" { + permissionMode = "auto" + } if in.PRNumber == 0 || in.HeadBranch == "" || in.RepoURL == "" || in.PRURL == "" { return nil, errors.New( @@ -135,6 +139,7 @@ func ResolveHandler(ctx context.Context, deps *Deps, input map[string]any) (any, resolverModel = "sonnet" } + remoteBefore := remoteBranchSHA(ctx, repoPath, in.HeadBranch) resolveResult, err := deps.Call(ctx, "run_pr_resolver", map[string]any{ "repo_path": repoPath, "pr_number": in.PRNumber, @@ -148,7 +153,7 @@ func ResolveHandler(ctx context.Context, deps *Deps, input map[string]any) (any, "goal": in.Goal, "additional_context": in.AdditionalContext, "model": resolverModel, - "permission_mode": cfg.PermissionMode, + "permission_mode": permissionMode, "ai_provider": cfg.AIProvider(), }, "run_pr_resolver") if err != nil { @@ -170,6 +175,19 @@ func ResolveHandler(ctx context.Context, deps *Deps, input map[string]any) (any, "resolve", "push", "error") } } + if resolverReportInvalid(resolveResult) { + remoteAfter := remoteBranchSHA(ctx, repoPath, in.HeadBranch) + if remoteBefore != "" && remoteAfter != "" && remoteBefore != remoteAfter { + resolveResult["pushed"] = true + resolveResult["fixed"] = true + resolveResult["commit_shas"] = remoteCommitSHAs(ctx, repoPath, remoteBefore, remoteAfter) + resolveResult["files_changed"] = remoteFilesChanged(ctx, repoPath, remoteBefore, remoteAfter) + resolveResult["summary"] = "work pushed; agent report invalid" + resolveResult["error_message"] = "agent report invalid; verified work pushed to the remote branch" + pushed = true + deps.Note(ctx, "Resolve: agent report invalid, but verified work pushed to the remote branch", "resolve", "report", "warning") + } + } // Capture the new HEAD SHA after push so the CI watcher can anchor verdicts // to this specific commit (avoids the previous HEAD's stale check states). @@ -264,6 +282,49 @@ func ResolveHandler(ctx context.Context, deps *Deps, input map[string]any) (any, }, nil } +func resolverReportInvalid(result map[string]any) bool { + return mapStr(result, "error_message", "") == "PR resolver agent failed to produce a valid result." || + mapStr(result, "summary", "") == "PR resolver agent failed to produce a valid result." +} + +func remoteBranchSHA(ctx context.Context, repoPath, branch string) string { + r := runGit(ctx, repoPath, "ls-remote", "origin", "refs/heads/"+branch) + if r.ExitCode != 0 { + return "" + } + fields := strings.Fields(r.Stdout) + if len(fields) == 0 { + return "" + } + return fields[0] +} + +func remoteCommitSHAs(ctx context.Context, repoPath, before, after string) []string { + r := runGit(ctx, repoPath, "rev-list", "--reverse", before+".."+after) + if r.ExitCode != 0 { + return []string{} + } + return nonEmptyLines(r.Stdout) +} + +func remoteFilesChanged(ctx context.Context, repoPath, before, after string) []string { + r := runGit(ctx, repoPath, "diff", "--name-only", before, after) + if r.ExitCode != 0 { + return []string{} + } + return nonEmptyLines(r.Stdout) +} + +func nonEmptyLines(s string) []string { + var out []string + for _, line := range strings.Split(s, "\n") { + if line = strings.TrimSpace(line); line != "" { + out = append(out, line) + } + } + return out +} + // attemptBaseMerge fetches base_branch and merges it into the current branch. // Ports _attempt_base_merge (app.py:1960). Returns (merge_state, conflicted) // where merge_state is "clean" (already up to date), "merged" (merge succeeded), From e87df509a517c2d0cd394a5b09298a0bc84a0471 Mon Sep 17 00:00:00 2001 From: Abir Abbas Date: Fri, 24 Jul 2026 15:50:44 -0400 Subject: [PATCH 2/4] fix(go): honor existing build work branches --- go/internal/fast/build.go | 15 +++++++++++++-- go/internal/prompts/gitops/git_init.go | 13 ++++++++++--- go/internal/roles/gitops/workspace.go | 8 +++++--- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/go/internal/fast/build.go b/go/internal/fast/build.go index 7fdbf6b..a0f0f13 100644 --- a/go/internal/fast/build.go +++ b/go/internal/fast/build.go @@ -112,6 +112,7 @@ type buildInput struct { RepoURL string `json:"repo_url"` ArtifactsDir string `json:"artifacts_dir"` AdditionalContext string `json:"additional_context"` + WorkBranch string `json:"work_branch"` Config map[string]any `json:"config"` } @@ -196,7 +197,7 @@ func Build(ctx context.Context, deps *Deps, input map[string]any) (any, error) { // ── 1. GIT INIT (1 attempt, non-fatal) ────────────────────────────────── deps.note(ctx, "Fast build: git init", "fast_build", "git_init") var gitConfig map[string]any - rawGit, gitErr := deps.Call(ctx, node+".run_git_init", map[string]any{ + gitInput := map[string]any{ "repo_path": repoPath, "goal": in.Goal, "artifacts_dir": absArtifactsDir, @@ -204,12 +205,22 @@ func Build(ctx context.Context, deps *Deps, input map[string]any) (any, error) { "permission_mode": cfg.PermissionMode, "ai_provider": aiProvider, "build_id": "", - }) + } + if in.WorkBranch != "" { + gitInput["work_branch"] = in.WorkBranch + } + rawGit, gitErr := deps.Call(ctx, node+".run_git_init", gitInput) if gitErr != nil { + if in.WorkBranch != "" { + return nil, fmt.Errorf("work branch %q could not be initialized: %w", in.WorkBranch, gitErr) + } deps.note(ctx, fmt.Sprintf("Git init exception (non-fatal): %s", gitErr), "fast_build", "git_init", "error") } else { gitInit := rawGit + if in.WorkBranch != "" && (!mapBool(gitInit, "success") || getString(gitInit, "integration_branch", "") != in.WorkBranch) { + return nil, fmt.Errorf("work branch %q does not exist on the remote or could not be checked out", in.WorkBranch) + } if mapBool(gitInit, "success") { gitConfig = map[string]any{ "integration_branch": gitInit["integration_branch"], diff --git a/go/internal/prompts/gitops/git_init.go b/go/internal/prompts/gitops/git_init.go index 7a6a3c4..6643d3a 100644 --- a/go/internal/prompts/gitops/git_init.go +++ b/go/internal/prompts/gitops/git_init.go @@ -88,9 +88,10 @@ const GitInitSystemPrompt = "You are a DevOps engineer setting up a git-based fe // GitInitOptions carries the arguments for GitInitTaskPrompt. type GitInitOptions struct { - RepoPath string - Goal string - BuildID string + RepoPath string + Goal string + BuildID string + WorkBranch string } // GitInitTaskPrompt builds the task prompt for the git initialization agent @@ -104,6 +105,9 @@ func GitInitTaskPrompt(opts GitInitOptions) string { if opts.BuildID != "" { sections = append(sections, fmt.Sprintf("- **Build ID**: `%s` (prefix integration branch slug with this)", opts.BuildID)) } + if opts.WorkBranch != "" { + sections = append(sections, fmt.Sprintf("- **Existing work branch**: `%s` (fetch and check out this remote branch; do not create a new integration branch)", opts.WorkBranch)) + } sections = append(sections, "\n## Your Task\n"+ "1. Check if `.git` exists in the repository path.\n"+ @@ -113,6 +117,9 @@ func GitInitTaskPrompt(opts GitInitOptions) string { "5. Create the `.worktrees/` directory and ensure it's in `.gitignore`.\n"+ "6. Detect the remote origin URL and default branch (if any).\n"+ "7. Return a GitInitResult JSON object.") + if opts.WorkBranch != "" { + sections = append(sections, fmt.Sprintf("\n## Existing Work Branch\nFetch `origin/%s` and check it out directly as the integration branch. Do not create a new branch. If it does not exist on the remote, return `success=false` with a clear error before any planning or coding work.", opts.WorkBranch)) + } return strings.Join(sections, "\n") } diff --git a/go/internal/roles/gitops/workspace.go b/go/internal/roles/gitops/workspace.go index cec449b..8cb3da4 100644 --- a/go/internal/roles/gitops/workspace.go +++ b/go/internal/roles/gitops/workspace.go @@ -23,6 +23,7 @@ type gitInitInput struct { AIProvider string `json:"ai_provider"` PreviousError string `json:"previous_error"` BuildID string `json:"build_id"` + WorkBranch string `json:"work_branch"` } // RunGitInit initializes the git repo and creates the integration branch. @@ -40,9 +41,10 @@ func RunGitInit(ctx context.Context, deps *Deps, input map[string]any) (any, err deps.App.Note(ctx, fmt.Sprintf("Git init starting for: %s", truncateRunes(in.Goal, 80)), "git_init", "start") taskPrompt := gitprompts.GitInitTaskPrompt(gitprompts.GitInitOptions{ - RepoPath: in.RepoPath, - Goal: in.Goal, - BuildID: in.BuildID, + RepoPath: in.RepoPath, + Goal: in.Goal, + BuildID: in.BuildID, + WorkBranch: in.WorkBranch, }) // Build system prompt with error context if retrying. From 3121b602a8c0dc5f88bffe8d6a96feb001b88ada Mon Sep 17 00:00:00 2001 From: Abir Abbas Date: Fri, 24 Jul 2026 15:50:50 -0400 Subject: [PATCH 3/4] fix(go): require affected tests before resolver pushes --- go/internal/prompts/advisor/pr_resolver.go | 3 +++ go/internal/prompts/advisor/testdata/pr_resolver_rendered.txt | 3 ++- .../prompts/advisor/testdata/pr_resolver_rendered_merged.txt | 3 ++- go/internal/prompts/advisor/testdata/pr_resolver_system.txt | 3 ++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/go/internal/prompts/advisor/pr_resolver.go b/go/internal/prompts/advisor/pr_resolver.go index d374e10..d289c5c 100644 --- a/go/internal/prompts/advisor/pr_resolver.go +++ b/go/internal/prompts/advisor/pr_resolver.go @@ -138,6 +138,7 @@ func PRResolverTaskPrompt(opts PRResolverTaskOptions) string { taskLines = append(taskLines, strconv.Itoa(step)+". Re-run failing tests locally to confirm they pass.") step++ + taskLines = append(taskLines, "Before committing, run the test suite for every package or module touched, plus gofmt and repository-standard linters where applicable. Do NOT push if any affected test or required check fails; report every test command and outcome in the result.") taskLines = append(taskLines, strconv.Itoa(step)+". Commit + `git push origin "+opts.HeadBranch+"` — do NOT create "+ "a new PR.") @@ -171,6 +172,8 @@ push. The PR already exists — do NOT create a new one. branch). 5. You have re-run the relevant tests locally and they pass. +Before committing, you MUST run the test suite for every package or module you touched, plus gofmt and any repository-standard linters where applicable. You MUST NOT push if any affected test or required check fails. Report every test command and its outcome in your result. + ## ABSOLUTELY FORBIDDEN — these are workarounds, not fixes You MUST NOT do any of the following to make CI green or to "address" a diff --git a/go/internal/prompts/advisor/testdata/pr_resolver_rendered.txt b/go/internal/prompts/advisor/testdata/pr_resolver_rendered.txt index 6dba7d8..e746c0e 100644 --- a/go/internal/prompts/advisor/testdata/pr_resolver_rendered.txt +++ b/go/internal/prompts/advisor/testdata/pr_resolver_rendered.txt @@ -41,5 +41,6 @@ Keep API stable. 3. Fix every failing CI check by changing PRODUCTION code (no silenced tests). 4. Address every actionable review comment, recording each one in `addressed_comments` (true/false + brief note). 5. Re-run failing tests locally to confirm they pass. +Before committing, run the test suite for every package or module touched, plus gofmt and repository-standard linters where applicable. Do NOT push if any affected test or required check fails; report every test command and outcome in the result. 6. Commit + `git push origin feature` — do NOT create a new PR. -7. Return a `PRResolveResult` JSON object. \ No newline at end of file +7. Return a `PRResolveResult` JSON object. diff --git a/go/internal/prompts/advisor/testdata/pr_resolver_rendered_merged.txt b/go/internal/prompts/advisor/testdata/pr_resolver_rendered_merged.txt index 2832800..87e80cc 100644 --- a/go/internal/prompts/advisor/testdata/pr_resolver_rendered_merged.txt +++ b/go/internal/prompts/advisor/testdata/pr_resolver_rendered_merged.txt @@ -18,5 +18,6 @@ The orchestrator already merged `origin/main` into the head branch with no confl 2. Fix every failing CI check by changing PRODUCTION code (no silenced tests). 3. Address every actionable review comment, recording each one in `addressed_comments` (true/false + brief note). 4. Re-run failing tests locally to confirm they pass. +Before committing, run the test suite for every package or module touched, plus gofmt and repository-standard linters where applicable. Do NOT push if any affected test or required check fails; report every test command and outcome in the result. 5. Commit + `git push origin feature` — do NOT create a new PR. -6. Return a `PRResolveResult` JSON object. \ No newline at end of file +6. Return a `PRResolveResult` JSON object. diff --git a/go/internal/prompts/advisor/testdata/pr_resolver_system.txt b/go/internal/prompts/advisor/testdata/pr_resolver_system.txt index b159ab8..7cf7574 100644 --- a/go/internal/prompts/advisor/testdata/pr_resolver_system.txt +++ b/go/internal/prompts/advisor/testdata/pr_resolver_system.txt @@ -20,6 +20,7 @@ push. The PR already exists — do NOT create a new one. 4. The fix is committed and pushed to the PR's head branch (not a new branch). 5. You have re-run the relevant tests locally and they pass. +Before committing, you MUST run the test suite for every package or module you touched, plus gofmt and any repository-standard linters where applicable. You MUST NOT push if any affected test or required check fails. Report every test command and its outcome in your result. ## ABSOLUTELY FORBIDDEN — these are workarounds, not fixes @@ -132,4 +133,4 @@ Return a `PRResolveResult` JSON object with: - READ to inspect source and test files - EDIT/WRITE to modify code (and to resolve conflict markers) - BASH for running tests, git operations, and `gh run view --log-failed` if - you need more log context than what was provided \ No newline at end of file + you need more log context than what was provided From d88c69a5eb9e86c0d186faf5deecb88b2a62075e Mon Sep 17 00:00:00 2001 From: Abir Abbas Date: Fri, 24 Jul 2026 15:57:03 -0400 Subject: [PATCH 4/4] test(prompts): align pr_resolver goldens with rendered output Co-Authored-By: Claude Fable 5 --- go/internal/prompts/advisor/testdata/pr_resolver_rendered.txt | 2 +- .../prompts/advisor/testdata/pr_resolver_rendered_merged.txt | 2 +- go/internal/prompts/advisor/testdata/pr_resolver_system.txt | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/go/internal/prompts/advisor/testdata/pr_resolver_rendered.txt b/go/internal/prompts/advisor/testdata/pr_resolver_rendered.txt index e746c0e..ec75a74 100644 --- a/go/internal/prompts/advisor/testdata/pr_resolver_rendered.txt +++ b/go/internal/prompts/advisor/testdata/pr_resolver_rendered.txt @@ -43,4 +43,4 @@ Keep API stable. 5. Re-run failing tests locally to confirm they pass. Before committing, run the test suite for every package or module touched, plus gofmt and repository-standard linters where applicable. Do NOT push if any affected test or required check fails; report every test command and outcome in the result. 6. Commit + `git push origin feature` — do NOT create a new PR. -7. Return a `PRResolveResult` JSON object. +7. Return a `PRResolveResult` JSON object. \ No newline at end of file diff --git a/go/internal/prompts/advisor/testdata/pr_resolver_rendered_merged.txt b/go/internal/prompts/advisor/testdata/pr_resolver_rendered_merged.txt index 87e80cc..e197144 100644 --- a/go/internal/prompts/advisor/testdata/pr_resolver_rendered_merged.txt +++ b/go/internal/prompts/advisor/testdata/pr_resolver_rendered_merged.txt @@ -20,4 +20,4 @@ The orchestrator already merged `origin/main` into the head branch with no confl 4. Re-run failing tests locally to confirm they pass. Before committing, run the test suite for every package or module touched, plus gofmt and repository-standard linters where applicable. Do NOT push if any affected test or required check fails; report every test command and outcome in the result. 5. Commit + `git push origin feature` — do NOT create a new PR. -6. Return a `PRResolveResult` JSON object. +6. Return a `PRResolveResult` JSON object. \ No newline at end of file diff --git a/go/internal/prompts/advisor/testdata/pr_resolver_system.txt b/go/internal/prompts/advisor/testdata/pr_resolver_system.txt index 7cf7574..44194a0 100644 --- a/go/internal/prompts/advisor/testdata/pr_resolver_system.txt +++ b/go/internal/prompts/advisor/testdata/pr_resolver_system.txt @@ -20,6 +20,7 @@ push. The PR already exists — do NOT create a new one. 4. The fix is committed and pushed to the PR's head branch (not a new branch). 5. You have re-run the relevant tests locally and they pass. + Before committing, you MUST run the test suite for every package or module you touched, plus gofmt and any repository-standard linters where applicable. You MUST NOT push if any affected test or required check fails. Report every test command and its outcome in your result. ## ABSOLUTELY FORBIDDEN — these are workarounds, not fixes @@ -133,4 +134,4 @@ Return a `PRResolveResult` JSON object with: - READ to inspect source and test files - EDIT/WRITE to modify code (and to resolve conflict markers) - BASH for running tests, git operations, and `gh run view --log-failed` if - you need more log context than what was provided + you need more log context than what was provided \ No newline at end of file