From e7efb4b25ec66b57d141551b8eb043cd828e4ae4 Mon Sep 17 00:00:00 2001 From: yiliang114 <1204183885@qq.com> Date: Sun, 21 Jun 2026 12:51:21 +0800 Subject: [PATCH 01/10] fix(ci): harden tmux triage reporting --- .github/workflows/qwen-triage.yml | 34 +++++++++---- scripts/tests/qwen-triage-workflow.test.js | 55 ++++++++++++++++++++++ 2 files changed, 81 insertions(+), 8 deletions(-) create mode 100644 scripts/tests/qwen-triage-workflow.test.js diff --git a/.github/workflows/qwen-triage.yml b/.github/workflows/qwen-triage.yml index 7bf80809da0..65fa33fccbd 100644 --- a/.github/workflows/qwen-triage.yml +++ b/.github/workflows/qwen-triage.yml @@ -272,11 +272,11 @@ jobs: verdict: '${{ steps.run.outputs.verdict || steps.prepare.outputs.verdict || steps.pr.outputs.verdict }}' failure_phase: '${{ steps.prepare.outputs.failure_phase }}' steps: - - name: 'Install tmux runner tools' + - name: 'Install PR resolver tools' run: |- set -euo pipefail apt-get update - apt-get install -y --no-install-recommends ca-certificates curl git gnupg jq tmux util-linux + apt-get install -y --no-install-recommends ca-certificates curl git gnupg jq install -d -m 755 /etc/apt/keyrings curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \ @@ -287,10 +287,7 @@ jobs: apt-get update apt-get install -y --no-install-recommends gh - npm install -g '@qwen-code/qwen-code@latest' - qwen --version gh --version - tmux -V - name: 'Resolve PR and check state' id: 'pr' @@ -365,6 +362,17 @@ jobs: ref: 'refs/pull/${{ steps.pr.outputs.pr_number }}/merge' fetch-depth: 1 + - name: 'Install tmux runner tools' + if: "steps.pr.outputs.decision == 'run'" + run: |- + set -euo pipefail + apt-get update + apt-get install -y --no-install-recommends tmux util-linux + + npm install -g '@qwen-code/qwen-code@latest' + qwen --version + tmux -V + - name: 'Install and build PR app' id: 'prepare' if: "steps.pr.outputs.decision == 'run'" @@ -654,6 +662,7 @@ jobs: "GH_TOKEN=" "OPENAI_API_KEY=qwen-loopback-proxy" "OPENAI_BASE_URL=$LOCAL_OPENAI_BASE_URL" + "OPENAI_MODEL=$OPENAI_MODEL" "NO_PROXY=${NO_PROXY:-}" "no_proxy=${no_proxy:-}" "QWEN_CI_HTTPS_PROXY=${QWEN_CI_HTTPS_PROXY:-}" @@ -778,9 +787,7 @@ jobs: if [ "$(wc -c < "$file")" -gt "$max" ]; then content="${content}"$'\n\n...truncated -- full log in the run artifacts.' fi - content="${content//&/&}" - content="${content///>}" + content="$(printf '%s' "$content" | sed -e 's/&/\&/g' -e 's//\>/g')" printf '
\n%s\n\n
\n' "$summary"
             printf '%s\n' "$content"
             printf '
\n\n
\n\n' @@ -816,6 +823,17 @@ jobs: emit_block 'Install/build log' "$PREPARE_LOG" 20000 printf '%s\n' '— _Qwen Code · tmux real-user testing_' } > "$BODY_FILE" + elif [ "$VERDICT" = "timeout" ] || [ "$VERDICT" = "infra-error" ]; then + REPORT="$(find tmux-results -name 'report.md' 2>/dev/null | head -1 || true)" + TRANSCRIPT="$(find tmux-results -name 'tmux-readable-full.log' 2>/dev/null | head -1 || true)" + { + printf '%s\n\n' '' + printf '**tmux real-user testing: %s** - [workflow run](%s)\n\n' "$VERDICT" "$RUN_URL" + printf 'The tmux test did not complete, so this is not a pass/fail result for the affected flow. See the workflow run and artifacts for details.\n\n' + emit_block 'E2E test report' "$REPORT" 20000 + emit_block 'Full tmux transcript' "$TRANSCRIPT" 30000 + printf '%s\n' '— _Qwen Code · tmux real-user testing_' + } > "$BODY_FILE" else REPORT="$(find tmux-results -name 'report.md' 2>/dev/null | head -1 || true)" TRANSCRIPT="$(find tmux-results -name 'tmux-readable-full.log' 2>/dev/null | head -1 || true)" diff --git a/scripts/tests/qwen-triage-workflow.test.js b/scripts/tests/qwen-triage-workflow.test.js new file mode 100644 index 00000000000..d3541218fdc --- /dev/null +++ b/scripts/tests/qwen-triage-workflow.test.js @@ -0,0 +1,55 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +import { readFileSync } from 'node:fs'; +import { describe, expect, it } from 'vitest'; + +const workflow = readFileSync('.github/workflows/qwen-triage.yml', 'utf8'); + +function step(name) { + const match = workflow.match( + new RegExp( + `\\n\\s+- name: '${name}'[\\s\\S]*?(?=\\n\\s+- name: '|\\n\\s{2}[a-zA-Z0-9_-]+:|$)`, + ), + ); + return match?.[0] ?? ''; +} + +describe('qwen-triage tmux workflow', () => { + it('escapes embedded tmux artifacts without bash pattern replacement ampersands', () => { + const postStep = step('Post tmux result comment'); + + expect(postStep).not.toContain('content="${content//&/&}"'); + expect(postStep).not.toContain('content="${content///>}"'); + expect(postStep).toContain("sed -e 's/&/\\&/g'"); + expect(postStep).toContain("-e 's//\\>/g'"); + }); + + it('passes the selected OpenAI model into the app under tmux test', () => { + const runStep = step('Run tmux real-user testing'); + + expect(runStep).toContain('"OPENAI_MODEL=$OPENAI_MODEL"'); + }); + + it('reports timeout and infra-error without claiming the flow was exercised', () => { + const postStep = step('Post tmux result comment'); + + expect(postStep).toContain( + 'elif [ "$VERDICT" = "timeout" ] || [ "$VERDICT" = "infra-error" ]; then', + ); + }); + + it('installs the heavy tmux test harness only for runnable PRs', () => { + const installStep = step('Install tmux runner tools'); + + expect(installStep).toContain('if: "steps.pr.outputs.decision == \'run\'"'); + expect( + workflow.indexOf("- name: 'Resolve PR and check state'"), + ).toBeLessThan(workflow.indexOf("- name: 'Install tmux runner tools'")); + }); +}); From 669a92e5e9a6850755e3248cb8f1e6a76040c3a8 Mon Sep 17 00:00:00 2001 From: yiliang114 <1204183885@qq.com> Date: Sun, 21 Jun 2026 16:19:26 +0800 Subject: [PATCH 02/10] fix(ci): address tmux triage review feedback --- .github/workflows/qwen-triage.yml | 42 ++++++++++++---------- scripts/tests/qwen-triage-workflow.test.js | 22 +++++++++--- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/.github/workflows/qwen-triage.yml b/.github/workflows/qwen-triage.yml index 65fa33fccbd..6def8fb5d40 100644 --- a/.github/workflows/qwen-triage.yml +++ b/.github/workflows/qwen-triage.yml @@ -662,7 +662,6 @@ jobs: "GH_TOKEN=" "OPENAI_API_KEY=qwen-loopback-proxy" "OPENAI_BASE_URL=$LOCAL_OPENAI_BASE_URL" - "OPENAI_MODEL=$OPENAI_MODEL" "NO_PROXY=${NO_PROXY:-}" "no_proxy=${no_proxy:-}" "QWEN_CI_HTTPS_PROXY=${QWEN_CI_HTTPS_PROXY:-}" @@ -672,6 +671,9 @@ jobs: "QWEN_CI_REAL_GH=${QWEN_CI_REAL_GH:-}" "QWEN_CI_REAL_GIT=${QWEN_CI_REAL_GIT:-}" ) + if [ -n "${OPENAI_MODEL:-}" ]; then + QWEN_ENV+=("OPENAI_MODEL=$OPENAI_MODEL") + fi set +e timeout --kill-after=10s 20m runuser -u node -- env -i "${QWEN_ENV[@]}" "${QWEN_CMD[@]}" \ @@ -781,13 +783,19 @@ jobs: # backtick fence (breakable by a long enough ``` run) cannot guarantee. # Order matters: escape & first so the < / > entities aren't re-escaped. emit_block() { - local summary="$1" file="$2" max="$3" content + local summary="$1" file="$2" max="$3" content truncated='' [ -n "$file" ] && [ -f "$file" ] || return 0 - content="$(head -c "$max" "$file")" if [ "$(wc -c < "$file")" -gt "$max" ]; then - content="${content}"$'\n\n...truncated -- full log in the run artifacts.' + truncated=$'\n\n...truncated -- full log in the run artifacts.' + fi + if ! content="$( + { + head -c "$max" "$file" + printf '%s' "$truncated" + } | tr -d '\000' | sed -e 's/&/\&/g' -e 's//\>/g' + )"; then + content='Log could not be rendered; see run artifacts.' fi - content="$(printf '%s' "$content" | sed -e 's/&/\&/g' -e 's//\>/g')" printf '
\n%s\n\n
\n' "$summary"
             printf '%s\n' "$content"
             printf '
\n\n
\n\n' @@ -823,24 +831,22 @@ jobs: emit_block 'Install/build log' "$PREPARE_LOG" 20000 printf '%s\n' '— _Qwen Code · tmux real-user testing_' } > "$BODY_FILE" - elif [ "$VERDICT" = "timeout" ] || [ "$VERDICT" = "infra-error" ]; then - REPORT="$(find tmux-results -name 'report.md' 2>/dev/null | head -1 || true)" - TRANSCRIPT="$(find tmux-results -name 'tmux-readable-full.log' 2>/dev/null | head -1 || true)" - { - printf '%s\n\n' '' - printf '**tmux real-user testing: %s** - [workflow run](%s)\n\n' "$VERDICT" "$RUN_URL" - printf 'The tmux test did not complete, so this is not a pass/fail result for the affected flow. See the workflow run and artifacts for details.\n\n' - emit_block 'E2E test report' "$REPORT" 20000 - emit_block 'Full tmux transcript' "$TRANSCRIPT" 30000 - printf '%s\n' '— _Qwen Code · tmux real-user testing_' - } > "$BODY_FILE" else REPORT="$(find tmux-results -name 'report.md' 2>/dev/null | head -1 || true)" TRANSCRIPT="$(find tmux-results -name 'tmux-readable-full.log' 2>/dev/null | head -1 || true)" + VERDICT_LABEL="${VERDICT:-unknown}" + if [ "${VERDICT:-}" = "infra-error" ]; then + VERDICT_LABEL='infra-error (crash/OOM)' + DESCRIPTION='The tmux test did not complete because the qwen process failed or was killed. This is not a pass/fail result for the affected flow; check runner resources and PR code for crashes or memory leaks.' + elif [ "${VERDICT:-}" = "timeout" ]; then + DESCRIPTION='The tmux test did not complete before the time limit. This is not a pass/fail result for the affected flow; see the workflow run and artifacts for details.' + else + DESCRIPTION='Launched the changed app in a real tmux session and exercised the affected flow.' + fi { printf '%s\n\n' '' - printf '**tmux real-user testing: %s** - [workflow run](%s)\n\n' "$VERDICT" "$RUN_URL" - printf 'Launched the changed app in a real tmux session and exercised the affected flow.\n\n' + printf '**tmux real-user testing: %s** - [workflow run](%s)\n\n' "$VERDICT_LABEL" "$RUN_URL" + printf '%s\n\n' "$DESCRIPTION" emit_block 'E2E test report' "$REPORT" 20000 emit_block 'Full tmux transcript' "$TRANSCRIPT" 30000 printf '%s\n' '— _Qwen Code · tmux real-user testing_' diff --git a/scripts/tests/qwen-triage-workflow.test.js b/scripts/tests/qwen-triage-workflow.test.js index d3541218fdc..3f8028510d4 100644 --- a/scripts/tests/qwen-triage-workflow.test.js +++ b/scripts/tests/qwen-triage-workflow.test.js @@ -9,10 +9,15 @@ import { describe, expect, it } from 'vitest'; const workflow = readFileSync('.github/workflows/qwen-triage.yml', 'utf8'); +function escapeRegExp(value) { + return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + function step(name) { + const escaped = escapeRegExp(name); const match = workflow.match( new RegExp( - `\\n\\s+- name: '${name}'[\\s\\S]*?(?=\\n\\s+- name: '|\\n\\s{2}[a-zA-Z0-9_-]+:|$)`, + `\\n\\s+- name: '${escaped}'[\\s\\S]*?(?=\\n\\s+- name: '|\\n\\s{2}[a-zA-Z0-9_-]+:|$)`, ), ); return match?.[0] ?? ''; @@ -28,26 +33,35 @@ describe('qwen-triage tmux workflow', () => { expect(postStep).toContain("sed -e 's/&/\\&/g'"); expect(postStep).toContain("-e 's//\\>/g'"); + expect(postStep).toContain("tr -d '\\000'"); + expect(postStep).toContain('Log could not be rendered'); }); it('passes the selected OpenAI model into the app under tmux test', () => { const runStep = step('Run tmux real-user testing'); + expect(runStep).toContain('if [ -n "${OPENAI_MODEL:-}" ]; then'); expect(runStep).toContain('"OPENAI_MODEL=$OPENAI_MODEL"'); }); it('reports timeout and infra-error without claiming the flow was exercised', () => { const postStep = step('Post tmux result comment'); - expect(postStep).toContain( - 'elif [ "$VERDICT" = "timeout" ] || [ "$VERDICT" = "infra-error" ]; then', - ); + expect(postStep).toContain('if [ "${VERDICT:-}" = "infra-error" ]; then'); + expect(postStep).toContain('elif [ "${VERDICT:-}" = "timeout" ]; then'); + expect(postStep).toContain('The tmux test did not complete'); + expect(postStep).toContain('not a pass/fail result'); + expect(postStep).toContain('crashes or memory leaks'); }); it('installs the heavy tmux test harness only for runnable PRs', () => { const installStep = step('Install tmux runner tools'); + const resolverStep = step('Install PR resolver tools'); expect(installStep).toContain('if: "steps.pr.outputs.decision == \'run\'"'); + expect(resolverStep).not.toContain('tmux'); + expect(resolverStep).not.toContain('npm install'); + expect(resolverStep).not.toContain('qwen --version'); expect( workflow.indexOf("- name: 'Resolve PR and check state'"), ).toBeLessThan(workflow.indexOf("- name: 'Install tmux runner tools'")); From e01a1ed0ef38f36a3dcb42d071af8247cc7680b8 Mon Sep 17 00:00:00 2001 From: yiliang114 <1204183885@qq.com> Date: Sun, 21 Jun 2026 18:15:38 +0800 Subject: [PATCH 03/10] fix(ci): tighten tmux triage review followups --- .github/workflows/qwen-triage.yml | 15 ++++++++++++--- scripts/tests/qwen-triage-workflow.test.js | 19 ++++++++++++++++++- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/.github/workflows/qwen-triage.yml b/.github/workflows/qwen-triage.yml index 6def8fb5d40..24b9b1ba445 100644 --- a/.github/workflows/qwen-triage.yml +++ b/.github/workflows/qwen-triage.yml @@ -362,11 +362,14 @@ jobs: ref: 'refs/pull/${{ steps.pr.outputs.pr_number }}/merge' fetch-depth: 1 + # Heavy tmux/qwen/util-linux setup is deferred here (gated on + # decision == 'run') so non-TUI PRs skip it. Any step added between + # 'Resolve PR and check state' and this one must not need tmux, qwen, or + # util-linux/runuser; those are installed here, not in resolver tools. - name: 'Install tmux runner tools' if: "steps.pr.outputs.decision == 'run'" run: |- set -euo pipefail - apt-get update apt-get install -y --no-install-recommends tmux util-linux npm install -g '@qwen-code/qwen-code@latest' @@ -789,11 +792,15 @@ jobs: truncated=$'\n\n...truncated -- full log in the run artifacts.' fi if ! content="$( + set -o pipefail { - head -c "$max" "$file" + head_status=0 + head -c "$max" "$file" || head_status=$? printf '%s' "$truncated" + exit "$head_status" } | tr -d '\000' | sed -e 's/&/\&/g' -e 's//\>/g' )"; then + echo "::warning::emit_block failed while rendering $summary; see run artifacts." >&2 content='Log could not be rendered; see run artifacts.' fi printf '
\n%s\n\n
\n' "$summary"
@@ -840,8 +847,10 @@ jobs:
               DESCRIPTION='The tmux test did not complete because the qwen process failed or was killed. This is not a pass/fail result for the affected flow; check runner resources and PR code for crashes or memory leaks.'
             elif [ "${VERDICT:-}" = "timeout" ]; then
               DESCRIPTION='The tmux test did not complete before the time limit. This is not a pass/fail result for the affected flow; see the workflow run and artifacts for details.'
-            else
+            elif [ "${VERDICT:-}" = "pass" ] || [ "${VERDICT:-}" = "fail" ]; then
               DESCRIPTION='Launched the changed app in a real tmux session and exercised the affected flow.'
+            else
+              DESCRIPTION='The tmux test produced an unrecognized verdict, so this is not a pass/fail result for the affected flow. See the workflow run and artifacts for details.'
             fi
             {
               printf '%s\n\n' ''
diff --git a/scripts/tests/qwen-triage-workflow.test.js b/scripts/tests/qwen-triage-workflow.test.js
index 3f8028510d4..8bda92bd463 100644
--- a/scripts/tests/qwen-triage-workflow.test.js
+++ b/scripts/tests/qwen-triage-workflow.test.js
@@ -17,7 +17,7 @@ function step(name) {
   const escaped = escapeRegExp(name);
   const match = workflow.match(
     new RegExp(
-      `\\n\\s+- name: '${escaped}'[\\s\\S]*?(?=\\n\\s+- name: '|\\n\\s{2}[a-zA-Z0-9_-]+:|$)`,
+      `\\n\\s+- name:\\s*(['"])${escaped}\\1[\\s\\S]*?(?=\\n\\s+- name:\\s*['"]|\\n\\s{2}[a-zA-Z0-9_-]+:|$)`,
     ),
   );
   return match?.[0] ?? '';
@@ -35,6 +35,10 @@ describe('qwen-triage tmux workflow', () => {
     expect(postStep).toContain("-e 's/>/\\>/g'");
     expect(postStep).toContain("tr -d '\\000'");
     expect(postStep).toContain('Log could not be rendered');
+    expect(postStep).toContain('if ! content="$(');
+    expect(postStep).toContain('set -o pipefail');
+    expect(postStep).toContain('head_status=0');
+    expect(postStep).toContain('::warning::emit_block failed');
   });
 
   it('passes the selected OpenAI model into the app under tmux test', () => {
@@ -49,15 +53,28 @@ describe('qwen-triage tmux workflow', () => {
 
     expect(postStep).toContain('if [ "${VERDICT:-}" = "infra-error" ]; then');
     expect(postStep).toContain('elif [ "${VERDICT:-}" = "timeout" ]; then');
+    expect(postStep).toContain(
+      'elif [ "${VERDICT:-}" = "pass" ] || [ "${VERDICT:-}" = "fail" ]; then',
+    );
+    expect(postStep).toContain('VERDICT_LABEL="${VERDICT:-unknown}"');
+    expect(postStep).toContain("VERDICT_LABEL='infra-error (crash/OOM)'");
     expect(postStep).toContain('The tmux test did not complete');
+    expect(postStep).toContain(
+      'The tmux test did not complete before the time limit',
+    );
     expect(postStep).toContain('not a pass/fail result');
     expect(postStep).toContain('crashes or memory leaks');
+    expect(postStep).toContain(
+      'Launched the changed app in a real tmux session and exercised the affected flow.',
+    );
+    expect(postStep).toContain('produced an unrecognized verdict');
   });
 
   it('installs the heavy tmux test harness only for runnable PRs', () => {
     const installStep = step('Install tmux runner tools');
     const resolverStep = step('Install PR resolver tools');
 
+    expect(resolverStep).toContain('apt-get install');
     expect(installStep).toContain('if: "steps.pr.outputs.decision == \'run\'"');
     expect(resolverStep).not.toContain('tmux');
     expect(resolverStep).not.toContain('npm install');

From 02938102a8c6823461115025bb3ce3ddff2a6d30 Mon Sep 17 00:00:00 2001
From: yiliang114 <1204183885@qq.com>
Date: Sun, 21 Jun 2026 19:55:27 +0800
Subject: [PATCH 04/10] fix(ci): address tmux triage review followups

---
 .github/workflows/qwen-triage.yml          |  2 +-
 scripts/tests/qwen-triage-workflow.test.js | 10 ++++++++++
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/qwen-triage.yml b/.github/workflows/qwen-triage.yml
index 24b9b1ba445..7e907b84497 100644
--- a/.github/workflows/qwen-triage.yml
+++ b/.github/workflows/qwen-triage.yml
@@ -372,7 +372,7 @@ jobs:
           set -euo pipefail
           apt-get install -y --no-install-recommends tmux util-linux
 
-          npm install -g '@qwen-code/qwen-code@latest'
+          npm install -g --registry=https://registry.npmjs.org '@qwen-code/qwen-code@latest'
           qwen --version
           tmux -V
 
diff --git a/scripts/tests/qwen-triage-workflow.test.js b/scripts/tests/qwen-triage-workflow.test.js
index 8bda92bd463..101a790ddb3 100644
--- a/scripts/tests/qwen-triage-workflow.test.js
+++ b/scripts/tests/qwen-triage-workflow.test.js
@@ -68,6 +68,8 @@ describe('qwen-triage tmux workflow', () => {
       'Launched the changed app in a real tmux session and exercised the affected flow.',
     );
     expect(postStep).toContain('produced an unrecognized verdict');
+    expect(postStep).toContain('"$VERDICT_LABEL" "$RUN_URL"');
+    expect(postStep).toContain('printf \'%s\\n\\n\' "$DESCRIPTION"');
   });
 
   it('installs the heavy tmux test harness only for runnable PRs', () => {
@@ -76,6 +78,14 @@ describe('qwen-triage tmux workflow', () => {
 
     expect(resolverStep).toContain('apt-get install');
     expect(installStep).toContain('if: "steps.pr.outputs.decision == \'run\'"');
+    expect(installStep).toContain(
+      'apt-get install -y --no-install-recommends tmux util-linux',
+    );
+    expect(installStep).toContain(
+      "npm install -g --registry=https://registry.npmjs.org '@qwen-code/qwen-code@latest'",
+    );
+    expect(installStep).toContain('qwen --version');
+    expect(installStep).toContain('tmux -V');
     expect(resolverStep).not.toContain('tmux');
     expect(resolverStep).not.toContain('npm install');
     expect(resolverStep).not.toContain('qwen --version');

From f423fb425c379d3e7af346b99f818b2345b9e043 Mon Sep 17 00:00:00 2001
From: yiliang114 <1204183885@qq.com>
Date: Sun, 21 Jun 2026 21:56:21 +0800
Subject: [PATCH 05/10] fix(ci): simplify tmux triage hardening

---
 .github/workflows/qwen-triage.yml          | 34 +++++++++-------------
 scripts/tests/qwen-triage-workflow.test.js |  4 ++-
 2 files changed, 17 insertions(+), 21 deletions(-)

diff --git a/.github/workflows/qwen-triage.yml b/.github/workflows/qwen-triage.yml
index 7e907b84497..1bf70d29243 100644
--- a/.github/workflows/qwen-triage.yml
+++ b/.github/workflows/qwen-triage.yml
@@ -345,6 +345,17 @@ jobs:
             echo "decision=na" >> "$GITHUB_OUTPUT"
           fi
 
+      # Install before checkout so PR-controlled .npmrc cannot affect npm.
+      - name: 'Install tmux runner tools'
+        if: "steps.pr.outputs.decision == 'run'"
+        run: |-
+          set -euo pipefail
+          apt-get install -y --no-install-recommends tmux util-linux
+
+          npm install -g --registry=https://registry.npmjs.org '@qwen-code/qwen-code@latest'
+          qwen --version
+          tmux -V
+
       - name: 'Clean stale review worktrees'
         if: "steps.pr.outputs.decision == 'run'"
         run: |-
@@ -362,20 +373,6 @@ jobs:
           ref: 'refs/pull/${{ steps.pr.outputs.pr_number }}/merge'
           fetch-depth: 1
 
-      # Heavy tmux/qwen/util-linux setup is deferred here (gated on
-      # decision == 'run') so non-TUI PRs skip it. Any step added between
-      # 'Resolve PR and check state' and this one must not need tmux, qwen, or
-      # util-linux/runuser; those are installed here, not in resolver tools.
-      - name: 'Install tmux runner tools'
-        if: "steps.pr.outputs.decision == 'run'"
-        run: |-
-          set -euo pipefail
-          apt-get install -y --no-install-recommends tmux util-linux
-
-          npm install -g --registry=https://registry.npmjs.org '@qwen-code/qwen-code@latest'
-          qwen --version
-          tmux -V
-
       - name: 'Install and build PR app'
         id: 'prepare'
         if: "steps.pr.outputs.decision == 'run'"
@@ -793,15 +790,12 @@ jobs:
             fi
             if ! content="$(
               set -o pipefail
-              {
-                head_status=0
-                head -c "$max" "$file" || head_status=$?
-                printf '%s' "$truncated"
-                exit "$head_status"
-              } | tr -d '\000' | sed -e 's/&/\&/g' -e 's//\>/g'
+              head -c "$max" "$file" | tr -d '\000' | sed -e 's/&/\&/g' -e 's//\>/g'
             )"; then
               echo "::warning::emit_block failed while rendering $summary; see run artifacts." >&2
               content='Log could not be rendered; see run artifacts.'
+            elif [ -n "$truncated" ]; then
+              content="${content}${truncated}"
             fi
             printf '
\n%s\n\n
\n' "$summary"
             printf '%s\n' "$content"
diff --git a/scripts/tests/qwen-triage-workflow.test.js b/scripts/tests/qwen-triage-workflow.test.js
index 101a790ddb3..6bad0403438 100644
--- a/scripts/tests/qwen-triage-workflow.test.js
+++ b/scripts/tests/qwen-triage-workflow.test.js
@@ -37,7 +37,6 @@ describe('qwen-triage tmux workflow', () => {
     expect(postStep).toContain('Log could not be rendered');
     expect(postStep).toContain('if ! content="$(');
     expect(postStep).toContain('set -o pipefail');
-    expect(postStep).toContain('head_status=0');
     expect(postStep).toContain('::warning::emit_block failed');
   });
 
@@ -92,5 +91,8 @@ describe('qwen-triage tmux workflow', () => {
     expect(
       workflow.indexOf("- name: 'Resolve PR and check state'"),
     ).toBeLessThan(workflow.indexOf("- name: 'Install tmux runner tools'"));
+    expect(
+      workflow.indexOf("- name: 'Install tmux runner tools'"),
+    ).toBeLessThan(workflow.indexOf("- name: 'Checkout PR merge ref'"));
   });
 });

From 7fb69d6b8e0bf4c1ea1fb585808d1e922d7da77b Mon Sep 17 00:00:00 2001
From: yiliang114 <1204183885@qq.com>
Date: Mon, 22 Jun 2026 00:39:49 +0800
Subject: [PATCH 06/10] fix(ci): sanitize tmux triage verdict output

---
 .github/workflows/qwen-triage.yml | 41 ++++++++++++++++++++-----------
 1 file changed, 27 insertions(+), 14 deletions(-)

diff --git a/.github/workflows/qwen-triage.yml b/.github/workflows/qwen-triage.yml
index 1bf70d29243..470fd01da76 100644
--- a/.github/workflows/qwen-triage.yml
+++ b/.github/workflows/qwen-triage.yml
@@ -389,13 +389,15 @@ jobs:
           set +e
           {
             printf '%s\n' '$ npm ci --prefer-offline --no-audit --progress=false'
-            runuser -u node -- npm ci --prefer-offline --no-audit --progress=false
+            runuser -u node -- env -u GITHUB_OUTPUT -u GITHUB_STATE -u GITHUB_ENV \
+              npm ci --prefer-offline --no-audit --progress=false
             install_status=$?
             if [ "$install_status" -ne 0 ]; then
               printf '\n%s\n' "npm ci failed with exit code ${install_status}."
             else
               printf '\n%s\n' '$ npm run build'
-              runuser -u node -- npm run build
+              runuser -u node -- env -u GITHUB_OUTPUT -u GITHUB_STATE -u GITHUB_ENV \
+                npm run build
               build_status=$?
               if [ "$build_status" -ne 0 ]; then
                 printf '\n%s\n' "npm run build failed with exit code ${build_status}."
@@ -823,7 +825,7 @@ jobs:
             case "$PREPARE_FAILURE_PHASE" in
               install) PREPARE_COMMAND='npm ci' ;;
               build) PREPARE_COMMAND='npm run build' ;;
-              *) PREPARE_COMMAND="$PREPARE_FAILURE_PHASE" ;;
+              *) PREPARE_COMMAND='install/build' ;;
             esac
             {
               printf '%s\n\n' ''
@@ -835,17 +837,28 @@ jobs:
           else
             REPORT="$(find tmux-results -name 'report.md' 2>/dev/null | head -1 || true)"
             TRANSCRIPT="$(find tmux-results -name 'tmux-readable-full.log' 2>/dev/null | head -1 || true)"
-            VERDICT_LABEL="${VERDICT:-unknown}"
-            if [ "${VERDICT:-}" = "infra-error" ]; then
-              VERDICT_LABEL='infra-error (crash/OOM)'
-              DESCRIPTION='The tmux test did not complete because the qwen process failed or was killed. This is not a pass/fail result for the affected flow; check runner resources and PR code for crashes or memory leaks.'
-            elif [ "${VERDICT:-}" = "timeout" ]; then
-              DESCRIPTION='The tmux test did not complete before the time limit. This is not a pass/fail result for the affected flow; see the workflow run and artifacts for details.'
-            elif [ "${VERDICT:-}" = "pass" ] || [ "${VERDICT:-}" = "fail" ]; then
-              DESCRIPTION='Launched the changed app in a real tmux session and exercised the affected flow.'
-            else
-              DESCRIPTION='The tmux test produced an unrecognized verdict, so this is not a pass/fail result for the affected flow. See the workflow run and artifacts for details.'
-            fi
+            case "${VERDICT:-}" in
+              infra-error)
+                VERDICT_LABEL='infra-error (crash/OOM)'
+                DESCRIPTION='The tmux test did not complete because the qwen process failed or was killed. This is not a pass/fail result for the affected flow; check runner resources and PR code for crashes or memory leaks.'
+                ;;
+              timeout)
+                VERDICT_LABEL='timeout'
+                DESCRIPTION='The tmux test did not complete before the time limit. This is not a pass/fail result for the affected flow; see the workflow run and artifacts for details.'
+                ;;
+              pass)
+                VERDICT_LABEL='pass'
+                DESCRIPTION='Launched the changed app in a real tmux session and exercised the affected flow.'
+                ;;
+              fail)
+                VERDICT_LABEL='fail'
+                DESCRIPTION='Launched the changed app in a real tmux session and exercised the affected flow.'
+                ;;
+              *)
+                VERDICT_LABEL='unknown'
+                DESCRIPTION='The tmux test produced an unrecognized verdict, so this is not a pass/fail result for the affected flow. See the workflow run and artifacts for details.'
+                ;;
+            esac
             {
               printf '%s\n\n' ''
               printf '**tmux real-user testing: %s** - [workflow run](%s)\n\n' "$VERDICT_LABEL" "$RUN_URL"

From d8f4d7c3b42962afca1da427c8fc83a0d3f66821 Mon Sep 17 00:00:00 2001
From: yiliang114 <1204183885@qq.com>
Date: Mon, 22 Jun 2026 10:10:30 +0800
Subject: [PATCH 07/10] fix(ci): harden tmux result rendering

---
 .github/workflows/qwen-triage.yml          | 20 ++++++++++-----
 scripts/tests/qwen-triage-workflow.test.js | 30 +++++++++++++++++-----
 2 files changed, 38 insertions(+), 12 deletions(-)

diff --git a/.github/workflows/qwen-triage.yml b/.github/workflows/qwen-triage.yml
index 470fd01da76..a47f23abb4d 100644
--- a/.github/workflows/qwen-triage.yml
+++ b/.github/workflows/qwen-triage.yml
@@ -389,14 +389,14 @@ jobs:
           set +e
           {
             printf '%s\n' '$ npm ci --prefer-offline --no-audit --progress=false'
-            runuser -u node -- env -u GITHUB_OUTPUT -u GITHUB_STATE -u GITHUB_ENV \
+            runuser -u node -- env -u GITHUB_OUTPUT -u GITHUB_STATE -u GITHUB_ENV -u GITHUB_PATH \
               npm ci --prefer-offline --no-audit --progress=false
             install_status=$?
             if [ "$install_status" -ne 0 ]; then
               printf '\n%s\n' "npm ci failed with exit code ${install_status}."
             else
               printf '\n%s\n' '$ npm run build'
-              runuser -u node -- env -u GITHUB_OUTPUT -u GITHUB_STATE -u GITHUB_ENV \
+              runuser -u node -- env -u GITHUB_OUTPUT -u GITHUB_STATE -u GITHUB_ENV -u GITHUB_PATH \
                 npm run build
               build_status=$?
               if [ "$build_status" -ne 0 ]; then
@@ -784,22 +784,27 @@ jobs:
           # as markdown — which a
           # backtick fence (breakable by a long enough ``` run) cannot guarantee.
           # Order matters: escape & first so the < / > entities aren't re-escaped.
+          html_escape() {
+            sed -e 's/&/\&/g' -e 's//\>/g'
+          }
+
           emit_block() {
-            local summary="$1" file="$2" max="$3" content truncated=''
+            local summary="$1" file="$2" max="$3" content truncated='' summary_html
             [ -n "$file" ] && [ -f "$file" ] || return 0
+            summary_html="$(printf '%s' "$summary" | html_escape)"
             if [ "$(wc -c < "$file")" -gt "$max" ]; then
               truncated=$'\n\n...truncated -- full log in the run artifacts.'
             fi
             if ! content="$(
               set -o pipefail
-              head -c "$max" "$file" | tr -d '\000' | sed -e 's/&/\&/g' -e 's//\>/g'
+              head -c "$max" "$file" | tr -d '\000' | html_escape
             )"; then
               echo "::warning::emit_block failed while rendering $summary; see run artifacts." >&2
               content='Log could not be rendered; see run artifacts.'
             elif [ -n "$truncated" ]; then
               content="${content}${truncated}"
             fi
-            printf '
\n%s\n\n
\n' "$summary"
+            printf '
\n%s\n\n
\n' "$summary_html"
             printf '%s\n' "$content"
             printf '
\n\n
\n\n' } @@ -856,7 +861,10 @@ jobs: ;; *) VERDICT_LABEL='unknown' - DESCRIPTION='The tmux test produced an unrecognized verdict, so this is not a pass/fail result for the affected flow. See the workflow run and artifacts for details.' + UNKNOWN_VERDICT="$( + printf '%s' "${VERDICT:-}" | tr -d '\000' | tr '\r\n' ' ' | head -c 200 | html_escape + )" + DESCRIPTION="The tmux test produced an unrecognized verdict (${UNKNOWN_VERDICT}), so this is not a pass/fail result for the affected flow. See the workflow run and artifacts for details." ;; esac { diff --git a/scripts/tests/qwen-triage-workflow.test.js b/scripts/tests/qwen-triage-workflow.test.js index 6bad0403438..7438fb7871a 100644 --- a/scripts/tests/qwen-triage-workflow.test.js +++ b/scripts/tests/qwen-triage-workflow.test.js @@ -33,11 +33,18 @@ describe('qwen-triage tmux workflow', () => { expect(postStep).toContain("sed -e 's/&/\\&/g'"); expect(postStep).toContain("-e 's//\\>/g'"); + expect(postStep).toContain('html_escape()'); expect(postStep).toContain("tr -d '\\000'"); expect(postStep).toContain('Log could not be rendered'); expect(postStep).toContain('if ! content="$('); expect(postStep).toContain('set -o pipefail'); expect(postStep).toContain('::warning::emit_block failed'); + expect(postStep).toContain( + "summary_html=\"$(printf '%s' \"$summary\" | html_escape)\"", + ); + expect(postStep).toContain( + "'
\\n%s\\n\\n
\\n' \"$summary_html\"",
+    );
   });
 
   it('passes the selected OpenAI model into the app under tmux test', () => {
@@ -50,13 +57,12 @@ describe('qwen-triage tmux workflow', () => {
   it('reports timeout and infra-error without claiming the flow was exercised', () => {
     const postStep = step('Post tmux result comment');
 
-    expect(postStep).toContain('if [ "${VERDICT:-}" = "infra-error" ]; then');
-    expect(postStep).toContain('elif [ "${VERDICT:-}" = "timeout" ]; then');
-    expect(postStep).toContain(
-      'elif [ "${VERDICT:-}" = "pass" ] || [ "${VERDICT:-}" = "fail" ]; then',
-    );
-    expect(postStep).toContain('VERDICT_LABEL="${VERDICT:-unknown}"');
+    expect(postStep).toContain('case "${VERDICT:-}" in');
     expect(postStep).toContain("VERDICT_LABEL='infra-error (crash/OOM)'");
+    expect(postStep).toContain("VERDICT_LABEL='timeout'");
+    expect(postStep).toContain("VERDICT_LABEL='pass'");
+    expect(postStep).toContain("VERDICT_LABEL='fail'");
+    expect(postStep).toContain("VERDICT_LABEL='unknown'");
     expect(postStep).toContain('The tmux test did not complete');
     expect(postStep).toContain(
       'The tmux test did not complete before the time limit',
@@ -67,10 +73,22 @@ describe('qwen-triage tmux workflow', () => {
       'Launched the changed app in a real tmux session and exercised the affected flow.',
     );
     expect(postStep).toContain('produced an unrecognized verdict');
+    expect(postStep).toContain('UNKNOWN_VERDICT="$(');
+    expect(postStep).toContain('tr \'\\r\\n\' \'  \'');
+    expect(postStep).toContain('${UNKNOWN_VERDICT}');
     expect(postStep).toContain('"$VERDICT_LABEL" "$RUN_URL"');
     expect(postStep).toContain('printf \'%s\\n\\n\' "$DESCRIPTION"');
   });
 
+  it('removes GitHub command files from PR-controlled lifecycle scripts', () => {
+    const prepareStep = step('Install and build PR app');
+
+    expect(prepareStep).toContain('-u GITHUB_OUTPUT');
+    expect(prepareStep).toContain('-u GITHUB_STATE');
+    expect(prepareStep).toContain('-u GITHUB_ENV');
+    expect(prepareStep).toContain('-u GITHUB_PATH');
+  });
+
   it('installs the heavy tmux test harness only for runnable PRs', () => {
     const installStep = step('Install tmux runner tools');
     const resolverStep = step('Install PR resolver tools');

From b7fa62296d4ec204154c046df719b6acafd736a6 Mon Sep 17 00:00:00 2001
From: yiliang114 <1204183885@qq.com>
Date: Mon, 22 Jun 2026 10:59:23 +0800
Subject: [PATCH 08/10] ci: strip step summary from triage build

---
 .github/workflows/qwen-triage.yml          | 4 ++--
 scripts/tests/qwen-triage-workflow.test.js | 1 +
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/qwen-triage.yml b/.github/workflows/qwen-triage.yml
index a47f23abb4d..5039adeac57 100644
--- a/.github/workflows/qwen-triage.yml
+++ b/.github/workflows/qwen-triage.yml
@@ -389,14 +389,14 @@ jobs:
           set +e
           {
             printf '%s\n' '$ npm ci --prefer-offline --no-audit --progress=false'
-            runuser -u node -- env -u GITHUB_OUTPUT -u GITHUB_STATE -u GITHUB_ENV -u GITHUB_PATH \
+            runuser -u node -- env -u GITHUB_OUTPUT -u GITHUB_STATE -u GITHUB_ENV -u GITHUB_PATH -u GITHUB_STEP_SUMMARY \
               npm ci --prefer-offline --no-audit --progress=false
             install_status=$?
             if [ "$install_status" -ne 0 ]; then
               printf '\n%s\n' "npm ci failed with exit code ${install_status}."
             else
               printf '\n%s\n' '$ npm run build'
-              runuser -u node -- env -u GITHUB_OUTPUT -u GITHUB_STATE -u GITHUB_ENV -u GITHUB_PATH \
+              runuser -u node -- env -u GITHUB_OUTPUT -u GITHUB_STATE -u GITHUB_ENV -u GITHUB_PATH -u GITHUB_STEP_SUMMARY \
                 npm run build
               build_status=$?
               if [ "$build_status" -ne 0 ]; then
diff --git a/scripts/tests/qwen-triage-workflow.test.js b/scripts/tests/qwen-triage-workflow.test.js
index 7438fb7871a..18f6e6655f6 100644
--- a/scripts/tests/qwen-triage-workflow.test.js
+++ b/scripts/tests/qwen-triage-workflow.test.js
@@ -87,6 +87,7 @@ describe('qwen-triage tmux workflow', () => {
     expect(prepareStep).toContain('-u GITHUB_STATE');
     expect(prepareStep).toContain('-u GITHUB_ENV');
     expect(prepareStep).toContain('-u GITHUB_PATH');
+    expect(prepareStep).toContain('-u GITHUB_STEP_SUMMARY');
   });
 
   it('installs the heavy tmux test harness only for runnable PRs', () => {

From a67aea413530d6dcadbac295bf0c971073b4a2de Mon Sep 17 00:00:00 2001
From: yiliang114 <1204183885@qq.com>
Date: Mon, 22 Jun 2026 12:44:26 +0800
Subject: [PATCH 09/10] fix(ci): report missing tmux artifacts

---
 .github/workflows/qwen-triage.yml          |  8 ++++++++
 scripts/tests/qwen-triage-workflow.test.js | 11 ++++++++---
 2 files changed, 16 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/qwen-triage.yml b/.github/workflows/qwen-triage.yml
index 5039adeac57..7a295e50a71 100644
--- a/.github/workflows/qwen-triage.yml
+++ b/.github/workflows/qwen-triage.yml
@@ -842,6 +842,10 @@ jobs:
           else
             REPORT="$(find tmux-results -name 'report.md' 2>/dev/null | head -1 || true)"
             TRANSCRIPT="$(find tmux-results -name 'tmux-readable-full.log' 2>/dev/null | head -1 || true)"
+            if [ -z "$REPORT" ] && [ -z "$TRANSCRIPT" ]; then
+              MISSING_ARTIFACTS_NOTE='No report.md or tmux-readable-full.log was found in tmux-results, so detailed report sections are omitted.'
+              echo "::warning::${MISSING_ARTIFACTS_NOTE}"
+            fi
             case "${VERDICT:-}" in
               infra-error)
                 VERDICT_LABEL='infra-error (crash/OOM)'
@@ -864,6 +868,7 @@ jobs:
                 UNKNOWN_VERDICT="$(
                   printf '%s' "${VERDICT:-}" | tr -d '\000' | tr '\r\n' '  ' | head -c 200 | html_escape
                 )"
+                echo "::warning::Unrecognized tmux verdict: ${UNKNOWN_VERDICT}"
                 DESCRIPTION="The tmux test produced an unrecognized verdict (${UNKNOWN_VERDICT}), so this is not a pass/fail result for the affected flow. See the workflow run and artifacts for details."
                 ;;
             esac
@@ -871,6 +876,9 @@ jobs:
               printf '%s\n\n' ''
               printf '**tmux real-user testing: %s** - [workflow run](%s)\n\n' "$VERDICT_LABEL" "$RUN_URL"
               printf '%s\n\n' "$DESCRIPTION"
+              if [ -n "${MISSING_ARTIFACTS_NOTE:-}" ]; then
+                printf '%s\n\n' "$MISSING_ARTIFACTS_NOTE"
+              fi
               emit_block 'E2E test report' "$REPORT" 20000
               emit_block 'Full tmux transcript' "$TRANSCRIPT" 30000
               printf '%s\n' '— _Qwen Code · tmux real-user testing_'
diff --git a/scripts/tests/qwen-triage-workflow.test.js b/scripts/tests/qwen-triage-workflow.test.js
index 18f6e6655f6..508fa381c05 100644
--- a/scripts/tests/qwen-triage-workflow.test.js
+++ b/scripts/tests/qwen-triage-workflow.test.js
@@ -40,10 +40,10 @@ describe('qwen-triage tmux workflow', () => {
     expect(postStep).toContain('set -o pipefail');
     expect(postStep).toContain('::warning::emit_block failed');
     expect(postStep).toContain(
-      "summary_html=\"$(printf '%s' \"$summary\" | html_escape)\"",
+      'summary_html="$(printf \'%s\' "$summary" | html_escape)"',
     );
     expect(postStep).toContain(
-      "'
\\n%s\\n\\n
\\n' \"$summary_html\"",
+      '\'
\\n%s\\n\\n
\\n\' "$summary_html"',
     );
   });
 
@@ -74,10 +74,15 @@ describe('qwen-triage tmux workflow', () => {
     );
     expect(postStep).toContain('produced an unrecognized verdict');
     expect(postStep).toContain('UNKNOWN_VERDICT="$(');
-    expect(postStep).toContain('tr \'\\r\\n\' \'  \'');
+    expect(postStep).toContain('::warning::Unrecognized tmux verdict');
+    expect(postStep).toContain("tr '\\r\\n' '  '");
     expect(postStep).toContain('${UNKNOWN_VERDICT}');
     expect(postStep).toContain('"$VERDICT_LABEL" "$RUN_URL"');
     expect(postStep).toContain('printf \'%s\\n\\n\' "$DESCRIPTION"');
+    expect(postStep).toContain('MISSING_ARTIFACTS_NOTE=');
+    expect(postStep).toContain(
+      'No report.md or tmux-readable-full.log was found in tmux-results',
+    );
   });
 
   it('removes GitHub command files from PR-controlled lifecycle scripts', () => {

From 7190ad6336430ef54fecf072695cfd958f942ad4 Mon Sep 17 00:00:00 2001
From: yiliang114 <1204183885@qq.com>
Date: Mon, 22 Jun 2026 14:01:49 +0800
Subject: [PATCH 10/10] fix(ci): clarify tmux prepare diagnostics

---
 .github/workflows/qwen-triage.yml          | 15 ++++++++++++++-
 scripts/tests/qwen-triage-workflow.test.js | 20 ++++++++++++++++++++
 2 files changed, 34 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/qwen-triage.yml b/.github/workflows/qwen-triage.yml
index 7a295e50a71..a3989cf64a0 100644
--- a/.github/workflows/qwen-triage.yml
+++ b/.github/workflows/qwen-triage.yml
@@ -830,12 +830,25 @@ jobs:
             case "$PREPARE_FAILURE_PHASE" in
               install) PREPARE_COMMAND='npm ci' ;;
               build) PREPARE_COMMAND='npm run build' ;;
-              *) PREPARE_COMMAND='install/build' ;;
+              *)
+                PREPARE_COMMAND='install/build'
+                UNKNOWN_PREPARE_PHASE="$(
+                  printf '%s' "$PREPARE_FAILURE_PHASE" | tr -d '\000' | tr '\r\n' '  ' | head -c 200 | html_escape
+                )"
+                echo "::warning::Unrecognized prepare failure phase: ${UNKNOWN_PREPARE_PHASE}"
+                ;;
             esac
+            if [ -z "$PREPARE_LOG" ]; then
+              PREPARE_LOG_NOTE='No prepare.log was found in tmux-results, so the install/build log section is omitted.'
+              echo "::warning::${PREPARE_LOG_NOTE}"
+            fi
             {
               printf '%s\n\n' ''
               printf '**tmux real-user testing: fail** - [workflow run](%s)\n\n' "$RUN_URL"
               printf 'The PR app could not be launched because `%s` failed before the tmux session started. This is treated as a PR failure verdict rather than an infrastructure failure.\n\n' "$PREPARE_COMMAND"
+              if [ -n "${PREPARE_LOG_NOTE:-}" ]; then
+                printf '%s\n\n' "$PREPARE_LOG_NOTE"
+              fi
               emit_block 'Install/build log' "$PREPARE_LOG" 20000
               printf '%s\n' '— _Qwen Code · tmux real-user testing_'
             } > "$BODY_FILE"
diff --git a/scripts/tests/qwen-triage-workflow.test.js b/scripts/tests/qwen-triage-workflow.test.js
index 508fa381c05..e1cbcae7dd6 100644
--- a/scripts/tests/qwen-triage-workflow.test.js
+++ b/scripts/tests/qwen-triage-workflow.test.js
@@ -87,12 +87,32 @@ describe('qwen-triage tmux workflow', () => {
 
   it('removes GitHub command files from PR-controlled lifecycle scripts', () => {
     const prepareStep = step('Install and build PR app');
+    const strippedEnv =
+      'env -u GITHUB_OUTPUT -u GITHUB_STATE -u GITHUB_ENV -u GITHUB_PATH -u GITHUB_STEP_SUMMARY';
 
     expect(prepareStep).toContain('-u GITHUB_OUTPUT');
     expect(prepareStep).toContain('-u GITHUB_STATE');
     expect(prepareStep).toContain('-u GITHUB_ENV');
     expect(prepareStep).toContain('-u GITHUB_PATH');
     expect(prepareStep).toContain('-u GITHUB_STEP_SUMMARY');
+    expect(prepareStep).toMatch(
+      new RegExp(`${escapeRegExp(strippedEnv)} \\\\\\s+npm ci`),
+    );
+    expect(prepareStep).toMatch(
+      new RegExp(`${escapeRegExp(strippedEnv)} \\\\\\s+npm run build`),
+    );
+  });
+
+  it('does not echo unrecognized prepare failure phases into comments', () => {
+    const postStep = step('Post tmux result comment');
+
+    expect(postStep).toContain("PREPARE_COMMAND='install/build'");
+    expect(postStep).toContain('::warning::Unrecognized prepare failure phase');
+    expect(postStep).toContain('PREPARE_LOG_NOTE=');
+    expect(postStep).toContain(
+      'No prepare.log was found in tmux-results, so the install/build log section is omitted.',
+    );
+    expect(postStep).not.toContain('PREPARE_COMMAND="$PREPARE_FAILURE_PHASE"');
   });
 
   it('installs the heavy tmux test harness only for runnable PRs', () => {