Skip to content

feat: make E003 file-count error actionable and document max-patch-files#32541

Merged
pelikhan merged 8 commits into
mainfrom
copilot/raise-file-cap-create-pull-request
May 16, 2026
Merged

feat: make E003 file-count error actionable and document max-patch-files#32541
pelikhan merged 8 commits into
mainfrom
copilot/raise-file-cap-create-pull-request

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 16, 2026

The hard-coded 100-file cap in create_pull_request silently blocks generated-content workflows (e.g. daily API-reference regeneration) with no recovery path — the error message gave no hint that the limit is configurable.

Changes

  • Actionable error message — E003 now tells the user the exact count received and the exact field to set:

    E003: Cannot create pull request with more than 100 files (received 220).
    To increase the limit, set `max-patch-files: 220` (or higher) under
    `safe-outputs.create-pull-request` in your workflow frontmatter.
    
  • Documentation (safe-outputs-pull-requests.md) — max-patch-files and max-patch-size were undocumented; added both to the config YAML example and added a prose section with a worked example for generated-content workflows:

    safe-outputs:
      create-pull-request:
        max-patch-files: 300   # raise from default 100
        max-patch-size: 2048   # raise from default 1024 KB
  • Test — added assertion that the E003 message always contains "max-patch-files" to prevent regression.

- Make E003 error actionable: tell users the exact file count and
  the `max-patch-files` frontmatter field they can set to raise it
- Document `max-patch-files` and `max-patch-size` options in
  safe-outputs-pull-requests.md with usage examples
- Add `max-patch-files` and `max-patch-size` to the config YAML snippet
- Update tests to assert the new actionable hint is present in E003

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Raise default 100-file cap in create_pull_request safe-output feat: make E003 file-count error actionable and document max-patch-files May 16, 2026
Copilot AI requested a review from pelikhan May 16, 2026 04:02
Copy link
Copy Markdown
Collaborator

@pelikhan pelikhan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot the error needs to be surface into the agent failure issue/comment message

Copilot AI and others added 2 commits May 16, 2026 04:14
When the patch exceeds `max-patch-files` and `fallback-as-issue` is
enabled (the default), create a fallback issue that surfaces the E003
error and the actionable `max-patch-files` fix — instead of silently
returning a bare failure result that only appears in step logs.

When `fallback-as-issue: false`, behaviour is unchanged (return error).

Add three integration tests covering:
- E003 + fallback_as_issue=true → fallback issue created
- E003 + fallback_as_issue=false → error returned, no issue
- max_patch_files raised above count → PR created normally

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 16, 2026

@copilot the error needs to be surface into the agent failure issue/comment message

Done in 031a69e. When E003 fires and fallback-as-issue is enabled (the default), the handler now creates a fallback issue containing the full error message and the exact max-patch-files YAML snippet needed to fix it — so it shows up in the agent failure issue/comment thread rather than only in step logs. When fallback-as-issue: false, the previous behaviour (plain error return) is preserved.

Copilot AI requested a review from pelikhan May 16, 2026 04:16
// issue/comment thread and the workflow operator knows exactly how to fix it.
const fallbackTitle = pullRequestItem.title?.trim() || "Agent Output";
const fallbackLabels = mergeFallbackIssueLabels(configFallbackLabels.length > 0 ? configFallbackLabels : envLabels);
const fallbackBody = [
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot refactor text to template file in actions/setup/md

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in the latest commits. Extracted the E003 fallback body into actions/setup/md/e003_file_limit_fallback.md with {error_message} and {suggested_limit} placeholders. The code now calls renderTemplateFromFile using getPromptPath("e003_file_limit_fallback.md"), matching the pattern used for other fallback messages. The E003 test was updated to copy the template into a temp prompts directory and set GH_AW_PROMPTS_DIR accordingly.

Copilot AI and others added 2 commits May 16, 2026 04:42
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI requested a review from pelikhan May 16, 2026 04:43
@pelikhan pelikhan marked this pull request as ready for review May 16, 2026 04:45
Copilot AI review requested due to automatic review settings May 16, 2026 04:45
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR makes the create-pull-request file-count limit more actionable and documents the related configuration knobs.

Changes:

  • Updates E003 to include the received file count and max-patch-files guidance.
  • Adds a fallback issue template for file-limit failures.
  • Documents max-patch-files and max-patch-size, and adds tests for the E003/fallback behavior.
Show a summary per file
File Description
docs/src/content/docs/reference/safe-outputs-pull-requests.md Documents patch file-count and patch-size configuration.
actions/setup/md/e003_file_limit_fallback.md Adds the fallback issue body template for E003 failures.
actions/setup/js/create_pull_request.test.cjs Adds assertions and fallback behavior tests for E003.
actions/setup/js/create_pull_request.cjs Updates E003 messaging and adds fallback issue creation on file-limit failures.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 4/4 changed files
  • Comments generated: 3

if (!fallbackAsIssue) {
return { success: false, error: errorMessage };
}

Comment on lines +1099 to +1102
const fallbackBody = renderTemplateFromFile(fallbackTemplatePath, {
error_message: errorMessage,
suggested_limit: maxFiles * 2,
});

// Surface the limit error in a fallback issue so it appears in the agent failure
// issue/comment thread and the workflow operator knows exactly how to fix it.
const fallbackTitle = pullRequestItem.title?.trim() || "Agent Output";
@github-actions github-actions Bot mentioned this pull request May 16, 2026
@github-actions
Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 80/100

Excellent test quality

Metric Value
New/modified tests analyzed 3
✅ Design tests (behavioral contracts) 3 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 3 (100%)
Duplicate test clusters 0
Test inflation detected ⚠️ Yes (142 test lines / 36 production lines ≈ 3.9:1)
🚨 Coding-guideline violations 0

Test Classification Details

View per-test classification
Test File Classification Issues Detected
should create a fallback issue when E003 fires and fallback_as_issue is true (default) actions/setup/js/create_pull_request.test.cjs ✅ Design None
should return success: false when E003 fires and fallback_as_issue is false actions/setup/js/create_pull_request.test.cjs ✅ Design None
should pass when max_patch_files is raised above the file count actions/setup/js/create_pull_request.test.cjs ✅ Design None

Analysis Notes

All 3 new tests are in the create_pull_request - E003 file-limit fallback-to-issue describe block added to cover the new behavioral feature.

⚠️ Test Inflation (informational — not a failure)

The test file grew by 142 lines vs 36 production lines (ratio ≈ 3.9:1), which mechanically triggers the inflation flag. However, the tests exercise a multi-step integration path (real fs.copyFileSync, os.tmpdir, full main() invocation with mocked GitHub API), which naturally requires more test scaffolding than production logic. The inflation is justifiable and the tests add genuine behavioral value. The 10-point deduction is applied per the scoring formula, but this should not be treated as a quality concern.

Test Strength Highlights

  • Test 1 verifies the full E003 fallback flow end-to-end: oversized patch → issues.create called once → body contains "E003" and "max-patch-files"pulls.create not called → result.fallback_used === true. This is a strong behavioral contract test.
  • Test 2 verifies the negative path (fallback_as_issue: false) — confirms the error is surfaced in result.error and no fallback issue is created. Good error-path coverage.
  • Test 3 verifies the escape hatch: raising max_patch_files above the file count allows the PR to be created normally with zero fallback. Covers the actionability of the error message.

The buildOversizedPatch() helper generates realistic git patch syntax with the correct diff --git header format, which is an excellent approach for deterministic unit testing of patch parsing.


Language Support

Tests analyzed:

  • 🟨 JavaScript (*.test.cjs): 3 tests (vitest)
  • 🐹 Go (*_test.go): 0 new/modified tests

Verdict

Check passed. 0% of new tests are implementation tests (threshold: 30%). All 3 new tests verify observable behavioral contracts with full error-path coverage.


📖 Understanding Test Classifications

Design Tests (High Value) verify what the system does:

  • Assert on observable outputs, return values, or state changes
  • Cover error paths and boundary conditions
  • Would catch a behavioral regression if deleted
  • Remain valid even after internal refactoring

Implementation Tests (Low Value) verify how the system does it:

  • Assert on internal function calls (mocking internals)
  • Only test the happy path with typical inputs
  • Break during legitimate refactoring even when behavior is correct
  • Give false assurance: they pass even when the system is wrong

Goal: Shift toward tests that describe the system's behavioral contract — the promises it makes to its users and collaborators.

References: §25953066106

🧪 Test quality analysis by Test Quality Sentinel · ● 9.6M ·

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /tdd for this feature addition — the PR introduces new fallback behaviour and has meaningful test coverage to evaluate.

Key Themes

  • Suggested limit inconsistency: the inline error message hints max-patch-files: <exact-count>, but the fallback issue template uses maxFiles * 2 — two different values for the same setting in the same failure event (see inline comment on line 1101).
  • Missing error-path test: the catch (issueError) branch (fallback issue creation fails) is untested, leaving a silent regression risk (see inline comment on line 3168).

Positive Highlights

  • ✅ Great UX improvement — the new error message is immediately actionable and tells the operator exactly what field to set.
  • ✅ Three distinct scenarios tested (fallback enabled, fallback disabled, limit raised) — good behavioural coverage for the happy paths.
  • ✅ Template-driven fallback issue body is a clean separation of concerns.
  • ✅ Documentation additions are clear and include worked examples, which matches the project's documentation style.

Verdict

Two small issues worth addressing before merge: align the suggested limit value across both surfaces, and add a test for the fallback-issue-creation-failure path. Neither is blocking, but fixing them keeps the error story internally consistent and closes the one untested branch.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · ● 8.3M

const fallbackTemplatePath = getPromptPath("e003_file_limit_fallback.md");
const fallbackBody = renderTemplateFromFile(fallbackTemplatePath, {
error_message: errorMessage,
suggested_limit: maxFiles * 2,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] The suggested_limit passed to the fallback template is maxFiles * 2 (e.g. 200 when the default 100-file limit is in effect), but the actionable error message in enforcePullRequestLimits suggests exactly fileCount (e.g. 101). A user who reads the fallback issue body will see "set max-patch-files: 200" while the inline error says "set max-patch-files: 101" — two different values for the same setting in the same failure event.

Consider aligning both surfaces to use fileCount:

suggested_limit: fileCount,  // match the error message hint

If a deliberate buffer is intended, documenting the reasoning would help avoid future confusion.

expect(global.github.rest.pulls.create).toHaveBeenCalledTimes(1);
expect(global.github.rest.issues.create).not.toHaveBeenCalled();
});
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] The happy-path and fallback_as_issue: false cases are well-covered, but the catch (issueError) branch in the new fallback block has no test. If github.rest.issues.create rejects, the combined error should be returned as { success: false, error: '...' } and core.error should be called. Without a test, a regression in that branch would be invisible.

Suggested addition:

it('should return success:false when fallback issue creation itself fails', async () => {
  global.github.rest.issues.create = vi.fn().mockRejectedValue(new Error('API rate limited'));

  const patchPath = path.join(tempDir, 'aw-test.patch');
  fs.writeFileSync(patchPath, buildOversizedPatch(101));

  const { main } = require('./create_pull_request.cjs');
  const handler = await main({});
  const result = await handler({ title: 'T', body: 'B', branch: 'b', patch_path: patchPath }, {});

  expect(result.success).toBe(false);
  expect(result.error).toContain('E003');
  expect(result.error).toContain('API rate limited');
  expect(global.core.error).toHaveBeenCalledTimes(1);
});

@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot review all comments

Copilot AI and others added 2 commits May 16, 2026 05:14
…llback

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 16, 2026

@copilot review all comments

Addressed all three reviewer comments:

  1. Staged mode — added an isStaged branch before creating the fallback issue; it now writes a step summary preview and returns { success: true, staged: true } without any API side effects, matching all other error paths in the handler.

  2. Suggested limit — replaced maxFiles * 2 with countUniquePatchFiles(patchContent) (computed once before the try block to avoid re-parsing) so the max-patch-files value in the fallback issue body is always exactly the number of files that triggered the error.

  3. Title sanitization — the fallback issue title now goes through sanitizeTitle + applyTitlePrefix before being passed to createFallbackIssue.

Also added regression assertions (not.toContain("max-patch-files: 200")) to prevent the old maxFiles * 2 behaviour from returning.

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.

Raise / unblock default 100-file cap in create_pull_request safe-output for generated-content workflows

3 participants