Skip to content

perf(cli): Optimize serve daemon startup #49282

perf(cli): Optimize serve daemon startup

perf(cli): Optimize serve daemon startup #49282

name: '🧐 Qwen Pull Request Review'
on:
pull_request_target:
types:
- 'opened'
- 'synchronize'
- 'reopened'
- 'ready_for_review'
- 'review_requested'
issue_comment:
types: ['created']
pull_request_review_comment:
types: ['created']
pull_request_review:
types: ['submitted']
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to review'
required: true
type: 'number'
review_mode:
description: 'dry-run (no comments) or comment (post inline comments)'
required: true
default: 'comment'
type: 'choice'
options:
- 'dry-run'
- 'comment'
timeout_minutes:
description: 'Review timeout in minutes'
required: false
default: '90'
type: 'number'
concurrency:
# PR lifecycle events share a PR-scoped group so new pushes restart the delay.
# Comment/review events use per-run groups to avoid cancelling active reviews.
group: >-
${{ github.event_name == 'pull_request_target' &&
format('qwen-pr-review-pr-{0}', github.event.pull_request.number) ||
format('qwen-pr-review-run-{0}', github.run_id) }}
cancel-in-progress: "${{ github.event_name == 'pull_request_target' && github.event.action == 'synchronize' }}"
jobs:
ack-review-request:
# KEEP IN SYNC with review-pr.if (explicit-trigger branches).
# Authorization is delegated to the `authorize` job (write+ permission);
# this `if` only matches the /review command shape.
needs: ['authorize']
if: |-
needs.authorize.outputs.should_review == 'true' &&
((github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
github.event.issue.state == 'open' &&
(github.event.comment.body == '@qwen-code /review' ||
startsWith(github.event.comment.body, '@qwen-code /review ') ||
startsWith(github.event.comment.body, format('@qwen-code /review{0}', '\n')))) ||
(github.event_name == 'pull_request_review_comment' &&
github.event.pull_request.state == 'open' &&
(github.event.comment.body == '@qwen-code /review' ||
startsWith(github.event.comment.body, '@qwen-code /review ') ||
startsWith(github.event.comment.body, format('@qwen-code /review{0}', '\n')))) ||
(github.event_name == 'pull_request_review' &&
github.event.pull_request.state == 'open' &&
(github.event.review.body == '@qwen-code /review' ||
startsWith(github.event.review.body, '@qwen-code /review ') ||
startsWith(github.event.review.body, format('@qwen-code /review{0}', '\n')))))
concurrency:
group: 'qwen-pr-ack-${{ github.event.issue.number || github.event.pull_request.number }}'
cancel-in-progress: false
runs-on: "${{ vars.MAINTAINER_ECS_RUNNER_DISABLED != 'true' && fromJSON('[\"self-hosted\", \"linux\", \"x64\", \"ecs-qwen\"]') || fromJSON('[\"ubuntu-latest\"]') }}"
timeout-minutes: 5
permissions:
pull-requests: 'write'
issues: 'write'
steps:
- name: 'Post queued acknowledgement'
env:
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
PR_NUMBER: '${{ github.event.issue.number || github.event.pull_request.number }}'
RUN_URL: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
run: |-
set -euo pipefail
PR_STATE="$(gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --json state --jq '.state')"
if [ "$PR_STATE" != "OPEN" ]; then
echo "PR #${PR_NUMBER} is ${PR_STATE}; skipping acknowledgement." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
ACK_BODY="<!-- qwen-review-ack -->_Qwen Code review request accepted. Review is queued in [workflow run](${RUN_URL})._"
EXISTING_ACK_ID="$(
gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \
--paginate \
-F per_page=100 \
| jq -sr '[.[][] | select(.body | contains("<!-- qwen-review-ack -->")) | select(.user.login == "github-actions[bot]")] | last | .id // empty'
)" || EXISTING_ACK_ID=""
if [ -n "$EXISTING_ACK_ID" ]; then
gh api \
--method PATCH \
"repos/${GITHUB_REPOSITORY}/issues/comments/${EXISTING_ACK_ID}" \
-f body="$ACK_BODY" > /dev/null
echo "Queued acknowledgement updated on PR #${PR_NUMBER}." >> "$GITHUB_STEP_SUMMARY"
else
gh pr comment "$PR_NUMBER" \
--repo "$GITHUB_REPOSITORY" \
--body "$ACK_BODY"
echo "Queued acknowledgement posted on PR #${PR_NUMBER}." >> "$GITHUB_STEP_SUMMARY"
fi
review-config:
if: |-
github.event_name == 'pull_request_target' &&
github.event.action == 'review_requested'
runs-on: "${{ vars.MAINTAINER_ECS_RUNNER_DISABLED != 'true' && fromJSON('[\"self-hosted\", \"linux\", \"x64\", \"ecs-qwen\"]') || fromJSON('[\"ubuntu-latest\"]') }}"
permissions: {}
outputs:
bot_login: '${{ steps.values.outputs.bot_login }}'
steps:
- name: 'Set review constants'
id: 'values'
run: |-
echo "bot_login=qwen-code-ci-bot" >> "$GITHUB_OUTPUT"
delay-automatic-review:
needs: ['authorize']
if: |-
github.event_name == 'pull_request_target' &&
(github.event.action == 'opened' ||
github.event.action == 'synchronize') &&
github.event.pull_request.state == 'open' &&
!github.event.pull_request.draft &&
needs.authorize.outputs.should_review == 'true'
# Stays on hosted: the 30-minute environment wait timer would otherwise idle a self-hosted ECS slot for the whole wait (GitHub allocates the runner before evaluating the environment timer).
runs-on: 'ubuntu-latest'
# Configured in repo settings with a 30-minute wait timer.
environment:
name: 'qwen-pr-review-delay'
deployment: false
permissions:
contents: 'read'
pull-requests: 'read'
outputs:
should_review: '${{ steps.pr_state.outputs.should_review }}'
steps:
- name: 'Re-check PR state'
id: 'pr_state'
env:
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
PR_NUMBER: '${{ github.event.pull_request.number }}'
run: |-
set -euo pipefail
pr_data="$(gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --json state,isDraft --jq '[.state, .isDraft] | @tsv')"
IFS=$'\t' read -r state is_draft <<< "$pr_data"
if [ "$state" != "OPEN" ]; then
echo "Skipping delayed review: PR #${PR_NUMBER} is ${state}." >> "$GITHUB_STEP_SUMMARY"
echo "should_review=false" >> "$GITHUB_OUTPUT"
exit 0
fi
if [ "$is_draft" = "true" ]; then
echo "Skipping delayed review: PR #${PR_NUMBER} is draft." >> "$GITHUB_STEP_SUMMARY"
echo "should_review=false" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "should_review=true" >> "$GITHUB_OUTPUT"
authorize:
# Single source of truth for "may this trigger spend review compute".
# The principal whose permission decides eligibility is the PR author
# (automatic PR events), the commenter (comment/review command events), or
# the requester (review_requested). They must have write+ permission.
# This replaces the per-path author_association checks, which are
# unreliable for fork PRs (a write user pushing from a fork is reported as
# CONTRIBUTOR, not MEMBER), so fork PRs by trusted authors now qualify.
# Only run for PR-target events and /review command comments — not every
# unrelated comment — to avoid spawning a job per comment. The downstream
# `if`s still do the exact /review body match; this prefix is just a filter.
if: |-
github.repository == 'QwenLM/qwen-code' &&
(github.event_name == 'pull_request_target' ||
((github.event_name == 'issue_comment' ||
github.event_name == 'pull_request_review_comment') &&
startsWith(github.event.comment.body, '@qwen-code /review')) ||
(github.event_name == 'pull_request_review' &&
startsWith(github.event.review.body, '@qwen-code /review')))
# Same-repo guard: this job loads CI_BOT_PAT, so fork-triggered runs stay on hosted (ephemeral); only in-repo PR events use the persistent ECS runner.
runs-on: "${{ (vars.MAINTAINER_ECS_RUNNER_DISABLED != 'true' && github.event.pull_request && github.event.pull_request.head.repo.full_name == github.repository) && fromJSON('[\"self-hosted\", \"linux\", \"x64\", \"ecs-qwen\"]') || fromJSON('[\"ubuntu-latest\"]') }}"
timeout-minutes: 5
permissions:
contents: 'read'
outputs:
should_review: '${{ steps.principal_permission.outputs.should_review }}'
steps:
- name: 'Check principal write permission'
id: 'principal_permission'
env:
# CI_BOT_PAT (not GITHUB_TOKEN): reading a user's collaborator
# permission requires write/maintain/admin access, which the
# GITHUB_TOKEN with contents:read does not have. Safe here — this job
# runs no agent, checks out nothing, and processes no untrusted PR
# content; it only reads event metadata and calls one read API.
GH_TOKEN: '${{ secrets.CI_BOT_PAT }}'
EVENT_NAME: '${{ github.event_name }}'
PR_ACTION: '${{ github.event.action }}'
PR_AUTHOR: '${{ github.event.pull_request.user.login }}'
COMMENT_USER: '${{ github.event.comment.user.login }}'
REVIEW_USER: '${{ github.event.review.user.login }}'
SENDER: '${{ github.event.sender.login }}'
run: |-
set -euo pipefail
# Select the principal whose permission gates this trigger.
case "$EVENT_NAME" in
pull_request_target)
if [ "$PR_ACTION" = "review_requested" ]; then
principal="$SENDER"
else
principal="$PR_AUTHOR"
fi
;;
issue_comment|pull_request_review_comment)
principal="$COMMENT_USER"
;;
pull_request_review)
principal="$REVIEW_USER"
;;
*)
principal=""
;;
esac
if [ -z "$principal" ]; then
echo "No principal resolved for ${EVENT_NAME}; denying." >> "$GITHUB_STEP_SUMMARY"
echo "should_review=false" >> "$GITHUB_OUTPUT"
exit 0
fi
# Fail closed: any API error or non-write permission denies the run.
api_error_file="$(mktemp)"
if ! permission="$(gh api "repos/${GITHUB_REPOSITORY}/collaborators/${principal}/permission" --jq '.permission' 2>"$api_error_file")"; then
api_error="$(cat "$api_error_file")"
rm -f "$api_error_file"
api_error="${api_error:-unknown error}"
api_error="${api_error//$'\r'/ }"
api_error="${api_error//$'\n'/ }"
echo "::error::Permission API call failed for ${principal}: ${api_error}"
echo "Failed to check permission for ${principal} (API error: ${api_error}); denying." >> "$GITHUB_STEP_SUMMARY"
echo "should_review=false" >> "$GITHUB_OUTPUT"
exit 0
fi
rm -f "$api_error_file"
case "$permission" in
admin|maintain|write)
echo "should_review=true" >> "$GITHUB_OUTPUT"
;;
*)
echo "Denying review: ${principal} permission is '${permission}' (needs write)." >> "$GITHUB_STEP_SUMMARY"
echo "should_review=false" >> "$GITHUB_OUTPUT"
;;
esac
review-pr:
needs: ['review-config', 'delay-automatic-review', 'authorize']
# pull_request_target routing (every path additionally gated by the
# `authorize` job = the principal has write+ permission):
# - review_requested checks the requester and skips delay
# - opened/synchronize uses delay-automatic-review
# - reopened/ready_for_review runs immediately
# KEEP IN SYNC with ack-review-request.if (explicit-trigger branches).
if: |-
always() &&
(github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request_target' &&
github.event.pull_request.state == 'open' &&
!github.event.pull_request.draft &&
needs.authorize.outputs.should_review == 'true' &&
((github.event.action == 'review_requested' &&
github.event.requested_reviewer.login == needs.review-config.outputs.bot_login) ||
(github.event.action != 'review_requested' &&
((github.event.action != 'opened' &&
github.event.action != 'synchronize') ||
needs.delay-automatic-review.outputs.should_review == 'true')))) ||
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
github.event.issue.state == 'open' &&
(github.event.comment.body == '@qwen-code /review' ||
startsWith(github.event.comment.body, '@qwen-code /review ') ||
startsWith(github.event.comment.body, format('@qwen-code /review{0}', '\n'))) &&
needs.authorize.outputs.should_review == 'true') ||
(github.event_name == 'pull_request_review_comment' &&
github.event.pull_request.state == 'open' &&
(github.event.comment.body == '@qwen-code /review' ||
startsWith(github.event.comment.body, '@qwen-code /review ') ||
startsWith(github.event.comment.body, format('@qwen-code /review{0}', '\n'))) &&
needs.authorize.outputs.should_review == 'true') ||
(github.event_name == 'pull_request_review' &&
github.event.pull_request.state == 'open' &&
(github.event.review.body == '@qwen-code /review' ||
startsWith(github.event.review.body, '@qwen-code /review ') ||
startsWith(github.event.review.body, format('@qwen-code /review{0}', '\n'))) &&
needs.authorize.outputs.should_review == 'true'))
timeout-minutes: 90
runs-on: ['self-hosted', 'linux', 'x64', 'ecs-qwen']
permissions:
contents: 'read'
pull-requests: 'write'
issues: 'write'
steps:
# Self-hosted runners reuse the workspace, so an interrupted review can
# leave a stale `.qwen/tmp/review-pr-*` worktree or `qwen-review/*` branch
# that trips the checkout below. Prune defensively; never fail the job.
- name: 'Clean stale review worktrees'
run: |-
set -uo pipefail
# `.git` is a directory in a normal checkout but a gitlink file in a
# worktree; -e covers both, and a missing .git (first run) too.
if [ ! -e .git ]; then
echo "no prior workspace; nothing to clean"
exit 0
fi
rm -rf .qwen/tmp/review-pr-* 2>/dev/null || true
git worktree prune -v || true
git for-each-ref --format='%(refname:short)' 'refs/heads/qwen-review/*' \
| while read -r stale_ref; do
if [ -n "$stale_ref" ]; then
git branch -D "$stale_ref" || true
fi
done
git worktree prune -v || true
echo "stale review worktrees cleaned"
# SECURITY: checkout trusted base code; /review fetches PR diff context.
- name: 'Checkout base branch'
uses: 'actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd' # v6.0.2
with:
ref: '${{ github.event.repository.default_branch }}'
fetch-depth: 0
- name: 'Resolve PR context'
id: 'context'
env:
TRIGGER_BODY: "${{ github.event.comment.body || github.event.review.body || '' }}"
run: |-
set -euo pipefail
TRIGGER_COMMAND="${TRIGGER_BODY%%$'\n'*}"
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
PR_NUMBER="${{ github.event.inputs.pr_number }}"
REVIEW_MODE="${{ github.event.inputs.review_mode }}"
elif [ "${{ github.event_name }}" = "issue_comment" ]; then
if ! printf '%s\n' "$TRIGGER_COMMAND" | grep -Eq '^@qwen-code[[:space:]]+/review([[:space:]]|$)'; then
echo "should_run=false" >> "$GITHUB_OUTPUT"
exit 0
fi
PR_NUMBER="${{ github.event.issue.number }}"
REVIEW_MODE="comment"
elif [ "${{ github.event_name }}" = "pull_request_target" ] ||
[ "${{ github.event_name }}" = "pull_request_review_comment" ] ||
[ "${{ github.event_name }}" = "pull_request_review" ]; then
if [ "${{ github.event_name }}" != "pull_request_target" ] &&
! printf '%s\n' "$TRIGGER_COMMAND" | grep -Eq '^@qwen-code[[:space:]]+/review([[:space:]]|$)'; then
echo "should_run=false" >> "$GITHUB_OUTPUT"
exit 0
fi
PR_NUMBER="${{ github.event.pull_request.number }}"
REVIEW_MODE="comment"
else
echo "Unsupported event: ${{ github.event_name }}" >&2
exit 1
fi
TIMEOUT_MINUTES="${{ github.event.inputs.timeout_minutes || '90' }}"
{
echo "should_run=true"
echo "pr_number=$PR_NUMBER"
echo "review_mode=$REVIEW_MODE"
echo "timeout_minutes=$TIMEOUT_MINUTES"
} >> "$GITHUB_OUTPUT"
- name: 'Run review'
id: 'review'
if: "steps.context.outputs.should_run == 'true'"
env:
GH_TOKEN: '${{ secrets.CI_BOT_PAT }}'
OPENAI_API_KEY: '${{ secrets.REVIEW_OPENAI_API_KEY }}'
OPENAI_BASE_URL: '${{ secrets.REVIEW_OPENAI_BASE_URL }}'
OPENAI_MODEL: '${{ vars.QWEN_PR_REVIEW_MODEL }}'
PR_NUMBER: '${{ steps.context.outputs.pr_number }}'
REVIEW_MODE: '${{ steps.context.outputs.review_mode }}'
TIMEOUT_MINUTES: '${{ steps.context.outputs.timeout_minutes }}'
run: |-
set -euo pipefail
fail() {
local message="$1"
local code="${2:-1}"
echo "$message" >&2
echo "failure_reason=$message" >> "$GITHUB_OUTPUT"
echo "$message" >> "$GITHUB_STEP_SUMMARY"
exit "$code"
}
REPO="${GITHUB_REPOSITORY}"
REVIEW_URL="${GITHUB_SERVER_URL}/${REPO}/pull/${PR_NUMBER}"
LOG_PATH="${RUNNER_TEMP:-/tmp}/qwen-review-pr-${PR_NUMBER}.jsonl"
trap 'rm -f "$LOG_PATH"' EXIT
if [ -z "${GH_TOKEN:-}" ]; then
fail "CI_BOT_PAT secret is required for Qwen PR review."
fi
if [ -z "${OPENAI_API_KEY:-}" ]; then
fail "REVIEW_OPENAI_API_KEY secret is required for Qwen PR review."
fi
if [ -z "${OPENAI_BASE_URL:-}" ]; then
fail "REVIEW_OPENAI_BASE_URL secret is required for Qwen PR review."
fi
if ! command -v qwen >/dev/null 2>&1; then
fail "qwen CLI is required on the review runner."
fi
# shellcheck disable=SC2016
configure_qwen_network() {
local openai_host proxy_bin
if ! command -v node >/dev/null 2>&1; then
fail "node is required to parse OPENAI_BASE_URL for the proxy bypass."
fi
openai_host="$(node -e 'console.log(new URL(process.env.OPENAI_BASE_URL).hostname)')"
if [ -z "$openai_host" ]; then
fail "Could not parse a hostname from OPENAI_BASE_URL."
fi
export NO_PROXY="${NO_PROXY:+$NO_PROXY,}${openai_host}"
export no_proxy="${no_proxy:+$no_proxy,}${openai_host}"
# qwen currently reads HTTP(S)_PROXY directly and does not apply
# NO_PROXY when constructing its proxy agent. Clear proxy env for
# qwen itself, while restoring it for child gh/git commands.
export QWEN_CI_HTTPS_PROXY="${HTTPS_PROXY:-}"
export QWEN_CI_https_proxy="${https_proxy:-}"
export QWEN_CI_HTTP_PROXY="${HTTP_PROXY:-}"
export QWEN_CI_http_proxy="${http_proxy:-}"
proxy_bin="${RUNNER_TEMP:-/tmp}/qwen-network-bin"
mkdir -p "$proxy_bin"
if command -v gh >/dev/null 2>&1; then
local real_gh
real_gh="$(command -v gh)"
export QWEN_CI_REAL_GH="$real_gh"
{
printf '%s\n' '#!/usr/bin/env bash'
printf '%s\n' '[ -n "${QWEN_CI_HTTPS_PROXY:-}" ] && export HTTPS_PROXY="$QWEN_CI_HTTPS_PROXY"'
printf '%s\n' '[ -n "${QWEN_CI_https_proxy:-}" ] && export https_proxy="$QWEN_CI_https_proxy"'
printf '%s\n' '[ -n "${QWEN_CI_HTTP_PROXY:-}" ] && export HTTP_PROXY="$QWEN_CI_HTTP_PROXY"'
printf '%s\n' '[ -n "${QWEN_CI_http_proxy:-}" ] && export http_proxy="$QWEN_CI_http_proxy"'
printf '%s\n' 'exec "$QWEN_CI_REAL_GH" "$@"'
} > "$proxy_bin/gh"
chmod +x "$proxy_bin/gh"
fi
if command -v git >/dev/null 2>&1; then
local real_git
real_git="$(command -v git)"
export QWEN_CI_REAL_GIT="$real_git"
{
printf '%s\n' '#!/usr/bin/env bash'
printf '%s\n' '[ -n "${QWEN_CI_HTTPS_PROXY:-}" ] && export HTTPS_PROXY="$QWEN_CI_HTTPS_PROXY"'
printf '%s\n' '[ -n "${QWEN_CI_https_proxy:-}" ] && export https_proxy="$QWEN_CI_https_proxy"'
printf '%s\n' '[ -n "${QWEN_CI_HTTP_PROXY:-}" ] && export HTTP_PROXY="$QWEN_CI_HTTP_PROXY"'
printf '%s\n' '[ -n "${QWEN_CI_http_proxy:-}" ] && export http_proxy="$QWEN_CI_http_proxy"'
printf '%s\n' 'exec "$QWEN_CI_REAL_GIT" "$@"'
} > "$proxy_bin/git"
chmod +x "$proxy_bin/git"
fi
export PATH="$proxy_bin:$PATH"
unset HTTPS_PROXY https_proxy HTTP_PROXY http_proxy
echo "qwen_path=$(command -v qwen)"
qwen --version
echo "openai_host=${openai_host}"
echo "qwen_http_proxy=disabled"
if [ -n "${QWEN_CI_HTTPS_PROXY}${QWEN_CI_https_proxy}${QWEN_CI_HTTP_PROXY}${QWEN_CI_http_proxy}" ]; then
echo "child_git_github_proxy=restored"
else
echo "child_git_github_proxy=unset"
fi
}
configure_qwen_network
case "$TIMEOUT_MINUTES" in
''|*[!0-9]*)
fail "Invalid timeout_minutes: ${TIMEOUT_MINUTES}"
;;
esac
if [ "$TIMEOUT_MINUTES" -le 5 ]; then
fail "timeout_minutes must be greater than 5"
fi
if [ "$TIMEOUT_MINUTES" -gt 90 ]; then
fail "timeout_minutes must not exceed the 90 minute job timeout"
fi
if ! PR_STATE="$(gh pr view "$PR_NUMBER" --repo "$REPO" --json state --jq '.state')"; then
fail "Failed to determine state for PR #${PR_NUMBER}."
fi
if [ "$PR_STATE" != "OPEN" ]; then
echo "Skipping: PR #${PR_NUMBER} is ${PR_STATE}." | tee -a "$GITHUB_STEP_SUMMARY"
exit 0
fi
PROMPT="/review ${REVIEW_URL}"
if [ "$REVIEW_MODE" = "comment" ]; then
PROMPT="${PROMPT} --comment"
fi
MODEL_ARGS=()
if [ -n "${OPENAI_MODEL:-}" ]; then
MODEL_ARGS=(--model "$OPENAI_MODEL")
fi
QWEN_TIMEOUT=$((TIMEOUT_MINUTES - 5))
set +e
# GNU timeout times out command children unless --foreground is used.
timeout --kill-after=10s "${QWEN_TIMEOUT}m" qwen \
--auth-type openai \
--approval-mode yolo \
"${MODEL_ARGS[@]}" \
--prompt "$PROMPT" \
--output-format stream-json \
| tee "$LOG_PATH"
pipeline_status=("${PIPESTATUS[@]}")
set -e
qwen_status="${pipeline_status[0]}"
tee_status="${pipeline_status[1]}"
if [ "$tee_status" -ne 0 ]; then
fail "Failed to write qwen review log."
fi
if [ "$qwen_status" -eq 124 ]; then
fail "Qwen review timed out after ${QWEN_TIMEOUT} minutes."
fi
if [ "$qwen_status" -ne 0 ]; then
fail "Qwen review exited with status ${qwen_status}."
fi
if [ ! -s "$LOG_PATH" ]; then
fail "Qwen review completed but produced no output."
fi
# qwen can exit 0 even when the run aborted mid-review (e.g. the model
# connection dropped before the review was posted). In that case the
# final stream-json `result` event still renders the error inline and
# carries subtype=success / is_error=false, so the checks above all
# pass and the job goes green without ever posting a comment. Inspect
# the terminal `result` event explicitly and treat an errored or
# aborted run as a failure so the fallback-comment step runs.
RESULT_LINE="$(grep '"type":"result"' "$LOG_PATH" | tail -n1 || true)"
if [ -z "$RESULT_LINE" ]; then
fail "Qwen review produced no result event (run aborted before completion)."
fi
RESULT_IS_ERROR="$(printf '%s' "$RESULT_LINE" | jq -r '.is_error // false')"
RESULT_SUBTYPE="$(printf '%s' "$RESULT_LINE" | jq -r '.subtype // ""')"
RESULT_TEXT="$(printf '%s' "$RESULT_LINE" | jq -r '.result // ""')"
if [ "$RESULT_IS_ERROR" = "true" ] || [ "$RESULT_SUBTYPE" != "success" ]; then
fail "Qwen review ended in an error result (subtype=${RESULT_SUBTYPE}, is_error=${RESULT_IS_ERROR})."
fi
case "$RESULT_TEXT" in
*"[API Error"*)
fail "Qwen review aborted with an API error before posting comments."
;;
esac
- name: 'Post fallback comment on failure'
if: |-
failure() &&
steps.context.outputs.should_run == 'true' &&
steps.context.outputs.review_mode == 'comment' &&
steps.context.outputs.pr_number != ''
env:
GH_TOKEN: '${{ secrets.CI_BOT_PAT }}'
FAILURE_REASON: "${{ steps.review.outputs.failure_reason || 'Run review failed. See workflow logs for details.' }}"
PR_NUMBER: '${{ steps.context.outputs.pr_number }}'
RUN_URL: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
run: |-
gh pr comment "$PR_NUMBER" \
--repo "$GITHUB_REPOSITORY" \
--body "_Qwen Code review did not complete successfully: ${FAILURE_REASON} See [workflow logs](${RUN_URL})._"