You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
SideRepoOps: API for a user step to resolve the target branch at runtime, then check it out and use it as the patch base (dynamic base; not covered by #39407) #41265
🤖 This issue was investigated and filed by Claude Code.
Summary
In a SideRepoOps workflow, the branch of the target/side repository that a given run should operate on is sometimes only knowable at runtime — e.g. it is read from a label on the triggering issue, a dispatch input, or an API lookup — and is not fixed in the workflow YAML. Today there is no supported way to:
let a user-defined step resolve that branch for the current run, and
have gh-aw check out the target repo at that branchanduse it as the base when generating the create_pull_request / push_to_pull_request_branch patch.
Because nothing carries a runtime-resolved branch into patch generation, the patch is computed against the target repo's default branch. The merge-base lies far behind the intended branch, so the diff drags in the entire default→release divergence — blowing past max-patch-files and failing PR creation.
Request: an API / extension point for a runtime-resolved base branch in the SideRepoOps pattern.
#39407 covers the static case: a checkout that pins a non-default ref directly in YAML (checkout: [{ repository, ref: <release-branch> }]), asking that the checked-out ref be used as the base; its suggested workaround is a static safe-outputs.create-pull-request.base-branch:.
This issue is the dynamic case, which neither that workaround nor the fix proposed in #39407 covers:
The branch is not known at compile time. It is computed per-run by a user step (e.g. from an issue's branch/<name> label). A static base-branch: or a static checkout.ref cannot express a per-run value.
The per-call base argument is ignored by patch generation. When the agent calls create_pull_request with base: <release-branch> (permitted via allowed-base-branches), that value is honored for the eventual PR API target but is not used to compute the patch base — patch generation prefers the checkout manifest's default_branch.
In short: there is currently no mechanism — static config, static checkout ref, or per-call base argument — that lets a SideRepoOps run target a runtime-resolved branch and have the patch computed against it.
Where the base comes from (for reference)
Base resolution for create_pull_request (per #39407's citation of safe_outputs_handlers.cjs):
if(prConfig.base_branch)baseBranch=prConfig.base_branch;// ① static configelseif(manifestEntry&&manifestEntry.default_branch)baseBranch=…;// ② checkout manifestelsebaseBranch=awaitgetBaseBranch(...);// ③ repos.get().default_branch
The manifest's default_branch (build_checkout_manifest.cjs) is resolved as:
Neither path consults the per-call base argument, the DEFAULT_BRANCH env var, or any runtime-resolved value. ① is the only override, and it is static.
Observed failure (redacted)
A SideRepoOps run (gh-aw v0.80.9) in which the agent committed a small change (5 tracked files) on a feature branch derived from <release-branch>, then called:
[safeoutputs] Using checkout-manifest default_branch for <owner>/<side-repo>: <default-branch>
[safeoutputs] Generating patch ... baseBranch: <default-branch>
[generate_git_patch] Starting patch generation: mode=full, branch=<feature-branch>, defaultBranch=<default-branch>
[generate_git_patch] Strategy 1 (full): Computing merge-base with <default-branch>
[generate_git_patch] Strategy 1: Found 455 commits between <merge-base> and <tip>
[generate_git_patch] Final: SUCCESS - patchSize≈66 MB, patchLines≈1.4M
i.e. the patch was computed as diff(origin/<default-branch>, HEAD) despite base: <release-branch> in the call. The resulting patch contained thousands of files (generated .gen.go / .pb.* artifacts produced by the default↔release divergence — none of them touched by the agent), exceeding max-patch-files, so PR creation was rejected and fell back to a review issue.
Current workaround and why it silently fails
A custom step placed after the manifest step re-anchors the checkout and tries to override the base:
This no longer works: the safe-outputs patch generator reads the checkout manifest (built earlier, recording the default branch), which takes precedence over the DEFAULT_BRANCH env override. The manifest is frozen before the re-anchor runs, so the patch base reverts to the default branch — with no error, just a huge patch.
Requested API
A supported way to inject a runtime base-branch resolution into the SideRepoOps lifecycle. Sketch of the contract:
A documented hook/step that runs before the cross-repo checkout + manifest, in which a user step resolves the target branch for this run (e.g. writes it to a known step output / env var / file).
gh-aw then (a) checks out the target repo at that branch, and (b) records it as the manifest default_branch / patch base for create_pull_request and push_to_pull_request_branch.
Possible shapes (any one would suffice):
A frontmatter field such as safe-outputs.create-pull-request.base-branch-from: pointing at a step output / env var that gh-aw reads at runtime, and which also drives the target-repo checkout ref.
A pre-checkout / resolve-base extension point that runs before the cross-repo checkout + manifest, whose output sets both the checkout ref and the manifest default_branch.
Make patch generation honor the per-call base argument (and/or the existing DEFAULT_BRANCH env override) so a custom step that has already resolved the branch can set it.
Options 1 and 2 are preferable, because they derive the checkout ref and the patch base from a single user-resolved value, eliminating the drift that comes from specifying the branch in two places.
Redaction note
Private repository names, branch names, issue numbers, and run URLs are replaced with placeholders (<owner>/<side-repo>, <release-branch>, <default-branch>, <feature-branch>). Related: #39407.
🤖 This issue was investigated and filed by Claude Code.
Summary
In a SideRepoOps workflow, the branch of the target/side repository that a given run should operate on is sometimes only knowable at runtime — e.g. it is read from a label on the triggering issue, a dispatch input, or an API lookup — and is not fixed in the workflow YAML. Today there is no supported way to:
create_pull_request/push_to_pull_request_branchpatch.Because nothing carries a runtime-resolved branch into patch generation, the patch is computed against the target repo's default branch. The merge-base lies far behind the intended branch, so the diff drags in the entire default→release divergence — blowing past
max-patch-filesand failing PR creation.Request: an API / extension point for a runtime-resolved base branch in the SideRepoOps pattern.
How this differs from #39407
#39407 covers the static case: a checkout that pins a non-default
refdirectly in YAML (checkout: [{ repository, ref: <release-branch> }]), asking that the checked-out ref be used as the base; its suggested workaround is a staticsafe-outputs.create-pull-request.base-branch:.This issue is the dynamic case, which neither that workaround nor the fix proposed in #39407 covers:
branch/<name>label). A staticbase-branch:or a staticcheckout.refcannot express a per-run value.HEAD) is insufficient here. The only place a user step can re-anchor the checkout is after the built-in "Build checkout manifest for safe-outputs handlers" step (customsteps:are emitted after the checkout block). So at manifest timeHEADis still the default branch, and recordingHEADwould record the default branch.baseargument is ignored by patch generation. When the agent callscreate_pull_requestwithbase: <release-branch>(permitted viaallowed-base-branches), that value is honored for the eventual PR API target but is not used to compute the patch base — patch generation prefers the checkout manifest'sdefault_branch.In short: there is currently no mechanism — static config, static checkout
ref, or per-callbaseargument — that lets a SideRepoOps run target a runtime-resolved branch and have the patch computed against it.Where the base comes from (for reference)
Base resolution for
create_pull_request(per #39407's citation ofsafe_outputs_handlers.cjs):The manifest's
default_branch(build_checkout_manifest.cjs) is resolved as:Neither path consults the per-call
baseargument, theDEFAULT_BRANCHenv var, or any runtime-resolved value. ① is the only override, and it is static.Observed failure (redacted)
A SideRepoOps run (gh-aw
v0.80.9) in which the agent committed a small change (5 tracked files) on a feature branch derived from<release-branch>, then called:The safe-outputs server logged:
i.e. the patch was computed as
diff(origin/<default-branch>, HEAD)despitebase: <release-branch>in the call. The resulting patch contained thousands of files (generated.gen.go/.pb.*artifacts produced by the default↔release divergence — none of them touched by the agent), exceedingmax-patch-files, so PR creation was rejected and fell back to a review issue.Current workaround and why it silently fails
A custom step placed after the manifest step re-anchors the checkout and tries to override the base:
This no longer works: the safe-outputs patch generator reads the checkout manifest (built earlier, recording the default branch), which takes precedence over the
DEFAULT_BRANCHenv override. The manifest is frozen before the re-anchor runs, so the patch base reverts to the default branch — with no error, just a huge patch.Requested API
A supported way to inject a runtime base-branch resolution into the SideRepoOps lifecycle. Sketch of the contract:
default_branch/ patch base forcreate_pull_requestandpush_to_pull_request_branch.Possible shapes (any one would suffice):
safe-outputs.create-pull-request.base-branch-from:pointing at a step output / env var that gh-aw reads at runtime, and which also drives the target-repo checkoutref.pre-checkout/resolve-baseextension point that runs before the cross-repo checkout + manifest, whose output sets both the checkoutrefand the manifestdefault_branch.baseargument (and/or the existingDEFAULT_BRANCHenv override) so a custom step that has already resolved the branch can set it.Options 1 and 2 are preferable, because they derive the checkout
refand the patch base from a single user-resolved value, eliminating the drift that comes from specifying the branch in two places.Redaction note
Private repository names, branch names, issue numbers, and run URLs are replaced with placeholders (
<owner>/<side-repo>,<release-branch>,<default-branch>,<feature-branch>). Related: #39407.