diff --git a/internal/cli/run.go b/internal/cli/run.go index 5ff832bd6..fd7d544d4 100644 --- a/internal/cli/run.go +++ b/internal/cli/run.go @@ -592,7 +592,7 @@ func runAgent(agentName, fullsendDir, outputBase, targetRepo, fullsendBinary str break } - printer.StepFail("Validation failed: " + strings.TrimSpace(string(valOut))) + printer.StepFail("Validation failed: " + validationFailMessage(valOut, valErr)) if iteration < maxIterations { printer.StepInfo(fmt.Sprintf("Will retry (%d iterations remaining)", maxIterations-iteration)) } @@ -936,6 +936,16 @@ func bootstrapEnv(sandboxName, repoDir string, h *harness.Harness) error { return nil } +// validationFailMessage returns a human-readable message for a validation +// script failure. When the script produces output, that output is used; +// otherwise it falls back to the exec error string (e.g. ENOENT / EACCES). +func validationFailMessage(output []byte, execErr error) string { + if msg := strings.TrimSpace(string(output)); msg != "" { + return msg + } + return execErr.Error() +} + // envToList converts a map of env vars to a sorted list of KEY=VALUE strings. func envToList(env map[string]string) []string { keys := make([]string, 0, len(env)) diff --git a/internal/cli/run_test.go b/internal/cli/run_test.go index ca05647f5..ae6f83270 100644 --- a/internal/cli/run_test.go +++ b/internal/cli/run_test.go @@ -915,3 +915,23 @@ func TestDownloadReleaseBinary_ChecksumMatch(t *testing.T) { require.NoError(t, err) assert.Equal(t, "good binary", string(data)) } + +func TestValidationFailMessage_UsesOutputWhenPresent(t *testing.T) { + msg := validationFailMessage([]byte("check failed: lint errors"), fmt.Errorf("exit status 1")) + assert.Equal(t, "check failed: lint errors", msg) +} + +func TestValidationFailMessage_FallsBackToError(t *testing.T) { + msg := validationFailMessage([]byte(""), fmt.Errorf("exec: \"missing-script\": executable file not found in $PATH")) + assert.Equal(t, "exec: \"missing-script\": executable file not found in $PATH", msg) +} + +func TestValidationFailMessage_FallsBackWhenWhitespaceOnly(t *testing.T) { + msg := validationFailMessage([]byte(" \n\t "), fmt.Errorf("exit status 127")) + assert.Equal(t, "exit status 127", msg) +} + +func TestValidationFailMessage_TrimsOutput(t *testing.T) { + msg := validationFailMessage([]byte(" some output\n"), fmt.Errorf("exit status 1")) + assert.Equal(t, "some output", msg) +}