Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
ba83d54
refactor(ci): split PR triage into 4-job pipeline
yiliang114 Jun 8, 2026
99d2d0e
fix(ci): pass untrusted event data through env vars
yiliang114 Jun 8, 2026
cb1fa47
refactor(skills): update /triage to dispatch to 4 independent skills
yiliang114 Jun 8, 2026
23a9933
fix(ci): make tmux-testing depend on review (serial pipeline)
yiliang114 Jun 8, 2026
904a5de
fix(ci): implement file-based verdict passing between jobs
yiliang114 Jun 8, 2026
3b7e94b
fix(ci): review verdict gates tmux + add push delay
yiliang114 Jun 8, 2026
53ea4c3
fix(ci): address review findings (P1-P3)
yiliang114 Jun 9, 2026
7a04c8c
fix(ci): use env var for steps.run.outcome (style consistency)
yiliang114 Jun 9, 2026
6c492c7
fix(ci): resolve Copilot review comments
yiliang114 Jun 9, 2026
10ca42e
ci(triage): tune review timeouts and run product-decision on self-hos…
yiliang114 Jun 10, 2026
fa5b72b
ci(triage): render stream-json logs readably and front-load PR templa…
yiliang114 Jun 10, 2026
69c188f
fix(triage): harden self-hosted review findings
yiliang114 Jun 10, 2026
e2a36d9
fix(triage): close product gate bypass, real-time logs, per-job scrat…
yiliang114 Jun 10, 2026
880a306
ci(triage): use REVIEW_OPENAI_* credentials for product-decision
yiliang114 Jun 10, 2026
6e9cb7c
ci(triage): restore queued-acknowledgement for explicit comment triggers
yiliang114 Jun 10, 2026
30b0ca9
ci(triage): restore review-comment and review-body trigger surfaces
yiliang114 Jun 10, 2026
c45cf89
ci(triage): re-check PR state after the debounce wait
yiliang114 Jun 10, 2026
f37c8b7
ci(triage): restore internal-author gate and timeout_minutes input
yiliang114 Jun 10, 2026
07bd197
ci(triage): require open PR state for comment triggers in resolve
yiliang114 Jun 10, 2026
86e36d0
ci: extend qwen PR review timeout to 90min and queue delay to 30min
yiliang114 Jun 10, 2026
cad67c1
Merge remote-tracking branch 'origin/main' into worktree-triage-ci-re…
yiliang114 Jun 10, 2026
86dc442
Merge branch 'ci/extend-pr-review-timeouts' (PR #4962)
yiliang114 Jun 10, 2026
2453112
ci(triage): align debounce comments with the 30-minute wait timer
yiliang114 Jun 10, 2026
a1f6a03
ci(triage): single-quote inline if: conditions for yamllint
yiliang114 Jun 11, 2026
d75fcf5
fix(ci): harden triage pipeline gating and ECS network resilience
yiliang114 Jun 11, 2026
37ae8a5
Merge remote-tracking branch 'origin/main' into worktree-triage-ci-re…
yiliang114 Jun 11, 2026
eeb006a
fix(ci): raise approval-decision turn limit and tolerate exit-53
yiliang114 Jun 12, 2026
b5c25dd
fix(ci): remove checkout retries, add tmux PR state precheck
yiliang114 Jun 12, 2026
6c8464c
fix(ci): restore checkout retry with 3 attempts and stall detection
yiliang114 Jun 12, 2026
5e7e703
fix(ci): remove checkout retries and reduce fetch-depth
yiliang114 Jun 12, 2026
ab84bc4
fix(ci): fail-closed verdict defaults and cover tmux in fallback
yiliang114 Jun 12, 2026
b36ffae
fix(ci): prune stale review worktrees before checkout; simplify runne…
yiliang114 Jun 12, 2026
459f029
feat(ci): auto-trigger triage for all PRs including forks
yiliang114 Jun 12, 2026
df1613d
fix(ci): split agent analysis from privileged publish in triage
yiliang114 Jun 12, 2026
95b925f
fix(ci): use find instead of ls for review JSON fallback glob
yiliang114 Jun 12, 2026
a3c6e32
fix(ci): use review model secrets for approval stage
yiliang114 Jun 14, 2026
d050856
fix(ci): checkout workflow ref during PR triage dispatch
yiliang114 Jun 14, 2026
64a31af
fix(ci): fail triage on missing agent outputs
yiliang114 Jun 14, 2026
471e5a6
fix(ci): handle unavailable review results
yiliang114 Jun 14, 2026
55ad9e1
fix(ci): dedupe unavailable review notices
yiliang114 Jun 14, 2026
3e4a561
fix(review): prewrite emit-only fallback result
yiliang114 Jun 14, 2026
0bb5e25
fix(ci): harden review result publishing
yiliang114 Jun 14, 2026
e409461
Merge remote-tracking branch 'origin/main' into HEAD
yiliang114 Jun 14, 2026
b298f91
fix(ci): address triage review follow-ups
yiliang114 Jun 15, 2026
70dc200
fix(ci): harden triage pipeline triggers
yiliang114 Jun 15, 2026
89e2d94
fix(ci): tighten triage pipeline permissions
yiliang114 Jun 15, 2026
12070a3
fix(ci): require product decision comment output
yiliang114 Jun 15, 2026
0610d61
fix(ci): harden triage review gates
yiliang114 Jun 15, 2026
38e2527
fix(ci): harden approval publish and drop persisted checkout creds
yiliang114 Jun 15, 2026
c92d1ca
fix(ci): bypass proxy for qwen model calls
yiliang114 Jun 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix(ci): harden triage review gates
  • Loading branch information
yiliang114 committed Jun 15, 2026
commit 0610d61a6c1a4b0acebb5c9212b0b8a37f47c1bc
35 changes: 31 additions & 4 deletions .github/workflows/qwen-pr-triage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,18 @@ jobs:
fi

RESULT="$QWEN_REVIEW_OUTPUT_FILE"
if [ -f "$RESULT" ]; then
RESULT_BODY="$(jq -r '.body // ""' "$RESULT" 2>/dev/null || echo "")"
if [[ "$RESULT_BODY" == *"<!-- qwen-triage:review-unavailable -->"* ]]; then
ALT_RESULT="$(find "$GITHUB_WORKSPACE/.qwen/tmp" -maxdepth 1 -name 'qwen-review-*-review.json' 2>/dev/null | head -1 || true)"
if [ -n "${ALT_RESULT:-}" ] && [ -f "$ALT_RESULT" ]; then
ALT_BODY="$(jq -r '.body // ""' "$ALT_RESULT" 2>/dev/null || echo "")"
if [[ "$ALT_BODY" != *"<!-- qwen-triage:review-unavailable -->"* ]]; then
RESULT="$ALT_RESULT"
fi
fi
fi
fi
if [ ! -f "$RESULT" ]; then
RESULT="$(find "$GITHUB_WORKSPACE/.qwen/tmp" -maxdepth 1 -name 'qwen-review-*-review.json' 2>/dev/null | head -1 || true)"
fi
Expand Down Expand Up @@ -864,7 +876,18 @@ jobs:
PR_NUMBER: '${{ needs.resolve.outputs.pr_number }}'
run: |
set -euo pipefail
pr_data="$(gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --json state,isDraft --jq '[.state, .isDraft] | @tsv')"
pr_data=""
for attempt in 1 2 3; do
if pr_data="$(gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --json state,isDraft --jq '[.state, .isDraft] | @tsv')"; then
break
fi
if [ "$attempt" -eq 3 ]; then
echo "::error::Failed to read PR state before tmux testing after 3 attempts"
exit 1
fi
echo "::warning::gh pr view failed before tmux testing (attempt ${attempt}/3), retrying..."
sleep $((attempt * 15))
done
IFS=$'\t' read -r state is_draft <<< "$pr_data"
if [ "$state" != "OPEN" ] || [ "$is_draft" = "true" ]; then
echo "::notice::Skipping tmux testing: PR #${PR_NUMBER} state=${state} draft=${is_draft}."
Expand Down Expand Up @@ -967,8 +990,9 @@ jobs:
# - review fail → /review posts CHANGES_REQUESTED, stop
# - review pass → approval-decision posts the final verdict here
# So this job only runs when product-decision passed (or was explicitly
# skipped via a review_only trigger) AND review completed with verdict
# pass — never alongside an upstream stage's request-changes.
# skipped via a review_only trigger), review completed with verdict pass,
# and tmux-testing produced a real verdict — never alongside an upstream
# stage's request-changes or a skipped tmux run.
if: >-
always() &&
needs.resolve.outputs.should_run == 'true' &&
Expand All @@ -978,7 +1002,10 @@ jobs:
needs.resolve.outputs.trigger_type == 'review_only') &&
needs.review.result == 'success' &&
needs.review.outputs.verdict == 'pass' &&
(needs.tmux-testing.result == 'success' || needs.tmux-testing.result == 'skipped')
needs.tmux-testing.result == 'success' &&
(needs.tmux-testing.outputs.verdict == 'pass' ||
needs.tmux-testing.outputs.verdict == 'fail' ||
needs.tmux-testing.outputs.verdict == 'timeout')
timeout-minutes: 10
concurrency:
group: 'qwen-pr-triage-pipeline-${{ needs.resolve.outputs.pr_number }}'
Expand Down
9 changes: 5 additions & 4 deletions .qwen/skills/approval-decision/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ Final gate in the PR triage pipeline. Read all prior stage results, reflect hone
`${TRIAGE_RESULTS_DIR:-/tmp/triage-results}`; the CI workflow publishes them
with the write-capable token.
- This skill ONLY runs when product-decision passed (or was explicitly
skipped by a review-only trigger) AND code review completed without
requesting changes. Upstream failures post their own request-changes and
stop the pipeline — so never re-litigate an upstream rejection here; this
stage weighs review nits + tmux results and issues the final verdict
skipped by a review-only trigger), code review completed without requesting
changes, and tmux-testing produced a real verdict (`pass`, `fail`, or
`timeout`). Upstream failures and tmux skips stop the pipeline — so never
re-litigate an upstream rejection here; this stage weighs review nits + tmux
results and issues the final verdict.

## Procedure

Expand Down
7 changes: 5 additions & 2 deletions .qwen/skills/triage/references/pr-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ When running locally via `/triage <N>`, follow this dependency chain:
├─ requested changes → STOP (the review's CHANGES_REQUESTED is the verdict)
│
▼ passed
tmux-real-user-testing (skip for fork PRs)
tmux-real-user-testing (internal PRs only)
│
├─ skipped → STOP (fork, closed, draft, or no runnable environment)
│
▼
/approval-decision (reads all prior comments, decides final verdict)
Expand All @@ -85,7 +87,8 @@ stage owns the formal review verdict on every path:
- **product-decision fails** → it posts request-changes; nothing else runs
- **review requests changes** → that review IS the verdict; tmux and approval skipped
- **tmux fails** → approval still runs and weighs the failure (review passed, so there is no competing verdict)
- **approval-decision runs only when product and review both passed** — it issues the single final approve/request-changes
- **tmux is skipped** → stop; do not approve without real-scenario evidence
- **approval-decision runs only when product, review, and tmux gates completed** — it issues the single final approve/request-changes

Each stage posts its own comment with a unique marker. Re-runs update in place.

Expand Down
35 changes: 24 additions & 11 deletions packages/core/src/skills/bundled/review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -620,17 +620,30 @@ fi
If there are **no confirmed findings**, submit a single-line review. Use `event=APPROVE` by default; if the presubmit JSON has `downgradeApprove=true`, use `event=COMMENT` and prepend the downgrade reasons to the body. **In emit-only mode**, build the same `{commit_id, event, body}` object and write it to `QWEN_REVIEW_OUTPUT_FILE` instead of calling `gh api`:

```bash
# downgradeApprove=false (non-self PR, green CI):
gh api repos/{owner}/{repo}/pulls/{pr_number}/reviews \
-f commit_id="{commit_sha}" \
-f event="APPROVE" \
-f body="No issues found. LGTM! ✅ _— YOUR_MODEL_ID via Qwen Code /review_"

# downgradeApprove=true (self-PR, CI failing, or CI still running):
gh api repos/{owner}/{repo}/pulls/{pr_number}/reviews \
-f commit_id="{commit_sha}" \
-f event="COMMENT" \
-f body="No review findings. Downgraded from Approve to Comment: <downgradeReasons joined with '; '>. _— YOUR_MODEL_ID via Qwen Code /review_"
EVENT="APPROVE"
BODY="No issues found. LGTM! ✅ _— YOUR_MODEL_ID via Qwen Code /review_"

if [ "<downgradeApprove>" = "true" ]; then
EVENT="COMMENT"
BODY="No review findings. Downgraded from Approve to Comment: <downgradeReasons joined with '; '>. _— YOUR_MODEL_ID via Qwen Code /review_"
fi

if [ -n "${QWEN_REVIEW_EMIT_ONLY:-}" ]; then
OUTPUT_FILE="${QWEN_REVIEW_OUTPUT_FILE:?set in emit-only mode}"
TMP_OUTPUT="${OUTPUT_FILE}.tmp"
jq -n \
--arg commit_id "{commit_sha}" \
--arg event "$EVENT" \
--arg body "$BODY" \
'{commit_id:$commit_id, event:$event, body:$body}' > "$TMP_OUTPUT"
mv "$TMP_OUTPUT" "$OUTPUT_FILE"
echo "Emit-only: wrote no-findings review JSON to $OUTPUT_FILE"
else
gh api repos/{owner}/{repo}/pulls/{pr_number}/reviews \
-f commit_id="{commit_sha}" \
-f event="$EVENT" \
-f body="$BODY"
fi
```

Clean up the JSON file in Step 11.
Expand Down
Loading