diff --git a/actions/setup/js/handle_agent_failure.cjs b/actions/setup/js/handle_agent_failure.cjs index 2891c32955a..becd2131f85 100644 --- a/actions/setup/js/handle_agent_failure.cjs +++ b/actions/setup/js/handle_agent_failure.cjs @@ -4,7 +4,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); const { getDetectionCautionAlert, getFooterAgentFailureIssueMessage, getFooterAgentFailureCommentMessage, generateXMLMarker } = require("./messages.cjs"); -const { renderTemplate, renderTemplateFromFile, getPromptPath } = require("./messages_core.cjs"); +const { renderTemplate, renderTemplateFromFile, getPromptPath, renderFilesList } = require("./messages_core.cjs"); const { getCurrentBranch } = require("./get_current_branch.cjs"); const { createExpirationLine, extractExpirationDate, generateFooterWithExpiration } = require("./ephemerals.cjs"); const { MAX_SUB_ISSUES, getSubIssueCount } = require("./sub_issue_helpers.cjs"); @@ -42,6 +42,7 @@ const ELLIPSIS_LENGTH = ELLIPSIS.length; const ENGINE_RATE_LIMIT_429_RE = /(?:\b429\b[\s\S]{0,120}(?:too many requests|rate[\s-]*limit)|\brate_limit_(?:error|exceeded)\b|capierror:\s*429|failed to get response from the ai model[\s\S]{0,120}\b429\b|exceeded your rate limit for utility models)/i; const ENGINE_MAX_RUNS_EXCEEDED_RE = /(?:\bmax_runs_exceeded\b|\bmaximum\s+llm\s+invocations\s+exceeded\b)/i; +const ALLOWED_FILES_ERROR_RE = /^(?.*outside the allowed-files list) \((?.+?)\)\. (?Add the files to the allowed-files configuration field or remove them from the (?:patch|bundle)\.)$/; /** * Parse action failure issue expiration from environment. @@ -77,6 +78,35 @@ function buildWarningAlertLine(title, message) { return `\n> [!WARNING]\n> **${title}**: ${message}\n`; } +/** + * Render an allowed-files error using progressive disclosure for the file list. + * @param {string} type + * @param {string} error + * @returns {string|null} + */ +function renderAllowedFilesError(type, error) { + const match = error.match(ALLOWED_FILES_ERROR_RE); + if (!match?.groups) { + return null; + } + + const summary = match.groups.summary; + const files = match.groups.files.split(",").map(file => file.trim()); + const remediation = match.groups.remediation; + const fileCount = files.length; + const fileWord = fileCount === 1 ? "file" : "files"; + + return `- \`${type}\`: ${summary}. ${remediation} + +
+Show ${fileCount} blocked ${fileWord} + +${renderFilesList(files)} + +
+`; +} + /** * Render a prompt template from runtime prompts. * @param {string} templateName @@ -947,7 +977,7 @@ gh pr create --head aw/manual-apply } context += "\n**Code Push Errors:**\n"; for (const { type, error } of otherErrors) { - context += `- \`${type}\`: ${error}\n`; + context += renderAllowedFilesError(type, error) || `- \`${type}\`: ${error}\n`; } context += "\n"; } else if (manifestErrors.length > 0 || patchSizeErrors.length > 0 || patchApplyErrors.length > 0) { diff --git a/actions/setup/js/handle_agent_failure.test.cjs b/actions/setup/js/handle_agent_failure.test.cjs index e167eed7066..4639a54a21e 100644 --- a/actions/setup/js/handle_agent_failure.test.cjs +++ b/actions/setup/js/handle_agent_failure.test.cjs @@ -1457,6 +1457,48 @@ describe("handle_agent_failure", () => { expect(result).not.toContain("Protected Files"); }); + it("renders allowed-files errors with progressive disclosure", () => { + const errors = + "create_pull_request:Cannot create pull request: patch modifies files outside the allowed-files list " + + "(pkg/workflow/codex_engine.go, pkg/workflow/codex_mcp.go, pkg/workflow/compiler_yaml.go). " + + "Add the files to the allowed-files configuration field or remove them from the patch."; + const result = buildCodePushFailureContext(errors); + expect(result).toContain("Code Push Failed"); + expect(result).toContain("outside the allowed-files list. Add the files to the allowed-files configuration field or remove them from the patch."); + expect(result).toContain("
"); + expect(result).toContain("Show 3 blocked files"); + expect(result).toContain("`pkg/workflow/codex_engine.go`"); + expect(result).toContain("`pkg/workflow/codex_mcp.go`"); + expect(result).toContain("`pkg/workflow/compiler_yaml.go`"); + expect(result).not.toContain("outside the allowed-files list (pkg/workflow/codex_engine.go, pkg/workflow/codex_mcp.go, pkg/workflow/compiler_yaml.go)"); + }); + + it("falls back to raw error line for non-matching allowed-files text", () => { + const errors = "create_pull_request:Cannot create pull request: some other error message."; + const result = buildCodePushFailureContext(errors); + expect(result).toContain("Cannot create pull request: some other error message."); + expect(result).not.toContain("
"); + }); + + it("uses singular wording for one blocked file", () => { + const errors = "create_pull_request:Cannot create pull request: patch modifies files outside the allowed-files list " + "(pkg/foo.go). Add the files to the allowed-files configuration field or remove them from the patch."; + const result = buildCodePushFailureContext(errors); + expect(result).toContain("Show 1 blocked file"); + expect(result).toContain("`pkg/foo.go`"); + }); + + it("renders bundle remediation variant", () => { + const errors = + "create_pull_request:Cannot create pull request: patch modifies files outside the allowed-files list " + + "(pkg/workflow/compiler(bundle).go, pkg/workflow/compiler_yaml.go). " + + "Add the files to the allowed-files configuration field or remove them from the bundle."; + const result = buildCodePushFailureContext(errors); + expect(result).toContain("remove them from the bundle."); + expect(result).toContain("`pkg/workflow/compiler(bundle).go`"); + expect(result).toContain("`pkg/workflow/compiler_yaml.go`"); + expect(result).toContain("Show 2 blocked files"); + }); + it("shows both sections when protected file and non-protected-file errors are mixed", () => { const errors = [ "create_pull_request:Cannot create pull request: patch modifies package manifest files (package.json). Set allow-manifest-files: true in your workflow to allow this.",