Skip to content

safe-outputs: create_pull_request fails with 'No patch file found' because validateItem() strips MCP-set patch_path/bundle_path #37297

Description

@dsyme

Symptom

create_pull_request safe-output silently skips with the warning:

No patch file found - cannot create pull request without changes

…even when the MCP server successfully generated the patch and bundle files and they are present on disk in the safe_outputs job (downloaded via the agent artifact).

Example failing run: https://github.com/githubnext/gh-aw-test/actions/runs/27045945076 (Test Copilot Create Pull Request)

In that run the agent artifact contains:

  • /tmp/gh-aw/aw-copilot-multi-commit-test-1780703153.patch (2666 bytes, 104 lines)
  • /tmp/gh-aw/aw-copilot-multi-commit-test-1780703153.bundle (1602 bytes)

The MCP safeoutputs-create_pull_request response confirms generation:

{"result":"success","patch":{"path":"/tmp/gh-aw/aw-copilot-multi-commit-test-1780703153.patch","size":2666,"lines":104},"bundle":{"path":"/tmp/gh-aw/aw-copilot-multi-commit-test-1780703153.bundle","size":1602}}

But safe_outputs job logs show:

Processing create_pull_request: title=Multi-commit test from Copilot, bodyLength=616
Patch file path: (not set)
Target repository: githubnext/gh-aw-test
Base branch for githubnext/gh-aw-test: main
Apply transport mode: patch (bundle file present: false)
##[warning]No patch file found - cannot create pull request without changes

Root cause

The trusted MCP server (safe_outputs_handlers.cjs) sets entry.patch_path and entry.bundle_path on the entry it appends to safeoutputs.jsonl.

The Ingest agent output step (collect_ndjson_output.cjs) reads that JSONL and runs every entry through validateItem() in safe_output_type_validator.cjs, which unconditionally deletes patch_path, bundle_path, base_commit, and diff_size from the normalized item before writing agent_output.json.

// safe_output_type_validator.cjs ~L548
// SECURITY: Strip infrastructure fields that must only be set by the MCP handler,
// never by the agent. ...
delete normalizedItem.patch_path;
delete normalizedItem.bundle_path;
delete normalizedItem.base_commit;
delete normalizedItem.diff_size;

create_pull_request.cjs then reads back pullRequestItem.patch_path / pullRequestItem.bundle_path from agent_output.json, both undefined, and skips PR creation.

The strip was added in commit eacfb51 (#36762) to defend against agents forging patch_path/bundle_path via raw NDJSON, but it has no way to distinguish MCP-server-emitted entries from agent-emitted ones because they share the same file.

Affected output types

At minimum create_pull_request and push_to_pull_request_branch, which both consume message.patch_path / message.bundle_path.

Proposed fix

Re-derive the patch/bundle paths from the entry's branch field at the start of the handler in create_pull_request.cjs and push_to_pull_request_branch.cjs by globbing /tmp/gh-aw/aw-<sanitized-branch>.{patch,bundle}. This matches how the detection job already discovers patches (collect_ndjson_output.cjs uses ^aw-.+\.(patch|bundle)$) and removes the dependency on the stripped fields entirely.

The strip in validateItem() can stay as the security backstop.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingmcp

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions