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.
Symptom
create_pull_requestsafe-output silently skips with the warning:…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
agentartifact).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_requestresponse 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_outputsjob logs show:Root cause
The trusted MCP server (
safe_outputs_handlers.cjs) setsentry.patch_pathandentry.bundle_pathon the entry it appends tosafeoutputs.jsonl.The
Ingest agent outputstep (collect_ndjson_output.cjs) reads that JSONL and runs every entry throughvalidateItem()insafe_output_type_validator.cjs, which unconditionally deletespatch_path,bundle_path,base_commit, anddiff_sizefrom the normalized item before writingagent_output.json.create_pull_request.cjsthen reads backpullRequestItem.patch_path/pullRequestItem.bundle_pathfromagent_output.json, bothundefined, and skips PR creation.The strip was added in commit eacfb51 (#36762) to defend against agents forging
patch_path/bundle_pathvia 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_requestandpush_to_pull_request_branch, which both consumemessage.patch_path/message.bundle_path.Proposed fix
Re-derive the patch/bundle paths from the entry's
branchfield at the start of the handler increate_pull_request.cjsandpush_to_pull_request_branch.cjsby globbing/tmp/gh-aw/aw-<sanitized-branch>.{patch,bundle}. This matches how the detection job already discovers patches (collect_ndjson_output.cjsuses^aw-.+\.(patch|bundle)$) and removes the dependency on the stripped fields entirely.The strip in
validateItem()can stay as the security backstop.