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
Fix backport AI permission, surface infra failures, and allow manual dispatch (#1943)
* Hoist AI model env, fix opencode external_directory permission, fail loud on AI infra errors
Three related fixes triggered by the failed run on #1935:
1. Hoist the AI model name to a top-level `AI_MODEL` env var
(`anthropic/claude-opus-4.7`); both `opencode run` invocations
now interpolate `vercel/${AI_MODEL}` so the model is specified in
exactly one place.
2. Switch `OPENCODE_PERMISSION` from the bare-string shortcut
`"allow"` to the explicit object form
`{"*":"allow","external_directory":"allow"}`. The shortcut
was observed not to override `external_directory` (which defaults to
"ask" and auto-rejects in non-interactive `opencode run`),
causing the conflict-resolution AI to fail when reading scratch files
it created under `/tmp/`.
3. The `Resolve conflicts with opencode` step no longer uses
`continue-on-error`, and now distinguishes two outcomes via an AI-
written outcome file (`.backport-conflict-outcome.json`):
- `{"status":"resolved"}` — the legitimate clean path; cherry-pick
continues and the backport PR is opened.
- `{"status":"unresolved", ...}` — the legitimate "AI couldn't
do it, hand off to a human" path; `resolved=false` is set and the
conflict-failure comment is posted on the source PR.
- Anything else (missing file, malformed JSON, unknown status) is
treated as an opencode/AI Gateway infra failure: the step exits
non-zero, the workflow fails red, and the misleading
"couldn't resolve" comment is suppressed.
The prompt + scratch files are also moved into the workspace so
opencode never needs `external_directory` access anyway.
* Allow manual workflow_dispatch with ref+model inputs; use AI_MODEL in PR body
Add a `workflow_dispatch` trigger to the backport workflow with two
optional inputs:
- `ref` — commit SHA on `main` to back-port (defaults to `main` HEAD)
- `model` — overrides the default AI model used by opencode for the
decision and conflict-resolution steps (defaults to the workflow's
hardcoded `AI_MODEL`)
The top-level `AI_MODEL` env var now uses
`${{ inputs.model || 'anthropic/claude-opus-4.7' }}` so manual runs
pick up the override without changing anything else.
Manual dispatch (like the `backport-stable` label) always forces a
backport regardless of any AI verdict — the operator's intent is
explicit by virtue of triggering the workflow. The PR body shows
"Triggered manually via `workflow_dispatch`." in that case.
The PR body's conflict-resolution attribution also now interpolates
`${AI_MODEL}` (e.g. "opencode with `anthropic/claude-opus-4.7`")
instead of hardcoding "Claude Opus" so the text stays accurate if the
default model is later changed.
* Address PR review: also detect leftover conflict markers in staged files
The previous `Resolve conflicts with opencode` sanity check used
`git diff --diff-filter=U` to detect unresolved cherry-pick conflicts,
which only catches unmerged index entries. That misses the case where
the AI runs `git add` on a file that still has `<<<<<<<` /
`=======` / `>>>>>>>` markers in its content — git happily stages
the broken file as a normal modification.
Add a second check using `git diff --check --cached`, which emits
`leftover conflict marker` lines when any staged content still has
the standard markers. Grep specifically for that phrase so unrelated
whitespace warnings don't trip the check. Also update the inline
comment to accurately describe what each check covers (per Copilot's
review on #1943).
description: 'Commit SHA on `main` to back-port. Defaults to the current HEAD of `main`.'
13
+
required: false
14
+
type: string
15
+
model:
16
+
description: 'AI model used for AI-assisted decisions and conflict resolution. Format: `<provider>/<model>` (the `vercel/` prefix is added automatically). Leave blank to use the workflow default.'
17
+
required: false
18
+
type: string
9
19
10
20
concurrency: backport-stable
11
21
22
+
# AI model used by every opencode invocation in this workflow. The
23
+
# `vercel/` provider prefix is added at each use site, so this should
24
+
# be specified as `<provider>/<model>` (e.g. `anthropic/claude-opus-4.7`).
25
+
# Manual `workflow_dispatch` runs may override this via the `model` input.
# code alone — but with `set -e` above, any non-zero exit will also
296
350
# fail the job. The real signal is whether the decision file was
297
351
# produced.
298
-
opencode run --model vercel/anthropic/claude-opus-4.7 "$(cat .backport-decision-prompt.txt)"
352
+
opencode run --model "vercel/${AI_MODEL}" "$(cat .backport-decision-prompt.txt)"
299
353
300
354
if [ ! -f "$DECISION_FILE" ]; then
301
355
echo "::error::AI did not produce a decision file at ${DECISION_FILE}. This usually indicates an opencode/AI Gateway infrastructure failure (e.g. expired API key, gateway down, or rejected tool call) — check the step output above. To force a backport regardless of the AI decision, add the \`backport-stable\` label to the source PR."
echo "::error::AI conflict resolution did not produce ${OUTCOME_FILE}. This usually indicates an opencode/AI Gateway infrastructure failure (e.g. expired API key, rejected tool call, opencode crash) — check the step output above. To resolve manually, see the source PR for instructions."
568
+
exit 1
475
569
fi
476
570
571
+
if ! jq empty "$OUTCOME_FILE" 2>/dev/null; then
572
+
echo "::error::AI outcome file at ${OUTCOME_FILE} is not valid JSON:"
573
+
cat "$OUTCOME_FILE"
574
+
exit 1
575
+
fi
576
+
577
+
STATUS=$(jq -r '.status' "$OUTCOME_FILE")
578
+
case "$STATUS" in
579
+
resolved)
580
+
# Sanity-check the AI's claim before committing the
581
+
# cherry-pick. Two failure modes to defend against:
582
+
#
583
+
# 1. Files left as unmerged index entries (the AI didn't
if echo "$CHECK_OUTPUT" | grep -q "leftover conflict marker"; then
600
+
echo "::warning::AI claimed conflicts were resolved but staged files still contain conflict markers (<<<<<<<, =======, >>>>>>>); treating as unresolved:"
echo "AI could not fully resolve conflicts: $REASON"
613
+
echo "resolved=false" >> "$GITHUB_OUTPUT"
614
+
;;
615
+
*)
616
+
echo "::error::AI returned unexpected status '$STATUS' (expected 'resolved' or 'unresolved'). Outcome file:"
617
+
cat "$OUTCOME_FILE"
618
+
exit 1
619
+
;;
620
+
esac
621
+
477
622
- name: Push backport branch via GitHub API
478
623
# Branch protection on this repo requires verified signatures on
479
624
# every ref (an enterprise-level ruleset matching `~ALL`). A normal
@@ -717,15 +862,21 @@ jobs:
717
862
fi
718
863
echo ""
719
864
720
-
if [ "$TRIGGER" = "label" ]; then
721
-
echo "Triggered by the \`backport-stable\` label."
722
-
else
723
-
echo "**AI recommendation:** $AI_REASONING"
724
-
fi
865
+
case "$TRIGGER" in
866
+
label)
867
+
echo "Triggered by the \`backport-stable\` label."
868
+
;;
869
+
dispatch)
870
+
echo "Triggered manually via \`workflow_dispatch\`."
871
+
;;
872
+
*)
873
+
echo "**AI recommendation:** $AI_REASONING"
874
+
;;
875
+
esac
725
876
726
877
if [ "$CHERRY_PICK_STATUS" = "conflict" ]; then
727
878
echo ""
728
-
echo "Merge conflicts were resolved by AI ([opencode](https://opencode.ai) with Claude Opus). **Please review the conflict resolution carefully before merging.**"
879
+
echo "Merge conflicts were resolved by AI ([opencode](https://opencode.ai) with \`${AI_MODEL}\`). **Please review the conflict resolution carefully before merging.**"
729
880
fi
730
881
} > /tmp/pr-body.md
731
882
@@ -771,10 +922,15 @@ jobs:
771
922
});
772
923
773
924
- name: Comment on conflict failure
925
+
# Only post the manual-resolution comment when the AI ran cleanly
926
+
# but couldn't resolve all conflicts (`resolved=false`). Infra
927
+
# failures (auth errors, opencode crashes, etc.) cause the
928
+
# `Resolve conflicts with opencode` step to exit non-zero, which
929
+
# fails the job — we don't want to also post a misleading
Copy file name to clipboardExpand all lines: AGENTS.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -239,6 +239,8 @@ When in doubt, AI is told to lean toward recommending a backport — a human rev
239
239
240
240
**Manual override.** Adding the `backport-stable` label to a merged PR forces a backport regardless of AI's verdict (it skips AI analysis entirely). Use this when AI declined a backport that you want to ship to `stable`, or when AI hasn't run yet and you want to express intent up front. The label can be applied before or after merging — the action triggers on both push events and label events.
241
241
242
+
The workflow can also be run manually from the GitHub Actions UI via `workflow_dispatch`, which accepts an optional `ref` input (a commit SHA on `main`; defaults to `main` HEAD) and an optional `model` input (the AI model used for AI-assisted decisions and conflict resolution, in `<provider>/<model>` form — defaults to the workflow's current default). Manual dispatch always forces a backport (skipping AI analysis), the same way the label does.
243
+
242
244
**No-backport notification.** When AI decides against a backport, it leaves a comment on the source PR (if one is associated with the commit) explaining its reasoning, with instructions for forcing a backport via the `backport-stable` label.
243
245
244
246
**Conflict handling.** If the cherry-pick fails due to conflicts, the action first auto-resolves conflicts in directories that are not maintained on `stable` (docs app files under `docs/` except `docs/content/`, and any files under `skills/`) by keeping the `stable` branch version. It also auto-resolves `pnpm-lock.yaml` conflicts by re-running `pnpm install`. Any remaining conflicts are resolved using [opencode](https://opencode.ai) (AI-powered conflict resolution); the resulting backport PR notes that conflicts were AI-resolved and must be reviewed carefully. If AI cannot resolve the conflicts, the action comments on the original PR with instructions for manual resolution.
0 commit comments