create-pull-request with protected-files: fallback-to-issue + bundle transport drops the branch — fallback issue has no link
Summary
When safe-outputs.create-pull-request is configured with
protected-files: fallback-to-issue and the default patch-format: bundle
transport, gh-aw skips the branch push as soon as the protected-files
fallback is triggered, then routes the fallback issue through the
"push-failed" template. The resulting review issue contains no link to
the agent's branch and no compare URL — only a copy-paste git am
recipe — so a human reviewer has no way to inspect the actual changes
on GitHub without manually downloading the patch artifact via
gh run download.
To be clear: the desired outcome is not to auto-create the PR.
fallback-to-issue should still create the issue (no PR). What's
broken is that the agent's branch is never pushed to origin, so the
issue can't link to it and reviewers can't view the diff in the
GitHub UI. The branch should be pushed; only the PR creation should
be replaced with an issue.
The non-bundle (patch-format: am) path does the right thing: it pushes
the branch first, then renders the normal fallback template with a
"Click here to create the pull request" compare URL — note the link is
a prefilled compare URL, not an automatic PR creation. A human still
clicks through to create the PR after reviewing the branch.
Why this matters
fallback-to-issue is the documented escape hatch for letting an agent
propose changes to manifests/CI files without hard-blocking the run. The
whole point is that a human can review the proposed change in GitHub's
diff UI before promoting it to a PR. Under the bundle transport, that
review surface never gets built — the branch isn't pushed, so there's
nothing to compare against. The resulting issue effectively says "trust
me, run these git commands locally," which defeats the safety story.
Multi-commit agent changes (and the merge-commit topology that the
bundle transport was specifically designed to preserve, per the schema
docs) are unreachable for any workflow that also wants
protected-files: fallback-to-issue. Today the only working
combination is patch-format: am, which trades away exactly the
properties bundle was added for.
Reproduction
Workflow frontmatter:
safe-outputs:
create-pull-request:
max: 1
draft: true
protected-files: fallback-to-issue
# patch-format: bundle ← default, bug appears here
Trigger the workflow with an agent prompt that modifies any default
protected file (e.g. README.md, requirements.txt, package.json).
Observed: a review issue is created with body rendered from
manifest_protection_push_failed_fallback.md. The body contains
gh run download <run_id> instructions and no branch URL or compare
URL. The branch is not on origin.
Expected: a review issue rendered from
manifest_protection_create_pr_fallback.md, with a clickable compare
URL pointing at the pushed agent branch.
Concrete example (cautious-spoon issue #11 in our internal GHES instance,
with patch-format unset → defaulted to bundle): the agent modified
README.md and requirements.txt, the branch was never pushed, the
fallback issue had no link.
Root cause
In actions/setup/js/create_pull_request.cjs, the bundle path
short-circuits the push when manifestProtectionFallback is set:
// create_pull_request.cjs:1485-1488
// Push the commits from the bundle to the remote branch
if (manifestProtectionFallback) {
core.info(
"Skipping branch push because protected-files fallback-to-issue was triggered",
);
manifestProtectionPushFailedError = new Error(
"Push skipped because protected-files fallback-to-issue was triggered",
);
} else {
// ... pushSignedCommits(...) ...
}
Setting manifestProtectionPushFailedError then steers the issue body
selector at line ~1962 to the push-failed template:
// create_pull_request.cjs:1962-1984
let fallbackBody;
if (manifestProtectionPushFailedError) {
// push-failed template (no branch link, no compare URL)
fallbackBody = renderTemplateFromFile(pushFailedTemplatePath, { ... });
} else {
// normal fallback (compare URL)
const createPrUrl = buildManifestProtectionCreatePrUrl(...);
fallbackBody = renderManifestProtectionFallbackBody(...);
}
The patch (am) path at lines 1745-1746 has the same skip logic — but
because that path apparently still has another push site that fires
before the fallback issue is built (or because the default bundle
transport is now the only path actually reached in our setup), the net
effect on the user is: bundle = no branch link; am = branch link.
Either way, the explicit core.info("Skipping branch push…") followed
by setting a pushFailedError looks intentional but seems wrong — the
review issue is exactly the case that needs the branch on origin most.
Expected behaviour
When protected-files: fallback-to-issue triggers, the branch should
be pushed using the same code path used for a successful PR (signed
commits via createCommitOnBranch GraphQL for the bundle transport,
git push for the am transport). The fallback issue should then render
the normal manifest_protection_create_pr_fallback.md template with the
compare URL.
The push-failed template should only render when the push actually
fails (e.g. missing workflows permission, GraphQL rejection, etc.) —
not as the default outcome of fallback-to-issue.
A reasonable fix:
- Remove the unconditional skip-and-mark-failed at
create_pull_request.cjs:1486-1488 (and its sibling at 1745-1746).
- Let the push run normally.
- If the push genuinely fails, the existing
try/catch around
pushSignedCommits already sets manifestProtectionPushFailedError
on real errors, so the push-failed template still renders for the
actual failure case it was designed for.
Workaround
Set patch-format: am on create-pull-request. This routes through
the patch path, which (today) does push the branch before creating the
fallback issue. Trade-off: loses merge-commit topology and per-commit
authorship preservation that bundle transport was added for.
safe-outputs:
create-pull-request:
max: 1
draft: true
protected-files: fallback-to-issue
patch-format: am # workaround for branch-link bug
Environment
- gh-aw
v0.74.4
actions/setup/js/create_pull_request.cjs (current main)
create-pull-requestwithprotected-files: fallback-to-issue+ bundle transport drops the branch — fallback issue has no linkSummary
When
safe-outputs.create-pull-requestis configured withprotected-files: fallback-to-issueand the defaultpatch-format: bundletransport, gh-aw skips the branch push as soon as the protected-files
fallback is triggered, then routes the fallback issue through the
"push-failed" template. The resulting review issue contains no link to
the agent's branch and no compare URL — only a copy-paste
git amrecipe — so a human reviewer has no way to inspect the actual changes
on GitHub without manually downloading the patch artifact via
gh run download.To be clear: the desired outcome is not to auto-create the PR.
fallback-to-issueshould still create the issue (no PR). What'sbroken is that the agent's branch is never pushed to origin, so the
issue can't link to it and reviewers can't view the diff in the
GitHub UI. The branch should be pushed; only the PR creation should
be replaced with an issue.
The non-bundle (
patch-format: am) path does the right thing: it pushesthe branch first, then renders the normal fallback template with a
"Click here to create the pull request" compare URL — note the link is
a prefilled compare URL, not an automatic PR creation. A human still
clicks through to create the PR after reviewing the branch.
Why this matters
fallback-to-issueis the documented escape hatch for letting an agentpropose changes to manifests/CI files without hard-blocking the run. The
whole point is that a human can review the proposed change in GitHub's
diff UI before promoting it to a PR. Under the bundle transport, that
review surface never gets built — the branch isn't pushed, so there's
nothing to compare against. The resulting issue effectively says "trust
me, run these git commands locally," which defeats the safety story.
Multi-commit agent changes (and the merge-commit topology that the
bundletransport was specifically designed to preserve, per the schemadocs) are unreachable for any workflow that also wants
protected-files: fallback-to-issue. Today the only workingcombination is
patch-format: am, which trades away exactly theproperties bundle was added for.
Reproduction
Workflow frontmatter:
Trigger the workflow with an agent prompt that modifies any default
protected file (e.g.
README.md,requirements.txt,package.json).Observed: a review issue is created with body rendered from
manifest_protection_push_failed_fallback.md. The body containsgh run download <run_id>instructions and no branch URL or compareURL. The branch is not on origin.
Expected: a review issue rendered from
manifest_protection_create_pr_fallback.md, with a clickable compareURL pointing at the pushed agent branch.
Concrete example (cautious-spoon issue #11 in our internal GHES instance,
with
patch-formatunset → defaulted tobundle): the agent modifiedREADME.mdandrequirements.txt, the branch was never pushed, thefallback issue had no link.
Root cause
In
actions/setup/js/create_pull_request.cjs, the bundle pathshort-circuits the push when
manifestProtectionFallbackis set:Setting
manifestProtectionPushFailedErrorthen steers the issue bodyselector at line ~1962 to the push-failed template:
The patch (
am) path at lines 1745-1746 has the same skip logic — butbecause that path apparently still has another push site that fires
before the fallback issue is built (or because the default
bundletransport is now the only path actually reached in our setup), the net
effect on the user is: bundle = no branch link; am = branch link.
Either way, the explicit
core.info("Skipping branch push…")followedby setting a
pushFailedErrorlooks intentional but seems wrong — thereview issue is exactly the case that needs the branch on origin most.
Expected behaviour
When
protected-files: fallback-to-issuetriggers, the branch shouldbe pushed using the same code path used for a successful PR (signed
commits via createCommitOnBranch GraphQL for the bundle transport,
git pushfor the am transport). The fallback issue should then renderthe normal
manifest_protection_create_pr_fallback.mdtemplate with thecompare URL.
The push-failed template should only render when the push actually
fails (e.g. missing
workflowspermission, GraphQL rejection, etc.) —not as the default outcome of fallback-to-issue.
A reasonable fix:
create_pull_request.cjs:1486-1488(and its sibling at 1745-1746).try/catcharoundpushSignedCommitsalready setsmanifestProtectionPushFailedErroron real errors, so the push-failed template still renders for the
actual failure case it was designed for.
Workaround
Set
patch-format: amoncreate-pull-request. This routes throughthe patch path, which (today) does push the branch before creating the
fallback issue. Trade-off: loses merge-commit topology and per-commit
authorship preservation that bundle transport was added for.
Environment
v0.74.4actions/setup/js/create_pull_request.cjs(currentmain)