From e3859411ba49546e5bf86c0fa9eb98ba22fbc390 Mon Sep 17 00:00:00 2001 From: fullsend-code <278716306+fullsend-ai-coder[bot]@users.noreply.github.com> Date: Wed, 17 Jun 2026 09:49:49 +0000 Subject: [PATCH] fix(#2378): report failure when agent errors with no commits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the code agent exits non-zero (e.g., 429 RESOURCE_EXHAUSTED) but produces no commits, the status comment previously reported "Success" because runErr stayed nil — the OS-level execution succeeded even though the agent session failed. Root cause: lastExitCode was declared after the status and post-script defers, so neither closure could read it. The post-script then hit the "no changed files — nothing to do" path and exited 0, leaving runErr nil and the status as "success". Changes: - Move lastExitCode declaration before the post-script defer so both closures can reference it - Pass AGENT_EXIT_CODE env var to the post-script so it can distinguish agent errors from intentional no-ops - In post-code.sh, check AGENT_EXIT_CODE at both "nothing to do" exit points (no branch and no changed files); exit 1 when agent errored - Update report_failure_to_issue() to produce a distinct message for agent errors ("Code agent failed") vs post-script errors - Add shell tests covering agent error detection at both exit points and the error comment content Intentional no-change runs (agent exits 0, no commits) are unaffected — the AGENT_EXIT_CODE check only triggers on non-zero exit codes. Closes #2378 --- internal/cli/run.go | 10 +- .../fullsend-repo/scripts/post-code-test.sh | 97 +++++++++++++++++-- .../fullsend-repo/scripts/post-code.sh | 26 ++++- 3 files changed, 123 insertions(+), 10 deletions(-) diff --git a/internal/cli/run.go b/internal/cli/run.go index e705afc63..d874a6339 100644 --- a/internal/cli/run.go +++ b/internal/cli/run.go @@ -507,6 +507,14 @@ func runAgent(ctx context.Context, agentName, fullsendDir, outputBase, targetRep // ADR 0022's zero-trust model. var validationPassed bool + // lastExitCode is declared here (before the post-script and status + // defers) so both closures can read the agent's final exit code. + // When the agent exits non-zero but the Go-level execution succeeds, + // runErr stays nil — this variable lets the post-script and status + // comment distinguish "agent errored" from "agent chose to do nothing". + // See #2378. + var lastExitCode int + // Post-script runs after sandbox cleanup (defers are LIFO). // When a validation_loop is configured, the post-script only runs if // validation passed (ADR 0022). When no validation_loop exists (e.g., @@ -532,6 +540,7 @@ func runAgent(ctx context.Context, agentName, fullsendDir, outputBase, targetRep postCmd := exec.Command(h.PostScript) postCmd.Dir = runDir postCmd.Env = append(os.Environ(), envToList(h.RunnerEnv)...) + postCmd.Env = append(postCmd.Env, fmt.Sprintf("AGENT_EXIT_CODE=%d", lastExitCode)) postCmd.Stdout = os.Stdout postCmd.Stderr = os.Stderr if err := postCmd.Run(); err != nil { @@ -797,7 +806,6 @@ func runAgent(ctx context.Context, agentName, fullsendDir, outputBase, targetRep oidcWg.Wait() }() - var lastExitCode int var runCount int for iteration := 1; iteration <= maxIterations; iteration++ { diff --git a/internal/scaffold/fullsend-repo/scripts/post-code-test.sh b/internal/scaffold/fullsend-repo/scripts/post-code-test.sh index ef1e94213..36dfc0e14 100644 --- a/internal/scaffold/fullsend-repo/scripts/post-code-test.sh +++ b/internal/scaffold/fullsend-repo/scripts/post-code-test.sh @@ -259,15 +259,24 @@ count_closes_test "single-closes-empty-body" \ detect_noop() { local branch="$1" local changed_files="$2" + local agent_exit_code="${3:-0}" - # Step 1: branch check (mirrors lines 64-67 of post-code.sh) + # Step 1: branch check (mirrors post-code.sh section 1) if [ -z "${branch}" ] || [ "${branch}" = "main" ] || [ "${branch}" = "master" ]; then + if [ "${agent_exit_code}" != "0" ]; then + echo "error:branch:Agent exited with code ${agent_exit_code} and did not create a feature branch" + return 1 + fi echo "noop:branch:Agent did not create a feature branch (current: '${branch:-detached HEAD}') — nothing to do" return 0 fi - # Step 2: changed files check (mirrors lines 84-87 of post-code.sh) + # Step 2: changed files check (mirrors post-code.sh section 2) if [ -z "${changed_files}" ]; then + if [ "${agent_exit_code}" != "0" ]; then + echo "error:files:Agent exited with code ${agent_exit_code} and produced no changes" + return 1 + fi echo "noop:files:No changed files in agent's commit(s) — nothing to do" return 0 fi @@ -280,15 +289,17 @@ run_noop_test() { local test_name="$1" local branch="$2" local changed_files="$3" - local expected_prefix="$4" # "noop:branch", "noop:files", or "proceed" + local expected_prefix="$4" # "noop:branch", "noop:files", "error:branch", "error:files", or "proceed" + local agent_exit_code="${5:-0}" local actual - actual="$(detect_noop "${branch}" "${changed_files}")" + actual="$(detect_noop "${branch}" "${changed_files}" "${agent_exit_code}" 2>&1)" || true if [[ "${actual}" != ${expected_prefix}* ]]; then echo "FAIL: ${test_name}" - echo " branch: '${branch}'" - echo " changed_files: '${changed_files}'" + echo " branch: '${branch}'" + echo " changed_files: '${changed_files}'" + echo " agent_exit_code: '${agent_exit_code}'" echo " expected prefix: '${expected_prefix}'" echo " actual: '${actual}'" FAILURES=$((FAILURES + 1)) @@ -324,6 +335,28 @@ run_noop_test "proceed-feature-branch-with-changes" \ run_noop_test "noop-on-main-with-changes" \ "main" "src/widget.go" "noop:branch" +# --- Agent error detection test cases (#2378) --- + +# Agent errored (exit 1) on main with no changes → error via branch check +run_noop_test "error-agent-failed-on-main" \ + "main" "" "error:branch" "1" + +# Agent errored (exit 1) on feature branch with no changes → error via files check +run_noop_test "error-agent-failed-no-changes" \ + "agent/42-fix-widget" "" "error:files" "1" + +# Agent succeeded (exit 0) on feature branch with no changes → noop (not error) +run_noop_test "noop-agent-success-no-changes" \ + "agent/42-fix-widget" "" "noop:files" "0" + +# Agent errored but produced changes → proceed (changes take precedence) +run_noop_test "proceed-agent-failed-with-changes" \ + "agent/42-fix-widget" "src/widget.go" "proceed" "1" + +# Agent errored (exit 2) on detached HEAD → error via branch check +run_noop_test "error-agent-failed-detached-head" \ + "" "" "error:branch" "2" + # --------------------------------------------------------------------------- # Test helper — reimplements the stale branch cleanup decision logic from # post-code.sh section 7a. Given whether a remote branch exists and whether @@ -454,10 +487,23 @@ build_error_comment() { local repo_full_name="$2" local run_id="$3" local github_repository="${4:-}" # GITHUB_REPOSITORY override (org-mode) + local agent_error_exit="${5:-false}" + local agent_exit_code="${6:-unknown}" local run_repo="${github_repository:-${repo_full_name}}" local run_url="https://github.com/${run_repo}/actions/runs/${run_id}" - echo "⚠️ **Post-code script failed** (exit code ${exit_code}) + + if [ "${agent_error_exit}" = "true" ]; then + echo "⚠️ **Code agent failed** (agent exit code ${agent_exit_code}) + +The code agent terminated with an error and produced no PR. + +**Workflow run:** ${run_url} + +Please check the workflow logs for details and retry with \`/fs-code\` \ +if appropriate." + else + echo "⚠️ **Post-code script failed** (exit code ${exit_code}) The code agent completed, but the post-code script failed while \ pushing the branch or creating the PR. @@ -466,6 +512,7 @@ pushing the branch or creating the PR. Please check the workflow logs for details and retry with \`/fs-code\` \ if appropriate." + fi } run_error_comment_test() { @@ -476,9 +523,11 @@ run_error_comment_test() { local check_pattern="$5" local expect_present="$6" local github_repository="${7:-}" # optional GITHUB_REPOSITORY override + local agent_error_exit="${8:-false}" + local agent_exit_code="${9:-unknown}" local actual - actual="$(build_error_comment "${exit_code}" "${repo}" "${run_id}" "${github_repository}")" + actual="$(build_error_comment "${exit_code}" "${repo}" "${run_id}" "${github_repository}" "${agent_error_exit}" "${agent_exit_code}")" if [ "${expect_present}" = "yes" ]; then if ! echo "${actual}" | grep -qF "${check_pattern}"; then @@ -539,6 +588,38 @@ run_error_comment_test "error-comment-non-org-mode-fallback" \ "https://github.com/my-org/my-repo/actions/runs/67890" "yes" \ "" +# --- Agent error comment test cases (#2378) --- + +# Agent error comment should say "Code agent failed" +run_error_comment_test "agent-error-comment-title" \ + "1" "my-org/my-repo" "12345" \ + "Code agent failed" "yes" \ + "" "true" "1" + +# Agent error comment should include agent exit code +run_error_comment_test "agent-error-comment-exit-code" \ + "1" "my-org/my-repo" "12345" \ + "agent exit code 1" "yes" \ + "" "true" "1" + +# Agent error comment should NOT say "Post-code script failed" +run_error_comment_test "agent-error-comment-not-postcode" \ + "1" "my-org/my-repo" "12345" \ + "Post-code script failed" "no" \ + "" "true" "1" + +# Agent error comment should mention no PR was created +run_error_comment_test "agent-error-comment-no-pr" \ + "1" "my-org/my-repo" "12345" \ + "produced no PR" "yes" \ + "" "true" "1" + +# Non-agent error (default) should still say "Post-code script failed" +run_error_comment_test "non-agent-error-default" \ + "1" "my-org/my-repo" "12345" \ + "Post-code script failed" "yes" \ + "" "false" "0" + # --------------------------------------------------------------------------- # Test helper — reimplements the agent artifact stripping logic from # post-code.sh section 2b. Given a list of changed files, returns which diff --git a/internal/scaffold/fullsend-repo/scripts/post-code.sh b/internal/scaffold/fullsend-repo/scripts/post-code.sh index c6e839ab1..8a62ec670 100755 --- a/internal/scaffold/fullsend-repo/scripts/post-code.sh +++ b/internal/scaffold/fullsend-repo/scripts/post-code.sh @@ -75,7 +75,20 @@ report_failure_to_issue() { export GH_TOKEN="${PUSH_TOKEN}" fi local run_url="${GITHUB_SERVER_URL:-https://github.com}/${GITHUB_REPOSITORY:-${REPO_FULL_NAME}}/actions/runs/${GITHUB_RUN_ID:-unknown}" - local comment_body="⚠️ **Post-code script failed** (exit code ${exit_code}) + + local comment_body + if [ "${AGENT_ERROR_EXIT:-false}" = "true" ]; then + # Agent itself errored (non-zero exit) and produced no changes. + comment_body="⚠️ **Code agent failed** (agent exit code ${AGENT_EXIT_CODE:-unknown}) + +The code agent terminated with an error and produced no PR. + +**Workflow run:** ${run_url} + +Please check the workflow logs for details and retry with \`/fs-code\` \ +if appropriate." + else + comment_body="⚠️ **Post-code script failed** (exit code ${exit_code}) The code agent completed, but the post-code script failed while \ pushing the branch or creating the PR. @@ -84,6 +97,7 @@ pushing the branch or creating the PR. Please check the workflow logs for details and retry with \`/fs-code\` \ if appropriate." + fi echo "::warning::Posting failure comment to issue #${ISSUE_NUMBER}..." gh issue comment "${ISSUE_NUMBER}" \ @@ -99,6 +113,11 @@ trap report_failure_to_issue ERR BRANCH="$(git branch --show-current)" if [ -z "${BRANCH}" ] || [ "${BRANCH}" = "main" ] || [ "${BRANCH}" = "master" ]; then + if [ "${AGENT_EXIT_CODE:-0}" != "0" ]; then + echo "::error::Agent exited with code ${AGENT_EXIT_CODE} and did not create a feature branch" + AGENT_ERROR_EXIT=true + exit 1 + fi echo "::notice::Agent did not create a feature branch (current: '${BRANCH:-detached HEAD}') — nothing to do" exit 0 fi @@ -119,6 +138,11 @@ else fi if [ -z "${CHANGED_FILES}" ]; then + if [ "${AGENT_EXIT_CODE:-0}" != "0" ]; then + echo "::error::Agent exited with code ${AGENT_EXIT_CODE} and produced no changes" + AGENT_ERROR_EXIT=true + exit 1 + fi echo "::notice::No changed files in agent's commit(s) — nothing to do" exit 0 fi