Skip to content

fix: propagate errors from planning prompt builders - #8

Open
andrewwhitecdw wants to merge 2 commits into
mainfrom
codequality/planning-propagate-errors-from-planning-prompt
Open

fix: propagate errors from planning prompt builders#8
andrewwhitecdw wants to merge 2 commits into
mainfrom
codequality/planning-propagate-errors-from-planning-prompt

Conversation

@andrewwhitecdw

Copy link
Copy Markdown
Owner

This PR addresses the following issue in go/internal/roles/planning/planning.go: propagate errors from planning prompt builders.

Changes

  • go/internal/roles/planning/planning.go: propagate errors from planning prompt builders.

Details

--- a/go/internal/roles/planning/planning.go
+++ b/go/internal/roles/planning/planning.go
@@ -38,8 +38,17 @@ type Deps struct {
 	AgentFieldServer string
 }
 
+// prompt-builder seams let tests inject failures for the constructors that
+// return (systemPrompt, error). The handlers previously ignored that error.
+var (
+	productManagerPromptsFn = prompts.ProductManagerPrompts
+	architectPromptsFn      = prompts.ArchitectPrompts
+	techLeadPromptsFn       = prompts.TechLeadPrompts
+	sprintPlannerPromptsFn  = prompts.SprintPlannerPrompts
+)
+
 // executionContextFrom is a seam over agent.ExecutionContextFrom so tests can
 // inject a run_id / execution_id (the SDK's context key is unexported, so an
 // external test cannot seed an ExecutionContext into a ctx directly).
@@ -95,14 +104,17 @@ func RunProductManager(ctx context.Context, deps *Deps, input map[string]any) (
 	}
 
-	systemPrompt, _ := prompts.ProductManagerPrompts(prompts.ProductManagerPromptsOpts{
+	systemPrompt, err := productManagerPromptsFn(prompts.ProductManagerPromptsOpts{
 		Goal:               goal,
 		RepoPath:           repoPath,
 		PRDPath:            paths["prd"],
 		AdditionalContext:  additionalContext,
 		PriorUserResponses: initialPrior,
 	})
+	if err != nil {
+		return nil, err
+	}
 
 	provider, err := runtimex.RuntimeToHarnessAdapter(aiProvider)
@@ -218,14 +231,17 @@ func RunArchitect(ctx context.Context, deps *Deps, input map[string]any) (any, e
 	}
 
-	systemPrompt, _ := prompts.ArchitectPrompts(prompts.ArchitectPromptsOpts{
+	systemPrompt, err := architectPromptsFn(prompts.ArchitectPromptsOpts{
 		PRD:              prdObj,
 		RepoPath:         repoPath,
 		PRDPath:          paths["prd"],
 		ArchitecturePath: paths["architecture"],
 		Feedback:         feedback,
 	})
+	if err != nil {
+		return nil, err
+	}
 
 	taskPrompt := prompts.ArchitectTaskPrompt(prompts.ArchitectTaskPromptOpts{
@@ -282,12 +299,15 @@ func RunTechLead(ctx context.Context, deps *Deps, input map[string]any) (any, er
 	}
 
-	systemPrompt, _ := prompts.TechLeadPrompts(prompts.TechLeadPromptsOpts{
+	systemPrompt, err := techLeadPromptsFn(prompts.TechLeadPromptsOpts{
 		PRDPath:          paths["prd"],
 		ArchitecturePath: paths["architecture"],
 		RevisionNumber:   revisionNumber,
 	})
+	if err != nil {
+		return nil, err
+	}
 
 	taskPrompt := prompts.TechLeadTaskPrompt(prompts.TechLeadTaskPromptOpts{
@@ -367,18 +390,21 @@ func RunSprintPlanner(ctx context.Context, deps *Deps, input map[string]any) (an
 	}
 
-	systemPrompt, _ := prompts.SprintPlannerPrompts(prompts.SprintPlannerPromptsOpts{
+	systemPrompt, err := sprintPlannerPromptsFn(prompts.SprintPlannerPromptsOpts{
 		PRD:              prdObj,
 		Architecture:     archObj,
 		RepoPath:         repoPath,
 		PRDPath:          paths["prd"],
 		ArchitecturePath: paths["architecture"],
 	})
+	if err != nil {
+		return nil, err
+	}
 
 	prdMap, err := toMap(&prdObj)

Tests

  • go/internal/roles/planning/planning_test.go
--- /dev/null
+++ b/go/internal/roles/planning/planning_test.go
@@ -0,0 +1,37 @@
+package planning
+
+import (
+	"context"
+	"errors"
+	"strings"
+	"testing"
+
+	prompts "github.com/Agent-Field/SWE-AF/go/internal/prompts/planning"
+)
+
+type noteApp struct{}
+
+func (noteApp) Note(ctx context.Context, message, label, state string) {}
+
+func TestTechLeadPromptErrorPropagated(t *testing.T) {
+	old := techLeadPromptsFn
+	techLeadPromptsFn = func(_ prompts.TechLeadPromptsOpts) (string, error) {
+		return "", errors.New("prompt builder failure")
+	}
+	defer func() { techLeadPromptsFn = old }()
+
+	ctx := context.Background()
+	deps := &Deps{
+		App: noteApp{},
+	}
+	input := map[string]any{
+		"repo_path":   t.TempDir(),
+		"ai_provider": "invalid",
+	}
+
+	_, err := RunTechLead(ctx, deps, input)
+	if err == nil {
+		t.Fatal("expected prompt builder error to be propagated")
+	}
+	if !strings.Contains(err.Error(), "prompt builder failure") {
+		t.Fatalf("unexpected error: %v", err)
+	}
+}

AbirAbbas and others added 2 commits July 31, 2026 22:23
…ude/sonnet (Agent-Field#120)

Direct reasoner calls hard-coded ai_provider=claude and model=sonnet,
so on an OpenRouter-only deployment (the common cloud setup) every
direct role call tried the claude harness and failed instantly - even
though config.DefaultRuntime() already auto-selects open_code there and
the orchestrators already resolve properly. Resolve absent runtime and
model inputs at call time through the existing config cascade
(SWE_DEFAULT_RUNTIME, tier env vars, OpenRouter auto-default); explicit
input values are untouched. Adds config.DefaultRoleModel backed by
ResolveRuntimeModels/RoleToTier so coding and gitops roles pick their
tier-correct model.

Verified live on a Railway control plane where run_product_manager
failed in 500ms with the old defaults and succeeded via opencode with
provider overrides.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants