Files
qwen-code/.github/workflows/sdk-java.yml
T
Shaojin Wen ba8615f4c4 ci(sdk-java): stop the API contract version from regressing or stalling (#13804) (#13808)
The public Managed Agent contract's info.version is allocated by hand on
every contract-changing pull request, and nothing checked it: on
2026-10-09 four such pull requests were in flight with 1.34.0 claimed
twice while main was already at 1.35.0, and the stall had reached main
once before — #13163 changed the document with no version movement. A
duplicate Flyway version refuses to boot within minutes, but a regressed
or stalled contract version breaks nothing at runtime; it silently
misinforms every consumer that keys capability detection on it.

On every pull request the Flyway lane now also compares the merge
result's document with the base branch's head copy — fetched at depth
one, since the default checkout is one commit deep — and fails when a
changed document declares an equal or lower version, compared numerically
per segment so 1.9.0 ranks below 1.37.0, naming whether the number
stalled or regressed; a pull request arriving at the base ref's own
bytes owes no bump. Each refusal arm has a test that fails when its
guard is removed, and any path echoed into an ::error:: line is bound to
the same byte alphabet check-flyway-migrations.js applies, so a hostile
name cannot forge a second error line into a decoded log.

After every push to main that changes the document, and every 30
minutes, a new workflow re-checks each open pull request's changed paths
and document through the API — no pull request code is fetched, checked
out or run — and sets the 'API contract version uniqueness (latest
main)' status, the #13770 shape for the version number instead of the
migrations: failure when the declared version no longer advances past
main's, success otherwise, naming the other open pull requests claiming
the same version, and error when a file list or head document could not
be read in full. A head that already carries the status is re-evaluated
even after it stops changing the document, so a stale failure clears.

Both scripts ran live before this commit: the pull-request check over
four merge shapes (stall, regression, advance, untouched) against a
depth-one clone and filtered base fetch, and the re-check as a dry run
against the open queue, whose six contract-changing pull requests it
correctly read as stalled or regressed against main at 1.38.0.

Fixes #13804
2026-10-10 05:43:18 +00:00

888 lines
48 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: 'SDK Java'
on:
workflow_dispatch:
pull_request:
branches:
- 'main'
- 'release/**'
paths:
- 'packages/sdk-java/**'
- '**/.mvn/**'
- 'packages/core/src/config/approval-modes.json'
- 'packages/core/src/managed-runtime/**'
- 'packages/cli/src/config/**'
- 'packages/core/src/config/**'
- 'packages/core/src/core/**'
- 'packages/cli/src/commands/serve.ts'
- 'packages/cli/src/serve/**'
- 'packages/cli/src/acp-integration/**'
- 'packages/acp-bridge/**'
- 'integration-tests/fake-openai-server.ts'
- 'integration-tests/helpers/hosted-*'
- 'integration-tests/cli/hosted-latency-baseline.test.ts'
- 'integration-tests/baselines/hosted-latency.json'
- 'package.json'
- 'pnpm-lock.yaml'
- 'docs/developers/sdk-java.md'
- 'docs/design/java-daemon-sdk-alpha.md'
- 'scripts/run-java-daemon-sdk-e2e.ts'
- 'scripts/run-managed-agent-server-e2e.ts'
- 'scripts/check-failsafe-reports.js'
- 'scripts/check-flyway-migrations.js'
- 'scripts/flyway-migration-utils.js'
- 'scripts/check-contract-version.js'
- 'scripts/contract-version-utils.js'
- '.github/workflows/sdk-java.yml'
- '.github/workflows/release-sdk-java.yml'
push:
branches:
- 'main'
- 'release/**'
paths:
- 'packages/sdk-java/**'
- '**/.mvn/**'
- 'packages/core/src/config/approval-modes.json'
- 'packages/core/src/managed-runtime/**'
- 'packages/cli/src/config/**'
- 'packages/core/src/config/**'
- 'packages/core/src/core/**'
- 'packages/cli/src/commands/serve.ts'
- 'packages/cli/src/serve/**'
- 'packages/cli/src/acp-integration/**'
- 'packages/acp-bridge/**'
- 'integration-tests/fake-openai-server.ts'
- 'integration-tests/helpers/hosted-*'
- 'integration-tests/cli/hosted-latency-baseline.test.ts'
- 'integration-tests/baselines/hosted-latency.json'
- 'package.json'
- 'pnpm-lock.yaml'
- 'docs/developers/sdk-java.md'
- 'docs/design/java-daemon-sdk-alpha.md'
- 'scripts/run-java-daemon-sdk-e2e.ts'
- 'scripts/run-managed-agent-server-e2e.ts'
- 'scripts/check-failsafe-reports.js'
- 'scripts/check-flyway-migrations.js'
- 'scripts/flyway-migration-utils.js'
- 'scripts/check-contract-version.js'
- 'scripts/contract-version-utils.js'
- '.github/workflows/sdk-java.yml'
- '.github/workflows/release-sdk-java.yml'
permissions:
contents: 'read'
env:
MAVEN_VERSION: '3.9.11'
MAVEN_SHA512: 'bcfe4fe305c962ace56ac7b5fc7a08b87d5abd8b7e89027ab251069faebee516b0ded8961445d6d91ec1985dfe30f8153268843c89aa392733d1a3ec956c9978'
concurrency:
group: 'sdk-java-${{ github.event.pull_request.number || github.run_id }}'
cancel-in-progress: true
jobs:
test:
name: '${{ matrix.os }} / Java ${{ matrix.java }}'
runs-on: '${{ (matrix.os == ''ubuntu-latest'' && github.repository == ''QwenLM/qwen-code'' && vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'' && (github.event_name == ''push'' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON(''["OWNER","MEMBER","COLLABORATOR"]''), github.event.pull_request.author_association))) && fromJSON(''["self-hosted", "linux", "x64", "ecs-qwen"]'') || matrix.os }}'
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- os: 'ubuntu-latest'
java: '11'
- os: 'ubuntu-latest'
java: '17'
- os: 'ubuntu-latest'
java: '21'
- os: 'macos-latest'
java: '21'
- os: 'windows-latest'
java: '21'
steps:
- name: 'Restore workspace ownership'
if: "${{ runner.environment == 'self-hosted' }}"
run: |-
set -uo pipefail
RUNNER_UID="$(id -u)"
RUNNER_GID="$(id -g)"
if [ "$RUNNER_UID" != "0" ]; then
chown -R "$RUNNER_UID:$RUNNER_GID" "$GITHUB_WORKSPACE" 2>/dev/null || sudo -n chown -R "$RUNNER_UID:$RUNNER_GID" "$GITHUB_WORKSPACE" || echo "::warning::could not restore workspace ownership; checkout may fail on leftover root-owned files"
fi
chmod -R u+rwX "$GITHUB_WORKSPACE" 2>/dev/null || sudo -n chmod -R u+rwX "$GITHUB_WORKSPACE" || echo "::warning::could not restore workspace write permissions; checkout may fail on leftover read-only files"
# A leftover workspace owned by a different uid than the step user
# (e.g. a runner-user checkout under root-run steps) makes every git
# call fail with "detected dubious ownership". The pinned
# actions/checkout adds safe.directory only to its own temporary
# global config, so later steps are uncovered; trust the workspace
# here instead. The --get-all guard keeps a persistent runner's
# gitconfig from accumulating a duplicate entry per job.
git config --global --get-all safe.directory 2>/dev/null | grep -qxF "$GITHUB_WORKSPACE" || git config --global --add safe.directory "$GITHUB_WORKSPACE" || echo "::warning::could not mark the workspace as a git safe.directory; git steps may fail on a mismatched-ownership checkout"
# ci.yml's pre-checkout sweep, byte-identical to the ci.yml copies
# (pinned in scripts/tests/sdk-java-workflow.test.js): residue left by
# review/autofix jobs on the shared ECS pool fails actions/checkout's
# clean — the #13506 failure. Hosted legs skip it on fresh VMs.
- name: 'Clean stale .qwen before checkout'
if: "${{ runner.environment == 'self-hosted' }}"
run: |-
set -uo pipefail
for stale_qwen in "$GITHUB_WORKSPACE/.qwen" "$GITHUB_WORKSPACE/.qwen.root-orig" "$GITHUB_WORKSPACE/trusted-ci-classifier"; do
if [ ! -e "$stale_qwen" ] && [ ! -L "$stale_qwen" ]; then
continue
fi
if [ -d "$stale_qwen" ] && [ ! -L "$stale_qwen" ]; then
chmod -R u+w "$stale_qwen" 2>/dev/null || true
fi
# A foreign-owned directory cannot always be renamed to a
# different parent: updating its .. entry can fail even when the
# workspace parent is writable. If that individual move fails,
# quarantine the runner-owned workspace itself, then recreate the
# empty checkout root. Warm contents are lost only on this
# otherwise unrecoverable path.
rm -rf -- "$stale_qwen" 2>/dev/null ||
sudo -n rm -rf -- "$stale_qwen" 2>/dev/null ||
{
quarantine="$(dirname -- "$GITHUB_WORKSPACE")/_qwen-quarantine"
mkdir -p "$quarantine" 2>/dev/null || true
stale_name="$(basename -- "$stale_qwen")"
if mv -- "$stale_qwen" "$quarantine/${stale_name#\.}-$(date -u +%Y%m%dT%H%M%SZ)-$$" 2>/dev/null; then
echo "::warning::could not delete leaked $stale_name; moved it to $quarantine so this checkout can proceed — that directory needs manual cleanup"
else
workspace_quarantine="$quarantine/workspace-$(date -u +%Y%m%dT%H%M%SZ)-$$"
if mv -- "$GITHUB_WORKSPACE" "$workspace_quarantine" 2>/dev/null &&
mkdir -p "$GITHUB_WORKSPACE" 2>/dev/null &&
cd "$GITHUB_WORKSPACE"; then
echo "::warning::could not delete leaked $stale_name; moved the whole workspace to $workspace_quarantine so this checkout can proceed — that directory needs manual cleanup"
break
else
echo "::warning::leaked $stale_name survived every recovery; runner needs manual cleanup"
fi
fi
}
done
# Interrupted reviews leave worktree registrations under .qwen/tmp/
# and qwen-review/* branches behind. prune drops registrations whose
# directories the rm above removed; worktree remove --force then
# clears any still-registered leftover directory (--force tolerates
# dirty contents), since a branch checked out in a live worktree
# cannot be deleted. If removal still fails, the registration
# survives and the branch delete below warns. The sweep deletes all
# review artifacts, not just the current PR's: safe because a runner
# executes one job at a time. Kept inline rather than a shared
# script: this runs pre-checkout on shared runners, where leftover
# workspace files are untrusted.
if [ -e "$GITHUB_WORKSPACE/.git" ]; then
GIT_SAFE=(git -c core.hooksPath=/dev/null -c core.fsmonitor= -C "$GITHUB_WORKSPACE")
"${GIT_SAFE[@]}" worktree prune -v || true
"${GIT_SAFE[@]}" worktree list --porcelain \
| awk '$1 == "worktree" && index($0, "/.qwen/tmp/review-pr-") > 0 { sub(/^worktree /, ""); print }' \
| while read -r worktree; do
[ -n "$worktree" ] || continue
# Registered paths come from leftover git metadata and are
# untrusted: the awk filter above matched by substring, so reject
# `..` traversal and re-anchor to the review prefix before the
# destructive remove.
case "$worktree" in
*/../*|../*|*/..)
echo "::warning::skipping suspicious review worktree path: $worktree"
continue
;;
"$GITHUB_WORKSPACE/.qwen/tmp/review-pr-"*) : ;;
*)
echo "::warning::skipping unexpected review worktree path: $worktree"
continue
;;
esac
"${GIT_SAFE[@]}" worktree remove --force "$worktree" ||
echo "::warning::could not remove review worktree: $worktree"
done || true
"${GIT_SAFE[@]}" worktree prune -v || true
"${GIT_SAFE[@]}" for-each-ref --format='%(refname:short)' 'refs/heads/qwen-review/*' \
| while read -r stale_ref; do
if [ -n "$stale_ref" ]; then
"${GIT_SAFE[@]}" branch -D "$stale_ref" ||
echo "::warning::could not remove review branch: $stale_ref"
fi
done || true
fi
- name: 'Checkout'
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
with:
ref: "${{ (runner.environment == 'self-hosted' && github.event_name == 'pull_request' && format('refs/pull/{0}/head', github.event.pull_request.number)) || github.ref }}"
- name: 'Verify checkout includes expected head commit'
if: "${{ runner.environment == 'self-hosted' && github.event_name == 'pull_request' }}"
env:
EXPECTED_SHA: '${{ github.event.pull_request.head.sha }}'
run: |-
if ! git merge-base --is-ancestor "${EXPECTED_SHA}" HEAD; then
echo "::error::Checked out ref does not contain expected head ${EXPECTED_SHA}."
git log --oneline --decorate -5
exit 1
fi
# setup-java updates Maven files non-atomically. Keep them job-local
# because runner instances on one ECS host share HOME.
- name: 'Set up Java'
uses: 'actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654' # v5.2.0
with:
distribution: 'temurin'
java-version: '${{ matrix.java }}'
cache: 'maven'
cache-dependency-path: |-
packages/sdk-java/qwencode/pom.xml
packages/sdk-java/runtime-broker/pom.xml
settings-path: '${{ runner.temp }}/setup-java-m2'
# The runtime-broker process tests spawn the Node fixture worker.
# Self-hosted keeps relying on the runner's own Node, matching the
# daemon-e2e job's stance.
- name: 'Set up Node.js 22 (hosted)'
if: "${{ runner.environment == 'github-hosted' && matrix.java == '21' }}"
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
with:
node-version: '22'
package-manager-cache: false
- name: 'Set up Maven (self-hosted)'
if: "${{ runner.environment == 'self-hosted' }}"
run: |-
archive="${RUNNER_TEMP}/apache-maven-${MAVEN_VERSION}-bin.tar.gz"
curl -fsSL "https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/${MAVEN_VERSION}/apache-maven-${MAVEN_VERSION}-bin.tar.gz" -o "${archive}"
echo "${MAVEN_SHA512} ${archive}" | sha512sum --check --status
tar -xzf "${archive}" -C "${RUNNER_TEMP}"
echo "${RUNNER_TEMP}/apache-maven-${MAVEN_VERSION}/bin" >> "${GITHUB_PATH}"
- name: 'Run Java SDK tests (self-hosted)'
if: "${{ runner.environment == 'self-hosted' }}"
working-directory: 'packages/sdk-java/qwencode'
env:
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
MATRIX_JAVA: '${{ matrix.java }}'
run: |-
mkdir -p "${HOME}/.cache/qwen-code-ci"
exec 9>"${HOME}/.cache/qwen-code-ci/sdk-java-tests.lock"
if ! flock --wait 1200 9; then
echo "::error::sdk-java host lock not acquired within 20 minutes"
exit 1
fi
mvn --batch-mode --no-transfer-progress clean test
if [ "${MATRIX_JAVA}" = "21" ]; then
cd ../runtime-broker
mvn --batch-mode --no-transfer-progress clean test
fi
- name: 'Run Java SDK tests (hosted)'
if: "${{ runner.environment == 'github-hosted' }}"
working-directory: 'packages/sdk-java/qwencode'
env:
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
run: 'mvn --batch-mode --no-transfer-progress clean test'
- name: 'Run Runtime Broker state tests'
if: "${{ runner.environment == 'github-hosted' && matrix.java == '21' }}"
working-directory: 'packages/sdk-java/runtime-broker'
env:
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
run: 'mvn --batch-mode --no-transfer-progress clean test'
- name: 'Run Java SDK Checkstyle'
if: "${{ matrix.os == 'ubuntu-latest' && matrix.java == '11' }}"
working-directory: 'packages/sdk-java/qwencode'
env:
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
run: 'mvn --batch-mode --no-transfer-progress checkstyle:check'
- name: 'Run Runtime Broker state Checkstyle'
if: "${{ matrix.os == 'ubuntu-latest' && matrix.java == '21' }}"
working-directory: 'packages/sdk-java/runtime-broker'
env:
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
run: 'mvn --batch-mode --no-transfer-progress checkstyle:check'
- name: 'Build release artifacts'
if: "${{ matrix.os == 'ubuntu-latest' && matrix.java == '11' }}"
working-directory: 'packages/sdk-java/qwencode'
env:
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
run: 'mvn --batch-mode --no-transfer-progress -DskipTests -Dgpg.skip=true package'
# The Flyway duplicate-version guard of #12940. managed-agent-server
# allocates migration versions by hand across two locations that share one
# sequence, so two green PRs can collide once both merge; the test that
# catches it boots a database and runs only in the slow lanes below. This
# job is the fast lane: a filename scan, seconds in, on every pull request —
# whose checkout is the prospective merge result — and on every push.
# runtime-broker and qwencode carry no migrations today but share the
# namespace: managed-agent-server depends on both at compile scope, so
# Flyway resolves classpath:db/migration across the jars and a version
# claimed in any of them collides with managed-agent-server's exactly like
# an in-module duplicate.
#
# Trusted triggers run on the ECS pool (#13245): a five-second scan sat 26
# minutes in the hosted queue. Untrusted fork PRs stay hosted, and the
# default merge-ref checkout is kept on both — the merge result is the point.
flyway-migrations:
name: 'Flyway migration version uniqueness'
runs-on: '${{ (github.repository == ''QwenLM/qwen-code'' && vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'' && (github.event_name != ''pull_request'' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON(''["OWNER","MEMBER","COLLABORATOR"]''), github.event.pull_request.author_association))) && fromJSON(''["self-hosted", "linux", "x64", "ecs-qwen"]'') || fromJSON(''["ubuntu-latest"]'') }}'
timeout-minutes: 5
steps:
- name: 'Restore workspace ownership'
if: "${{ runner.environment == 'self-hosted' }}"
run: |-
set -uo pipefail
RUNNER_UID="$(id -u)"
RUNNER_GID="$(id -g)"
if [ "$RUNNER_UID" != "0" ]; then
chown -R "$RUNNER_UID:$RUNNER_GID" "$GITHUB_WORKSPACE" 2>/dev/null || sudo -n chown -R "$RUNNER_UID:$RUNNER_GID" "$GITHUB_WORKSPACE" || echo "::warning::could not restore workspace ownership; checkout may fail on leftover root-owned files"
fi
chmod -R u+rwX "$GITHUB_WORKSPACE" 2>/dev/null || sudo -n chmod -R u+rwX "$GITHUB_WORKSPACE" || echo "::warning::could not restore workspace write permissions; checkout may fail on leftover read-only files"
# A leftover workspace owned by a different uid than the step user
# (e.g. a runner-user checkout under root-run steps) makes every git
# call fail with "detected dubious ownership". The pinned
# actions/checkout adds safe.directory only to its own temporary
# global config, so later steps are uncovered; trust the workspace
# here instead. The --get-all guard keeps a persistent runner's
# gitconfig from accumulating a duplicate entry per job.
git config --global --get-all safe.directory 2>/dev/null | grep -qxF "$GITHUB_WORKSPACE" || git config --global --add safe.directory "$GITHUB_WORKSPACE" || echo "::warning::could not mark the workspace as a git safe.directory; git steps may fail on a mismatched-ownership checkout"
# ci.yml's pre-checkout sweep, byte-identical to the ci.yml copies
# (pinned in scripts/tests/sdk-java-workflow.test.js): residue left by
# review/autofix jobs on the shared ECS pool fails actions/checkout's
# clean — the #13506 failure. Hosted legs skip it on fresh VMs.
- name: 'Clean stale .qwen before checkout'
if: "${{ runner.environment == 'self-hosted' }}"
run: |-
set -uo pipefail
for stale_qwen in "$GITHUB_WORKSPACE/.qwen" "$GITHUB_WORKSPACE/.qwen.root-orig" "$GITHUB_WORKSPACE/trusted-ci-classifier"; do
if [ ! -e "$stale_qwen" ] && [ ! -L "$stale_qwen" ]; then
continue
fi
if [ -d "$stale_qwen" ] && [ ! -L "$stale_qwen" ]; then
chmod -R u+w "$stale_qwen" 2>/dev/null || true
fi
# A foreign-owned directory cannot always be renamed to a
# different parent: updating its .. entry can fail even when the
# workspace parent is writable. If that individual move fails,
# quarantine the runner-owned workspace itself, then recreate the
# empty checkout root. Warm contents are lost only on this
# otherwise unrecoverable path.
rm -rf -- "$stale_qwen" 2>/dev/null ||
sudo -n rm -rf -- "$stale_qwen" 2>/dev/null ||
{
quarantine="$(dirname -- "$GITHUB_WORKSPACE")/_qwen-quarantine"
mkdir -p "$quarantine" 2>/dev/null || true
stale_name="$(basename -- "$stale_qwen")"
if mv -- "$stale_qwen" "$quarantine/${stale_name#\.}-$(date -u +%Y%m%dT%H%M%SZ)-$$" 2>/dev/null; then
echo "::warning::could not delete leaked $stale_name; moved it to $quarantine so this checkout can proceed — that directory needs manual cleanup"
else
workspace_quarantine="$quarantine/workspace-$(date -u +%Y%m%dT%H%M%SZ)-$$"
if mv -- "$GITHUB_WORKSPACE" "$workspace_quarantine" 2>/dev/null &&
mkdir -p "$GITHUB_WORKSPACE" 2>/dev/null &&
cd "$GITHUB_WORKSPACE"; then
echo "::warning::could not delete leaked $stale_name; moved the whole workspace to $workspace_quarantine so this checkout can proceed — that directory needs manual cleanup"
break
else
echo "::warning::leaked $stale_name survived every recovery; runner needs manual cleanup"
fi
fi
}
done
# Interrupted reviews leave worktree registrations under .qwen/tmp/
# and qwen-review/* branches behind. prune drops registrations whose
# directories the rm above removed; worktree remove --force then
# clears any still-registered leftover directory (--force tolerates
# dirty contents), since a branch checked out in a live worktree
# cannot be deleted. If removal still fails, the registration
# survives and the branch delete below warns. The sweep deletes all
# review artifacts, not just the current PR's: safe because a runner
# executes one job at a time. Kept inline rather than a shared
# script: this runs pre-checkout on shared runners, where leftover
# workspace files are untrusted.
if [ -e "$GITHUB_WORKSPACE/.git" ]; then
GIT_SAFE=(git -c core.hooksPath=/dev/null -c core.fsmonitor= -C "$GITHUB_WORKSPACE")
"${GIT_SAFE[@]}" worktree prune -v || true
"${GIT_SAFE[@]}" worktree list --porcelain \
| awk '$1 == "worktree" && index($0, "/.qwen/tmp/review-pr-") > 0 { sub(/^worktree /, ""); print }' \
| while read -r worktree; do
[ -n "$worktree" ] || continue
# Registered paths come from leftover git metadata and are
# untrusted: the awk filter above matched by substring, so reject
# `..` traversal and re-anchor to the review prefix before the
# destructive remove.
case "$worktree" in
*/../*|../*|*/..)
echo "::warning::skipping suspicious review worktree path: $worktree"
continue
;;
"$GITHUB_WORKSPACE/.qwen/tmp/review-pr-"*) : ;;
*)
echo "::warning::skipping unexpected review worktree path: $worktree"
continue
;;
esac
"${GIT_SAFE[@]}" worktree remove --force "$worktree" ||
echo "::warning::could not remove review worktree: $worktree"
done || true
"${GIT_SAFE[@]}" worktree prune -v || true
"${GIT_SAFE[@]}" for-each-ref --format='%(refname:short)' 'refs/heads/qwen-review/*' \
| while read -r stale_ref; do
if [ -n "$stale_ref" ]; then
"${GIT_SAFE[@]}" branch -D "$stale_ref" ||
echo "::warning::could not remove review branch: $stale_ref"
fi
done || true
fi
- name: 'Checkout'
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
- name: 'Check Flyway migration versions are unique'
run: 'node scripts/check-flyway-migrations.js packages/sdk-java/managed-agent-server packages/sdk-java/runtime-broker packages/sdk-java/qwencode'
# Contract version monotonicity, #13804: every pull request that
# changes the OpenAPI document must move info.version strictly past
# the base ref's — consumers key capability detection on it, and a
# regressed or stalled number breaks nothing at runtime to be caught
# by. The default checkout is one commit deep and holds the
# prospective merge result, so the base branch's head is fetched at
# depth one for the comparison copy. Pull requests only: a push has
# no base to compare, and the races that remain after this ran are
# the re-check workflow's. Comparing contents, not a changed-paths
# list, is what lets a merge result that already advanced (another
# pull request landed) read as regressed-or-stalled instead of
# passing on a stale branch point.
- name: 'Check the API contract version advances'
if: "${{ github.event_name == 'pull_request' }}"
env:
BASE_REF: '${{ github.event.pull_request.base.ref }}'
run: |-
set -uo pipefail
DOCUMENT='packages/sdk-java/managed-agent-server/src/main/resources/openapi/managed-agent-public-api.openapi.json'
BASE_DOCUMENT="${RUNNER_TEMP}/contract-base.json"
git fetch --quiet --depth=1 --filter=blob:none origin "refs/heads/${BASE_REF}"
git show "FETCH_HEAD:${DOCUMENT}" > "${BASE_DOCUMENT}" 2>/dev/null || : > "${BASE_DOCUMENT}"
node scripts/check-contract-version.js "${BASE_DOCUMENT}" "${DOCUMENT}"
mysql-integration:
name: 'Runtime Broker and Managed Agent MariaDB / Java 21'
runs-on: 'ubuntu-latest'
timeout-minutes: 15
services:
mariadb:
image: 'mariadb:10.11.18'
env:
MARIADB_DATABASE: 'runtime_broker_test'
MARIADB_ROOT_PASSWORD: 'runtime-broker'
ports:
- '3306:3306'
options: >-
--health-cmd "healthcheck.sh --connect --innodb_initialized"
--health-interval 5s
--health-timeout 5s
--health-retries 20
steps:
- name: 'Checkout'
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
- name: 'Set up Java'
uses: 'actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654' # v5.2.0
with:
distribution: 'temurin'
java-version: '21'
cache: 'maven'
cache-dependency-path: |-
packages/sdk-java/qwencode/pom.xml
packages/sdk-java/runtime-broker/pom.xml
packages/sdk-java/managed-agent-server/pom.xml
settings-path: '${{ runner.temp }}/setup-java-m2'
- name: 'Run Runtime Broker MySQL integration tests'
working-directory: 'packages/sdk-java/runtime-broker'
env:
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
run: >-
mvn --batch-mode --no-transfer-progress -Pmysql-integration
-Dmysql.url='jdbc:mysql://127.0.0.1:3306/runtime_broker_test?allowPublicKeyRetrieval=true&useSSL=false'
-Dmysql.user=root -Dmysql.password=runtime-broker clean verify
- name: 'Install Managed Agent dependencies'
env:
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
run: |-
mvn --batch-mode --no-transfer-progress -f packages/sdk-java/qwencode/pom.xml -DskipTests -Dgpg.skip=true -Dmaven.javadoc.skip=true install
# These steps only stage jars; the SpotBugs gate lives on the
# `clean verify` invocations (pinned by scripts/tests/spotbugs-gate.test.js).
mvn --batch-mode --no-transfer-progress -f packages/sdk-java/runtime-broker/pom.xml -DskipTests -Dspotbugs.skip=true install
- name: 'Run Managed Agent tests, Checkstyle, and MySQL integration'
working-directory: 'packages/sdk-java/managed-agent-server'
env:
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
run: >-
mvn --batch-mode --no-transfer-progress -Pmysql-integration
-Dmysql.url='jdbc:mysql://127.0.0.1:3306/managed_agent_test?createDatabaseIfNotExist=true&allowPublicKeyRetrieval=true&useSSL=false'
-Dmysql.user=root -Dmysql.password=runtime-broker clean verify checkstyle:check
# Maven passes a selection narrowed by a profile, MAVEN_ARGS or
# .mvn/maven.config, so compare what ran with the source tree.
- name: 'Check that every non-Hosted integration test class ran'
run: 'node scripts/check-failsafe-reports.js non-hosted packages/sdk-java/runtime-broker packages/sdk-java/managed-agent-server'
hosted-harness-mysql:
name: 'Hosted process fault gates / MySQL 8.4 / Java 21'
runs-on: 'ubuntu-latest'
# The step ceilings (25 + 9×10 + 20 = 135 — the Verify and O4 steps
# sit above their failsafe fork timeouts) plus ~10 minutes for the
# uncapped setup steps (Checkout..Install MySQL binaries, the report
# checks) need ~145; 148 keeps a thin margin, and each arm buys its
# own 10 instead of sharing an envelope.
timeout-minutes: 148
services:
mysql:
image: 'mysql:8.4.6'
env:
MYSQL_DATABASE: 'hosted_harness_test'
MYSQL_ROOT_PASSWORD: 'hosted-fixture'
ports:
- '3306/tcp'
options: >-
--health-cmd "mysqladmin ping -h 127.0.0.1 -phosted-fixture"
--health-interval 5s
--health-timeout 5s
--health-retries 30
steps:
- name: 'Checkout'
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
- name: 'Set up Java'
uses: 'actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654' # v5.2.0
with:
distribution: 'temurin'
java-version: '21'
cache: 'maven'
cache-dependency-path: 'packages/sdk-java/**/pom.xml'
settings-path: '${{ runner.temp }}/setup-java-m2'
- name: 'Set up Node.js'
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
with:
node-version: '22'
package-manager-cache: false
- name: 'Enable Corepack'
run: 'corepack enable'
- name: 'Install, build and bundle CLI'
run: |-
corepack pnpm install --frozen-lockfile --prefer-offline --reporter=append-only
- name: 'Install Managed Agent dependencies'
id: 'managed_dependencies'
env:
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
run: |-
mvn --batch-mode --no-transfer-progress -f packages/sdk-java/qwencode/pom.xml -DskipTests -Dgpg.skip=true -Dmaven.javadoc.skip=true install
# These steps only stage jars; the SpotBugs gate lives on the
# `clean verify` invocations (pinned by scripts/tests/spotbugs-gate.test.js).
mvn --batch-mode --no-transfer-progress -f packages/sdk-java/runtime-broker/pom.xml -DskipTests -Dspotbugs.skip=true install
# The drivers' proxy header filter only shows up in the gates below as
# an intermittent socket-reuse failure, so pin it directly first.
- name: 'Check Hosted proxy header relay'
run: |-
cd integration-tests
npx vitest run helpers/hosted-relay-headers.test.ts
- name: 'Verify Hosted Java, Spring and MySQL processes'
# Must outlive the hosted-harness-mysql failsafe fork timeout (900 s)
# plus the wrapped compile/surefire/spotbugs/checkstyle work: a step
# killed before its fork leaves no per-test failure lines, so the
# main-CI failure analyzer can only file an undiagnosable per-commit
# issue (#13503). hosted-process-ci.test.js pins this ordering.
timeout-minutes: 25
env:
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
MYSQL_PORT: "${{ job.services.mysql.ports['3306'] }}"
run: >-
mvn --batch-mode --no-transfer-progress -f packages/sdk-java/managed-agent-server/pom.xml
-Phosted-harness-mysql
-Dnode.executable="$(command -v node)"
-Dqwen.cli.entry="${GITHUB_WORKSPACE}/dist/cli.js"
-Dmysql.url="jdbc:mysql://127.0.0.1:${MYSQL_PORT}/hosted_harness_test?allowPublicKeyRetrieval=true&useSSL=false"
-Dmysql.user=root -Dmysql.password=hosted-fixture clean verify checkstyle:check
- name: 'Check that every Hosted integration test class ran'
run: 'node scripts/check-failsafe-reports.js hosted packages/sdk-java/managed-agent-server'
- name: 'Check Hosted latency measurements'
run: |-
test -s packages/sdk-java/managed-agent-server/target/hosted-latency-baseline.json
cd integration-tests
npx vitest run cli/hosted-latency-baseline.test.ts
- name: 'Run Runtime Broker fault gates'
timeout-minutes: 10
env:
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
run: >-
mvn --batch-mode --no-transfer-progress -f packages/sdk-java/runtime-broker/pom.xml
-Pfault-gates -Dqwen.cli.entry="${GITHUB_WORKSPACE}/dist/cli.js" test
- name: 'Install MySQL binaries for the failover E2E'
run: |-
if ! command -v mysqld >/dev/null 2>&1; then
sudo apt-get update
sudo apt-get install --yes --no-install-recommends mysql-server mysql-client
fi
- name: 'Run session owner failover E2E'
timeout-minutes: 10
run: >-
npm run test:e2e:managed-session-failover
- name: 'Run in-flight owner failover E2E'
timeout-minutes: 10
run: >-
npm run test:e2e:managed-inflight-failover
- name: 'Run continuation owner failover E2E'
timeout-minutes: 10
run: >-
npm run test:e2e:managed-continuation-failover
- name: 'Run long-answer persistence E2E'
timeout-minutes: 10
run: >-
npm run test:e2e:managed-big-output
- name: 'Run Harness-restart session failover E2E'
timeout-minutes: 10
run: >-
npm run test:e2e:managed-harness-restart-failover
- name: 'Run Harness-restart in-flight failover E2E'
timeout-minutes: 10
run: >-
npm run test:e2e:managed-harness-restart-inflight-failover
- name: 'Run Harness-restart continuation failover E2E'
timeout-minutes: 10
run: >-
npm run test:e2e:managed-harness-restart-continuation-failover
- name: 'Run frozen former-owner fencing E2E'
timeout-minutes: 10
run: >-
npm run test:e2e:managed-continuation-frozen-owner-failover
- name: 'Upload Hosted process reports'
if: 'always()'
uses: 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' # v7.0.1
with:
name: 'hosted-harness-mysql-reports'
path: |
packages/sdk-java/managed-agent-server/target/failsafe-reports/
packages/sdk-java/managed-agent-server/target/hosted-latency-baseline.json
retention-days: 7
- name: 'Verify O4 filesystem process and capacity gates'
id: 'o4_mysql'
if: "${{ !cancelled() && steps.managed_dependencies.outcome == 'success' }}"
# Must outlive the o4-mysql-gates failsafe fork timeout (600 s) plus
# the wrapped compile/surefire/spotbugs/checkstyle work — the same
# ordering the Hosted verify step needed at #13503: killed at 12
# minutes on a loaded runner, the step left no per-test failure
# lines (#13684). hosted-process-ci.test.js pins this ordering.
timeout-minutes: 20
env:
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
MYSQL_PORT: "${{ job.services.mysql.ports['3306'] }}"
MYSQL_PWD: 'hosted-fixture'
QWEN_O4_MYSQL_PASSWORD: 'hosted-fixture'
run: |-
mysql --no-defaults --protocol=TCP --host=127.0.0.1 --port="${MYSQL_PORT}" --user=root -e 'CREATE DATABASE qwen_o4_ci'
mvn --batch-mode --no-transfer-progress -f packages/sdk-java/managed-agent-server/pom.xml \
-Po4-mysql-gates \
-Dqwen.o4.mysql.url="jdbc:mysql://127.0.0.1:${MYSQL_PORT}/qwen_o4_ci?allowPublicKeyRetrieval=true&useSSL=false" \
-Dqwen.o4.mysql.user=root clean verify checkstyle:check && \
node scripts/check-failsafe-reports.js o4-mysql packages/sdk-java/managed-agent-server
- name: 'Upload O4 gate reports'
if: "${{ always() && steps.o4_mysql.outcome != 'skipped' }}"
uses: 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' # v7.0.1
with:
name: 'o4-mysql-gate-reports'
path: 'packages/sdk-java/managed-agent-server/target/failsafe-reports/'
retention-days: 7
daemon-e2e:
name: 'Real daemon E2E / Java 11'
runs-on: '${{ (github.repository == ''QwenLM/qwen-code'' && vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'' && (github.event_name == ''push'' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON(''["OWNER","MEMBER","COLLABORATOR"]''), github.event.pull_request.author_association))) && fromJSON(''["self-hosted", "linux", "x64", "ecs-qwen"]'') || ''ubuntu-latest'' }}'
# 45, not 30: this job is ~90% setup. A quiet run is 12.2 min end to end --
# npm ci 5.9 + build 5.2 -- and the Java daemon E2E it exists for costs
# about a minute. On the shared pool that setup is what stretches: npm ci
# measured 4.0-5.9 min across 11 green runs and 19.9/22.6 min on the two
# that died, which is the whole difference between them. At that factor the
# setup alone needs ~44 min, so 30 cancelled the job before a single
# assertion ran. Matches the release.yml quality_build tier
# (QWEN_RELEASE_BUILD_TIMEOUT_MINUTES, default 45).
timeout-minutes: 45
steps:
- name: 'Restore workspace ownership'
if: "${{ runner.environment == 'self-hosted' }}"
run: |-
set -uo pipefail
RUNNER_UID="$(id -u)"
RUNNER_GID="$(id -g)"
if [ "$RUNNER_UID" != "0" ]; then
chown -R "$RUNNER_UID:$RUNNER_GID" "$GITHUB_WORKSPACE" 2>/dev/null || sudo -n chown -R "$RUNNER_UID:$RUNNER_GID" "$GITHUB_WORKSPACE" || echo "::warning::could not restore workspace ownership; checkout may fail on leftover root-owned files"
fi
chmod -R u+rwX "$GITHUB_WORKSPACE" 2>/dev/null || sudo -n chmod -R u+rwX "$GITHUB_WORKSPACE" || echo "::warning::could not restore workspace write permissions; checkout may fail on leftover read-only files"
# A leftover workspace owned by a different uid than the step user
# (e.g. a runner-user checkout under root-run steps) makes every git
# call fail with "detected dubious ownership". The pinned
# actions/checkout adds safe.directory only to its own temporary
# global config, so later steps are uncovered; trust the workspace
# here instead. The --get-all guard keeps a persistent runner's
# gitconfig from accumulating a duplicate entry per job.
git config --global --get-all safe.directory 2>/dev/null | grep -qxF "$GITHUB_WORKSPACE" || git config --global --add safe.directory "$GITHUB_WORKSPACE" || echo "::warning::could not mark the workspace as a git safe.directory; git steps may fail on a mismatched-ownership checkout"
# ci.yml's pre-checkout sweep, byte-identical to the ci.yml copies
# (pinned in scripts/tests/sdk-java-workflow.test.js): residue left by
# review/autofix jobs on the shared ECS pool fails actions/checkout's
# clean — the #13506 failure. Hosted legs skip it on fresh VMs.
- name: 'Clean stale .qwen before checkout'
if: "${{ runner.environment == 'self-hosted' }}"
run: |-
set -uo pipefail
for stale_qwen in "$GITHUB_WORKSPACE/.qwen" "$GITHUB_WORKSPACE/.qwen.root-orig" "$GITHUB_WORKSPACE/trusted-ci-classifier"; do
if [ ! -e "$stale_qwen" ] && [ ! -L "$stale_qwen" ]; then
continue
fi
if [ -d "$stale_qwen" ] && [ ! -L "$stale_qwen" ]; then
chmod -R u+w "$stale_qwen" 2>/dev/null || true
fi
# A foreign-owned directory cannot always be renamed to a
# different parent: updating its .. entry can fail even when the
# workspace parent is writable. If that individual move fails,
# quarantine the runner-owned workspace itself, then recreate the
# empty checkout root. Warm contents are lost only on this
# otherwise unrecoverable path.
rm -rf -- "$stale_qwen" 2>/dev/null ||
sudo -n rm -rf -- "$stale_qwen" 2>/dev/null ||
{
quarantine="$(dirname -- "$GITHUB_WORKSPACE")/_qwen-quarantine"
mkdir -p "$quarantine" 2>/dev/null || true
stale_name="$(basename -- "$stale_qwen")"
if mv -- "$stale_qwen" "$quarantine/${stale_name#\.}-$(date -u +%Y%m%dT%H%M%SZ)-$$" 2>/dev/null; then
echo "::warning::could not delete leaked $stale_name; moved it to $quarantine so this checkout can proceed — that directory needs manual cleanup"
else
workspace_quarantine="$quarantine/workspace-$(date -u +%Y%m%dT%H%M%SZ)-$$"
if mv -- "$GITHUB_WORKSPACE" "$workspace_quarantine" 2>/dev/null &&
mkdir -p "$GITHUB_WORKSPACE" 2>/dev/null &&
cd "$GITHUB_WORKSPACE"; then
echo "::warning::could not delete leaked $stale_name; moved the whole workspace to $workspace_quarantine so this checkout can proceed — that directory needs manual cleanup"
break
else
echo "::warning::leaked $stale_name survived every recovery; runner needs manual cleanup"
fi
fi
}
done
# Interrupted reviews leave worktree registrations under .qwen/tmp/
# and qwen-review/* branches behind. prune drops registrations whose
# directories the rm above removed; worktree remove --force then
# clears any still-registered leftover directory (--force tolerates
# dirty contents), since a branch checked out in a live worktree
# cannot be deleted. If removal still fails, the registration
# survives and the branch delete below warns. The sweep deletes all
# review artifacts, not just the current PR's: safe because a runner
# executes one job at a time. Kept inline rather than a shared
# script: this runs pre-checkout on shared runners, where leftover
# workspace files are untrusted.
if [ -e "$GITHUB_WORKSPACE/.git" ]; then
GIT_SAFE=(git -c core.hooksPath=/dev/null -c core.fsmonitor= -C "$GITHUB_WORKSPACE")
"${GIT_SAFE[@]}" worktree prune -v || true
"${GIT_SAFE[@]}" worktree list --porcelain \
| awk '$1 == "worktree" && index($0, "/.qwen/tmp/review-pr-") > 0 { sub(/^worktree /, ""); print }' \
| while read -r worktree; do
[ -n "$worktree" ] || continue
# Registered paths come from leftover git metadata and are
# untrusted: the awk filter above matched by substring, so reject
# `..` traversal and re-anchor to the review prefix before the
# destructive remove.
case "$worktree" in
*/../*|../*|*/..)
echo "::warning::skipping suspicious review worktree path: $worktree"
continue
;;
"$GITHUB_WORKSPACE/.qwen/tmp/review-pr-"*) : ;;
*)
echo "::warning::skipping unexpected review worktree path: $worktree"
continue
;;
esac
"${GIT_SAFE[@]}" worktree remove --force "$worktree" ||
echo "::warning::could not remove review worktree: $worktree"
done || true
"${GIT_SAFE[@]}" worktree prune -v || true
"${GIT_SAFE[@]}" for-each-ref --format='%(refname:short)' 'refs/heads/qwen-review/*' \
| while read -r stale_ref; do
if [ -n "$stale_ref" ]; then
"${GIT_SAFE[@]}" branch -D "$stale_ref" ||
echo "::warning::could not remove review branch: $stale_ref"
fi
done || true
fi
- name: 'Checkout'
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
with:
ref: "${{ (runner.environment == 'self-hosted' && github.event_name == 'pull_request' && format('refs/pull/{0}/head', github.event.pull_request.number)) || github.ref }}"
- name: 'Verify checkout includes expected head commit'
if: "${{ runner.environment == 'self-hosted' && github.event_name == 'pull_request' }}"
env:
EXPECTED_SHA: '${{ github.event.pull_request.head.sha }}'
run: |-
if ! git merge-base --is-ancestor "${EXPECTED_SHA}" HEAD; then
echo "::error::Checked out ref does not contain expected head ${EXPECTED_SHA}."
git log --oneline --decorate -5
exit 1
fi
# Self-hosted can't reach nodejs.org reliably; reuse the machine's Node.
- name: 'Set up Node.js 22 (hosted)'
if: "${{ runner.environment == 'github-hosted' }}"
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
with:
node-version: '22'
package-manager-cache: false
- name: 'Use pre-installed Node.js (self-hosted)'
if: "${{ runner.environment == 'self-hosted' }}"
run: |-
if ! command -v node >/dev/null 2>&1; then
echo "::error::Node.js is not on PATH for this self-hosted runner. Provision Node 22.x or set MAINTAINER_ECS_RUNNER_DISABLED=true."
exit 1
fi
echo "Using pre-installed Node $(node -v) / npm $(npm -v)"
if [[ "$(node -p 'process.versions.node.split(".")[0]')" != "22" ]]; then
echo "::warning::Expected Node 22.x but found $(node -v); daemon E2E will run against the runner's Node."
fi
- name: 'Set up Java'
uses: 'actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654' # v5.2.0
with:
distribution: 'temurin'
java-version: '11'
cache: 'maven'
cache-dependency-path: 'packages/sdk-java/qwencode/pom.xml'
settings-path: '${{ runner.temp }}/setup-java-m2'
- name: 'Set up Maven (self-hosted)'
if: "${{ runner.environment == 'self-hosted' }}"
run: |-
archive="${RUNNER_TEMP}/apache-maven-${MAVEN_VERSION}-bin.tar.gz"
curl -fsSL "https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/${MAVEN_VERSION}/apache-maven-${MAVEN_VERSION}-bin.tar.gz" -o "${archive}"
echo "${MAVEN_SHA512} ${archive}" | sha512sum --check --status
tar -xzf "${archive}" -C "${RUNNER_TEMP}"
echo "${RUNNER_TEMP}/apache-maven-${MAVEN_VERSION}/bin" >> "${GITHUB_PATH}"
- name: 'Install Node.js dependencies'
run: 'corepack pnpm install --frozen-lockfile --prefer-offline --reporter=append-only'
- name: 'Build Qwen Code'
run: 'npm run build'
- name: 'Bundle Qwen Code'
run: 'npm run bundle'
- name: 'Run Java daemon E2E'
env:
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
run: 'npx tsx scripts/run-java-daemon-sdk-e2e.ts'