Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 4 additions & 3 deletions .github/workflows/qwen-triage-finalize.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ jobs:
CI_BEGIN="<!-- qwen-triage-ci sha=${HEAD_SHA} -->"
CI_END='<!-- /qwen-triage-ci -->'
APPROVE_MARKER="<!-- qwen-triage approve-on-green sha=${HEAD_SHA} -->"
STATUS_MARKER='<!-- qwen-triage stage=status -->'
STATUS_MARKER='<!-- qwen-triage lifecycle -->'
LEGACY_STATUS_MARKER='<!-- qwen-triage stage=status -->'

if ! BOT_LOGIN="$(gh api user --jq '.login')" || [ -z "$BOT_LOGIN" ]; then
echo "::warning::Cannot resolve the bot identity; skipping (markers cannot be author-verified)."
Expand Down Expand Up @@ -320,8 +321,8 @@ jobs:
;;
*) return 0 ;;
esac
sid="$(jq -r --arg bot "$BOT_LOGIN" --arg m "$STATUS_MARKER" \
'[.[] | select(.user.login == $bot) | select(.body | contains($m))] | last | .id // empty' \
sid="$(jq -r --arg bot "$BOT_LOGIN" --arg m "$STATUS_MARKER" --arg legacy "$LEGACY_STATUS_MARKER" \
'[.[] | select(.user.login == $bot) | select((.body | startswith($m)) or (.body | startswith($legacy)))] | last | .id // empty' \
"/tmp/comments-$pr.json")"
[ -n "$sid" ] || return 0
printf '%s\n\n%s\n\n%s' "$STATUS_MARKER" "$en" "$zh" > /tmp/status-body.md
Expand Down
94 changes: 52 additions & 42 deletions .github/workflows/qwen-triage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -446,26 +446,31 @@ jobs:
RUN_URL: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
run: |-
set -uo pipefail
MARKER='<!-- qwen-triage stage=status -->'
printf -v BODY '%s\n\n%s\n\n%s' \
"$MARKER" \
"🔄 **Qwen Triage is running** — [watch live progress]($RUN_URL). Stage results will post in this thread as they complete." \
"🔄 **Qwen Triage 正在运行** —— [查看实时进度]($RUN_URL)。各阶段结果完成后会更新在本线程。"
EXISTING_ID="$(
gh api "repos/$GITHUB_REPOSITORY/issues/$NUMBER/comments" \
--method GET --paginate -F per_page=100 |
jq -rs --arg m "$MARKER" \
'[.[][] | select(.body | contains($m))] | last | .id // empty'
)" || EXISTING_ID=''
if [ -n "$EXISTING_ID" ]; then
gh api --method PATCH \
"repos/$GITHUB_REPOSITORY/issues/comments/$EXISTING_ID" \
-f body="$BODY" >/dev/null ||
echo "::warning::Failed to update triage status comment; continuing." >&2
MARKER='<!-- qwen-triage lifecycle -->'
LEGACY_MARKER='<!-- qwen-triage stage=status -->'
if ! BOT_LOGIN="$(gh api user --jq '.login')" || [ -z "$BOT_LOGIN" ]; then
echo "::warning::Cannot resolve bot identity; skipping status comment upsert."
else
gh api "repos/$GITHUB_REPOSITORY/issues/$NUMBER/comments" \
-f body="$BODY" >/dev/null ||
echo "::warning::Failed to post triage status comment; continuing." >&2
printf -v BODY '%s\n\n%s\n\n%s' \
"$MARKER" \
"🔄 **Qwen Triage is running** — [watch live progress]($RUN_URL). Stage results will post in this thread as they complete." \
"🔄 **Qwen Triage 正在运行** —— [查看实时进度]($RUN_URL)。各阶段结果完成后会更新在本线程。"
EXISTING_ID="$(
gh api "repos/$GITHUB_REPOSITORY/issues/$NUMBER/comments" \
--method GET --paginate -F per_page=100 |
jq -rs --arg m "$MARKER" --arg legacy "$LEGACY_MARKER" --arg bot "$BOT_LOGIN" \
'[.[][] | select(.user.login == $bot) | select((.body | startswith($m)) or (.body | startswith($legacy)))] | last | .id // empty'
)" || EXISTING_ID=''
if [ -n "$EXISTING_ID" ]; then
gh api --method PATCH \
"repos/$GITHUB_REPOSITORY/issues/comments/$EXISTING_ID" \
-f body="$BODY" >/dev/null ||
echo "::warning::Failed to update triage status comment; continuing." >&2
else
gh api "repos/$GITHUB_REPOSITORY/issues/$NUMBER/comments" \
-f body="$BODY" >/dev/null ||
echo "::warning::Failed to post triage status comment; continuing." >&2
fi
fi

- name: 'Inject model name into triage signature'
Expand Down Expand Up @@ -808,30 +813,35 @@ jobs:
TRIAGE_OUTCOME: '${{ steps.triage.outcome }}'
run: |-
set -uo pipefail
MARKER='<!-- qwen-triage stage=status -->'
if [ "${TRIAGE_OUTCOME:-}" = 'success' ]; then
EN="✅ **Qwen Triage finished** — [view run]($RUN_URL). See the stage comments in this thread for the result."
ZH="✅ **Qwen Triage 已完成** —— [查看运行]($RUN_URL)。结果见本线程中的各阶段评论。"
else
EN="⚠️ **Qwen Triage ended early** — [view run]($RUN_URL). It stopped before finishing; check the run log."
ZH="⚠️ **Qwen Triage 提前结束** —— [查看运行]($RUN_URL)。未跑完,请查看运行日志。"
fi
printf -v BODY '%s\n\n%s\n\n%s' "$MARKER" "$EN" "$ZH"
EXISTING_ID="$(
gh api "repos/$GITHUB_REPOSITORY/issues/$NUMBER/comments" \
--method GET --paginate -F per_page=100 |
jq -rs --arg m "$MARKER" \
'[.[][] | select(.body | contains($m))] | last | .id // empty'
)" || EXISTING_ID=''
if [ -n "$EXISTING_ID" ]; then
gh api --method PATCH \
"repos/$GITHUB_REPOSITORY/issues/comments/$EXISTING_ID" \
-f body="$BODY" >/dev/null ||
echo "::warning::Failed to finalize triage status comment; continuing." >&2
MARKER='<!-- qwen-triage lifecycle -->'
LEGACY_MARKER='<!-- qwen-triage stage=status -->'
if ! BOT_LOGIN="$(gh api user --jq '.login')" || [ -z "$BOT_LOGIN" ]; then
echo "::warning::Cannot resolve bot identity; skipping final status comment upsert."
else
gh api "repos/$GITHUB_REPOSITORY/issues/$NUMBER/comments" \
-f body="$BODY" >/dev/null ||
echo "::warning::Failed to post final triage status comment; continuing." >&2
if [ "${TRIAGE_OUTCOME:-}" = 'success' ]; then
EN="✅ **Qwen Triage finished** — [view run]($RUN_URL). See the stage comments in this thread for the result."
ZH="✅ **Qwen Triage 已完成** —— [查看运行]($RUN_URL)。结果见本线程中的各阶段评论。"
else
EN="⚠️ **Qwen Triage ended early** — [view run]($RUN_URL). It stopped before finishing; check the run log."
ZH="⚠️ **Qwen Triage 提前结束** —— [查看运行]($RUN_URL)。未跑完,请查看运行日志。"
fi
printf -v BODY '%s\n\n%s\n\n%s' "$MARKER" "$EN" "$ZH"
EXISTING_ID="$(
gh api "repos/$GITHUB_REPOSITORY/issues/$NUMBER/comments" \
--method GET --paginate -F per_page=100 |
jq -rs --arg m "$MARKER" --arg legacy "$LEGACY_MARKER" --arg bot "$BOT_LOGIN" \
'[.[][] | select(.user.login == $bot) | select((.body | startswith($m)) or (.body | startswith($legacy)))] | last | .id // empty'
)" || EXISTING_ID=''
if [ -n "$EXISTING_ID" ]; then
gh api --method PATCH \
"repos/$GITHUB_REPOSITORY/issues/comments/$EXISTING_ID" \
-f body="$BODY" >/dev/null ||
echo "::warning::Failed to finalize triage status comment; continuing." >&2
else
gh api "repos/$GITHUB_REPOSITORY/issues/$NUMBER/comments" \
-f body="$BODY" >/dev/null ||
echo "::warning::Failed to post final triage status comment; continuing." >&2
fi
fi

# On-demand real-user testing: a write-permission user comments
Expand Down
13 changes: 12 additions & 1 deletion scripts/tests/qwen-triage-finalize-workflow.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,17 @@ describe('qwen-triage-finalize workflow', () => {
// Marker text is public: anyone can paste it into a PR comment. Every
// lookup that acts on a marker must filter on the bot identity first.
expect(script).toContain("gh api user --jq '.login'");
// Status-comment lookup: body starts with the marker (a comment that
// merely quotes it must not be PATCHed), so startswith is the strict match.
expect(script).toContain(
'select(.user.login == $bot) | select((.body | startswith($m)) or (.body | startswith($legacy)))',
);
// CI-region, approve-marker, and new-head-marker lookups embed the marker
// inside the body (not at the start), so contains is the correct match —
// still author-filtered.
const authorFiltered = script.match(
/select\(\.user\.login == \$bot\) \| select\(\.body \| contains\(\$m\)\)/g,
);
// CI-region lookup, approve-marker lookup, and status-comment lookup.
expect(authorFiltered?.length).toBeGreaterThanOrEqual(3);
});

Expand Down Expand Up @@ -139,6 +146,10 @@ describe('qwen-triage-finalize workflow', () => {
expect(workflowText).toContain(
"HEAD_SHA: '${{ github.event.workflow_run.head_sha }}'",
);
expect(script).toContain("STATUS_MARKER='<!-- qwen-triage lifecycle -->'");
expect(script).toContain(
"LEGACY_STATUS_MARKER='<!-- qwen-triage stage=status -->'",
);
});

it('sanitizes attacker-influenced check names before the table', () => {
Expand Down
28 changes: 25 additions & 3 deletions scripts/tests/qwen-triage-workflow.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,21 +233,43 @@ describe('qwen-triage tmux workflow', () => {
it('posts an early live-progress status comment and finalizes the same one', () => {
const statusStep = step('Post triage status comment');
// Announced up front (before the long agent step) with the live run link.
expect(statusStep).toContain('<!-- qwen-triage stage=status -->');
expect(statusStep).toContain("MARKER='<!-- qwen-triage lifecycle -->'");
expect(statusStep).toContain(
"LEGACY_MARKER='<!-- qwen-triage stage=status -->'",
);
expect(statusStep).toContain('actions/runs/${{ github.run_id }}');
expect(statusStep).toContain('watch live progress');
// Upsert by marker so a re-run reuses the one comment instead of stacking.
expect(statusStep).toContain('contains($m)');
// startswith (not contains) prevents matching a comment that merely quotes
// the marker; the bot-author filter prevents matching a human's comment.
expect(statusStep).toContain("gh api user --jq '.login'");
expect(statusStep).toContain(
'Cannot resolve bot identity; skipping status comment upsert.',
);
expect(statusStep).toContain('select(.user.login == $bot)');
expect(statusStep).toContain('startswith($m)');
expect(statusStep).not.toContain('contains($m)');
expect(statusStep).toContain('--method PATCH');
// Best-effort: a failed status post warns and continues, never fails triage.
expect(statusStep).toContain('set -uo pipefail');
expect(statusStep).toContain('continuing.');

const finalizeStep = step('Finalize triage status comment');
// Runs on both outcomes and edits the SAME marker comment (no second post).
expect(finalizeStep).toContain('<!-- qwen-triage stage=status -->');
expect(finalizeStep).toContain(
"MARKER='<!-- qwen-triage lifecycle -->'",
);
expect(finalizeStep).toContain(
"LEGACY_MARKER='<!-- qwen-triage stage=status -->'",
);
expect(finalizeStep).toContain('success() || failure()');
expect(finalizeStep).toContain('steps.triage.outcome');
expect(finalizeStep).toContain(
'Cannot resolve bot identity; skipping final status comment upsert.',
);
expect(finalizeStep).toContain('select(.user.login == $bot)');
expect(finalizeStep).toContain('startswith($m)');
expect(finalizeStep).not.toContain('contains($m)');
expect(finalizeStep).toContain('--method PATCH');
expect(finalizeStep).toContain('Qwen Triage finished');
expect(finalizeStep).toContain('ended early');
Expand Down
Loading