9bb9644684
* fix(backend): make repository setup script failures non-fatal
A failing repository setup script aborted environment preparation and
stopped the agent ("Environment setup failed"), and Resume / Start-fresh
couldn't recover because each relaunch re-ran the same failing script.
runWorktreeSetupScript (run inside worktree creation) treated a non-zero
exit as fatal: it deleted the worktree and returned an error, which
cascaded to EnvPrepareResult{Success: false} and aborted the launch. This
contradicted the already-non-fatal task/profile-level setup script path.
The repo-level script is now non-fatal too: on failure it logs a warning,
records SetupScriptWarning on the worktree, keeps the worktree, and lets
the launch proceed. The env preparer surfaces it on the "Create worktree"
step so the panel shows completed_with_warnings instead of failed. The
full script output still streams to the script_execution chat message.
Adds backend regression tests and an e2e test (verified red before / green
after) asserting the agent still launches when the repo setup script fails.
* fix(backend): preserve base-branch warning when setup script also fails
Addresses review feedback: the post-Create block surfacing SetupScriptWarning
on the "Create worktree" step unconditionally overwrote BaseBranchFallbackWarning
when both occurred, silently dropping the "wrong branch" notice. Append instead
of replace so both survive.
Also updates TestWorktreePreparer_SetupScriptFailure_KeepsCreateWorktreeStepCompleted
to the non-fatal contract: a failed setup script now yields a successful prepare
with a warning on the step, not a failed prepare.
111 lines
3.5 KiB
Go
111 lines
3.5 KiB
Go
package worktree
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"testing"
|
|
)
|
|
|
|
// fakeScriptHandler simulates the script message handler. ExecuteSetupScript
|
|
// returns setupErr so tests can drive the setup-script failure path without a
|
|
// real task service / shell.
|
|
type fakeScriptHandler struct {
|
|
setupErr error
|
|
setupRuns int
|
|
}
|
|
|
|
func (f *fakeScriptHandler) ExecuteSetupScript(_ context.Context, _ ScriptExecutionRequest) error {
|
|
f.setupRuns++
|
|
return f.setupErr
|
|
}
|
|
|
|
func (f *fakeScriptHandler) ExecuteCleanupScript(_ context.Context, _ ScriptExecutionRequest) error {
|
|
return nil
|
|
}
|
|
|
|
func newManagerForSetupTest(t *testing.T, provider RepositoryProvider, handler ScriptMessageHandler) *Manager {
|
|
t.Helper()
|
|
cfg := newTestConfig(t)
|
|
mgr, err := NewManager(cfg, newMockStore(), newTestLogger())
|
|
if err != nil {
|
|
t.Fatalf("NewManager failed: %v", err)
|
|
}
|
|
if provider != nil {
|
|
mgr.SetRepositoryProvider(provider)
|
|
}
|
|
if handler != nil {
|
|
mgr.SetScriptMessageHandler(handler)
|
|
}
|
|
return mgr
|
|
}
|
|
|
|
// TestManagerCreate_SetupScriptFailure_NonFatal covers the regression where a
|
|
// failing repository setup script aborted environment preparation (and the
|
|
// launch) instead of being surfaced as a non-fatal warning. The worktree must
|
|
// survive the failure and the warning must be recorded on the worktree so the
|
|
// UI can show it without blocking the task.
|
|
func TestManagerCreate_SetupScriptFailure_NonFatal(t *testing.T) {
|
|
repoPath := initGitRepoForWorktreeTest(t)
|
|
provider := &fakeRepoProvider{
|
|
repo: &Repository{ID: "repo-setupfail", SetupScript: "make install"},
|
|
}
|
|
handler := &fakeScriptHandler{setupErr: errors.New("script exited with code 2")}
|
|
mgr := newManagerForSetupTest(t, provider, handler)
|
|
|
|
wt, err := mgr.Create(context.Background(), CreateRequest{
|
|
TaskID: "task-setupfail",
|
|
SessionID: "session-setupfail",
|
|
TaskTitle: "Setup script fails",
|
|
RepositoryID: "repo-setupfail",
|
|
RepositoryPath: repoPath,
|
|
BaseBranch: "main",
|
|
TaskDirName: "task-setupfail",
|
|
RepoName: "repo-setupfail",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Create() should not fail when the setup script fails, got: %v", err)
|
|
}
|
|
if wt == nil {
|
|
t.Fatal("expected non-nil worktree after non-fatal setup failure")
|
|
}
|
|
if handler.setupRuns != 1 {
|
|
t.Fatalf("setup script run count = %d, want 1", handler.setupRuns)
|
|
}
|
|
// The worktree directory must survive — the agent needs it to work in.
|
|
if _, statErr := os.Stat(wt.Path); statErr != nil {
|
|
t.Fatalf("expected worktree dir to survive setup failure, stat err = %v", statErr)
|
|
}
|
|
if wt.SetupScriptWarning == "" {
|
|
t.Fatal("expected SetupScriptWarning to be set when the setup script fails")
|
|
}
|
|
}
|
|
|
|
// TestManagerCreate_SetupScriptSuccess_NoWarning guards against the warning
|
|
// being set on the happy path.
|
|
func TestManagerCreate_SetupScriptSuccess_NoWarning(t *testing.T) {
|
|
repoPath := initGitRepoForWorktreeTest(t)
|
|
provider := &fakeRepoProvider{
|
|
repo: &Repository{ID: "repo-setupok", SetupScript: "make install"},
|
|
}
|
|
handler := &fakeScriptHandler{setupErr: nil}
|
|
mgr := newManagerForSetupTest(t, provider, handler)
|
|
|
|
wt, err := mgr.Create(context.Background(), CreateRequest{
|
|
TaskID: "task-setupok",
|
|
SessionID: "session-setupok",
|
|
TaskTitle: "Setup script succeeds",
|
|
RepositoryID: "repo-setupok",
|
|
RepositoryPath: repoPath,
|
|
BaseBranch: "main",
|
|
TaskDirName: "task-setupok",
|
|
RepoName: "repo-setupok",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Create() unexpected error: %v", err)
|
|
}
|
|
if wt.SetupScriptWarning != "" {
|
|
t.Fatalf("expected no setup-script warning on success, got %q", wt.SetupScriptWarning)
|
|
}
|
|
}
|