* fix(ci): allow triage for fork PRs that pass safety precheck
Fork PRs by non-write authors were blocked from triage even when the
safety precheck passed, because the authorize job gates on write
permission. Triage is read-only (checks out trusted base code, reads PR
via API), so precheck pass is sufficient to run it safely.
Split the pull_request_target and issue_comment authorization paths:
- pull_request_target: accept write permission OR precheck pass
- issue_comment /triage: still require write permission on the commenter
tmux-testing is unchanged — it executes PR code and must keep the write
permission gate.
* fix(ci): allow prechecked fork PR automation
* docs(ci): clarify PR review authorization routing
* test(ci): guard fork precheck authorization
* fix(ci): tighten fork precheck review guards
* test(ci): guard review-request authorization
* fix(ci): use CI_BOT_PAT for precheck comment on fork PRs
The precheck workflow's "Upsert/Clear manual approval comment" steps
fail with HTTP 403 on fork PRs because `github.token` from
`pull_request_target` via `workflow_call` lacks sufficient permissions
to read/write issue comments.
- Use `secrets.CI_BOT_PAT || github.token` for the comment steps
- Add `secrets: inherit` to both caller workflows so the reusable
workflow can access CI_BOT_PAT
- Drop the `.user.login == "github-actions[bot]"` filter from the jq
query since the HTML comment marker is sufficient and the posting
user may differ depending on which token is used
* fix(ci): scope precheck comment dedup to known bot users
Narrow the jq filter to match comments from github-actions[bot],
qwen-code-ci-bot, or any Bot-type user — prevents a manually posted
marker from hijacking the dedup logic.
* ci: pass only precheck bot token
* ci(workflows): remind authors not to force-push active PRs
Add a workflow that detects force-pushes (rebase/amend/reset) to open PRs
via the pull_request_target synchronize event and posts a one-time,
bilingual reminder that force-pushing invalidates existing review comments
and that the integration bots squash all changes into a single commit
automatically. A normal push (compare status "ahead") is ignored; the
reminder is posted at most once per PR, bot-initiated pushes are skipped,
and a failed compare is treated conservatively (no comment).
* ci(workflows): address review — add issues:write, serialize without cancel
- Add `issues: write`: the listComments/createComment calls go through the
Issues API; declaring it matches the repo's other PR-commenting workflows
and avoids any risk of a 403 making the workflow inert.
- Set `cancel-in-progress: false`: an in-flight run that already detected a
force-push must finish and post. The concurrency group still serializes
runs per PR, and the once-per-PR marker prevents duplicates, so later
pushes queue and then no-op instead of cancelling (and silently dropping)
a pending reminder.
* ci(workflows): harden force-push detection per review
- Marker dedup now requires the comment to be from github-actions[bot], so a
user pasting the marker string into a comment can't suppress reminders.
- Skip known automation logins (qwen-code-dev-bot et al.) that push via PAT as
sender.type 'User', not just GitHub App bots (mirrors qwen-autofix KNOWN_BOTS).
- Narrow the compare catch to 404 (orphaned old tip -> skip); rethrow other
errors so auth/rate failures go red instead of silently no-op'ing.
- Wrap createComment with structured error logging + rethrow.
Kept 3-dot compare and base-repo owner: verified that 3-dot returns
diverged/behind for force-pushes and that the base repo resolves fork-PR
commits, while the suggested 2-dot syntax 404s in the REST API.
* test(ci): add structural test for the force-push reminder workflow
- Add scripts/tests/pr-force-push-reminder-workflow.test.js (runs under
test:scripts, which CI chains into test:ci). It asserts the trigger, repo
guard, permissions, serialized concurrency, KNOWN_AUTOMATION sync with
qwen-autofix, the 3-dot compare on the base repo, 404-vs-rethrow, the marker
author check, and the bilingual body — locking in the reviewed behaviors.
- Wrap the listComments paginate call in the same core.error + rethrow the
other two API calls already use.
- Note that KNOWN_AUTOMATION must stay in sync with qwen-autofix.yml KNOWN_BOTS.
* ci(workflows): drop concurrency group, rely on marker for idempotency
A concurrency group keeps at most one pending run per group, so a burst of
pushes can cancel a still-pending force-push run before it reaches the script,
dropping the reminder this workflow exists to post. Remove the group entirely:
every synchronize event now runs independently and is always evaluated, and the
once-per-PR marker provides idempotency. A rare double-post on two
near-simultaneous first force-pushes is the acceptable cost of never silently
missing one. Update the structural test to assert there is no concurrency block.
The reviewer's suggested `queue: max` is not a valid GitHub Actions concurrency
key (only `group`/`cancel-in-progress` are allowed) and fails actionlint.
* test(ci): use Qwen Team header and assert the dedup skip path
- Switch the copyright header to the prevailing `Qwen Team` (14 of 17 sibling
test files use it; this file had copied an older Google LLC header).
- Assert the idempotency skip log line so removing the marker guard fails a test.
* test(ci): mechanically enforce KNOWN_AUTOMATION sync with qwen-autofix
Read qwen-autofix.yml's KNOWN_BOTS and assert each login is also skipped here,
so adding a bot there without updating this workflow fails the test instead of
silently drifting. Replaces the hardcoded login list whose comment overclaimed
that the sync was verified.
* ci(autofix): loosen issue candidate filters so the agent finds work
The scheduled issue phase had been finding 0 candidates on every run. Tier-1
requires the rarely-applied status/ready-for-agent label, and tier-2's
"unattended = no human comment at all" rule fought its ">2 days old" age
gate: on a busy repo every aged bug already has a community reply, so the
funnel collapsed (128 aged bugs -> 0 survivors) purely on the comment rule.
- Unattended now keys off maintainer engagement (OWNER/MEMBER/COLLABORATOR
comments, excluding our own bots), not any human comment. Community
"+1"/"me too" no longer disqualifies an issue.
- Tier-1 and tier-2 are always both gathered (tier-1 prioritized, then
de-duped by number) so a single weak ready-for-agent issue can't starve
the run.
- Tier-2 scans a bounded age window via MIN_ISSUE_AGE_DAYS..MAX_ISSUE_AGE_DAYS
(1..15) to focus on fresh, settled bugs instead of an unbounded set.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(ci): preserve autofix issue tier priority
* fix(ci): keep ready autofix issues in tier one
* fix(ci): reserve autofix tier two slots
* fix(ci): tolerate tier two autofix scan failures
* fix(ci): tolerate tier one autofix scan failures
* fix(ci): harden autofix candidate merging
* fix(ci): harden autofix issue scanning
* fix(ci): harden autofix diagnostics
* fix(ci): skip unsafe autofix comment fallbacks
* fix(ci): drop unused autofix comments payload
* test(ci): strengthen autofix comment refresh guard
* test(ci): satisfy autofix workflow lint
* test(ci): tighten autofix tier two assertion
* test(ci): cover autofix tier merge guards
* test(ci): cover autofix unattended filter
* fix(ci): summarize autofix comment refresh drops
* fix(ci): bound autofix tier-2 comment-refresh API calls
Cap the tier-2 issues fed into the comment-refresh loop to 15 (a margin
above the ~10 ever selected, since filter_unattended_candidates drops
attended issues afterward) and request full pages with per_page=100, to
avoid GitHub secondary rate limits. Add workflow tests asserting the
forced-issue skip/in-progress exclusion and the tier-2 ready-for-agent
label exclusion stay in place.
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
* fix(ci): refresh all scanned tier-2 autofix issues, not just first 15
The previous change capped comment-refresh to the first 15 tier-2 issues
before filter_unattended_candidates runs. The scan still fetches 30, so
valid unattended bugs at positions 16-30 were never refreshed nor
filterable: if the first 15 were all maintainer-attended/dropped, the run
could end with zero candidates again — the failure this PR set out to fix.
Drop the .[0:15] refresh cap and refresh the full scanned set (already
bounded by --limit 30 on the scan). per_page=100 stays as the
pages-per-issue rate-limit mitigation, and the drop summary total/counters
reflect the full refreshed set again. Top-N selection after filtering is
unchanged.
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
* fix(ci): order tier priority before recency in autofix assess prompt
The assess prompt gave two conflicting selection rules: the tier note says not to pick a tier-2 issue over a comparably actionable tier-1 one, while the recency tiebreaker says to prefer the most recently reported. Since tier-1 has no age bound and tier-2 is the recent (1-15 day) tier, these point in opposite directions for comparable confidence. Make recency apply only after the tier preference and only within the same tier, removing the contradiction. Prompt text only; no shell/jq change.
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
* fix(ci): guard forced-issue jq and derive autofix comment scratch path
Address two review suggestions on the autofix candidate scan step.
- Wrap the forced-issue candidate jq in the same error guard the other
jq calls use (tier-1/tier-2 stamp, tier merge): on malformed
forced-issue JSON it now emits a :⚠️: and falls back to an empty
candidate list instead of silently producing an empty candidates.json
with no diagnostic.
- Make refresh_issue_comments reuse-safe by deriving its NDJSON scratch
path from the output_file parameter (${output_file%.json}.ndjson)
instead of hardcoding tier2-with-comments.ndjson, so a second call or a
different tier can't clobber a shared scratch file. Behavior for the
current single call is unchanged.
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <noreply@qwen.ai>
Large PRs with extensive diffs across multiple packages can exceed
the 85-minute effective review window (90 - 5 buffer). The recent
run #28318973107 timed out on PR #5777 which touched acp-bridge,
serve, config, and chrome-extension packages.
Bump both the job timeout-minutes and the validation cap to 120 so
the review agent has enough headroom for complex multi-package PRs.
* Upgrade GitHub Actions to latest versions
Signed-off-by: Salman Muin Kayser Chishti <13schishti@gmail.com>
* ci: restore ratchet annotations on upgraded actions
The action version bumps had replaced '# ratchet:owner/repo@<ver>'
annotations with bare '# vX.Y.Z' comments, which silently breaks
ratchet's ability to track and update these SHA pins. Restore the
annotations on every changed line (updated to the new major), matching
each line's pre-existing convention on main.
---------
Signed-off-by: Salman Muin Kayser Chishti <13schishti@gmail.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: yiliang114 <effortyiliang@gmail.com>
* feat(cli): add /model --vision for a fallback vision model
Add `/model --vision <id>` and an interactive vision-model picker to
configure an image-capable model the vision bridge borrows when a
text-only main model receives an image, mirroring the `/model --fast`
pattern (flat `visionModel` setting + runtime `setVisionModel`). An
explicit vision model takes precedence over same-provider auto-select and
may cross providers; an unreachable pin falls back to auto-select rather
than firing the bridge at a model it can't reach.
Also fixes two vision-bridge issues surfaced while verifying #5126:
- The gate read stale modalities after a runtime model switch, so an
@image sent right after switching to a multimodal model could still
trigger the bridge. Refresh model-derived modalities on the qwen-oauth
hot-update paths (setModel default-model + handleModelChange) so the
gate reflects the current model.
- The transcription was appended after a now-empty "Content from <file>:"
header, leading the primary model to re-read the image file and ignore
the transcript. Stand the transcription in the image's slot and tell the
model the image cannot be read by a tool.
Closes#5597
* fix(cli): add vision model description translations
* feat(cli): warn on non-image-capable /model --vision pin; cover modality refresh
Self-review follow-ups on the /model --vision work:
- Warn (but still honor) when /model --vision or the picker pins a model that
isn't image-capable, reusing the vision bridge's isImageCapable so the
explicit and auto-select paths agree on what counts as vision-capable.
isImageCapable is now exported for the CLI to share.
- Exclude fastOnly models from --vision validation so it matches the
completion filter.
- Add a Config-level test asserting handleModelChange refreshes modalities on
the qwen-oauth hot path (the vision-bridge gate fix that lacked direct
coverage).
* fix(cli): translate the non-image-capable vision model warning
Add zh/zh-TW/en entries for the /model --vision picker warning shown when a pinned model isn't image-capable, matching the existing vision-string translations.
* fix(vision-bridge): stop showing the image transcription twice
The vision bridge printed the full transcription in its notice AND fed it to
the primary model, which then re-stated it — so the user read the same image
description twice (once in the notice box, once in the answer).
- The notice now shows only the egress-disclosure header; the transcription is
fed to the primary model and surfaced in its answer, so it is no longer
duplicated. Removes the now-unused transcript truncation / control-char
stripping helpers.
- The bridge model is instructed to transcribe/describe, not answer the user
request (the user's intent is carried as a focus hint, not a question), so its
output is context for the primary model rather than a second competing answer.
* fix(cli): register vision model i18n strings and cover /model --vision
- Register 'Vision Model' / 'Select Vision Model' in en/zh/zh-TW so the
vision picker and confirmation aren't English-only for non-English users
(mirrors the existing Voice Model entries).
- formatNonVisionModelWarning reuses the dialog's already-registered t()
key instead of a plain English literal, keeping both paths i18n-consistent.
- Add the missing bare '/model --vision' tests (interactive dialog +
non-interactive current-model report), mirroring the --voice coverage.
* refactor(vision-bridge): remove dead VisionBridgeResult.transcript field
The inline transcript display was dropped in 14de33333 (it duplicated the
description already fed to the primary model), leaving `transcript` set on
the result but never read in any production path. Remove the field and its
now-redundant test — the untrusted-wrapper assertion it carried is already
covered by 'converts images to an untrusted text block on success'.
* test(cli): cover vision-model branches; dedupe fast/vision helpers
Refactor (behavior-neutral):
- modelCommand.ts: fold identical formatUnavailableFast/VisionModelMessage
bodies into formatUnavailableAuxModelMessage(label, hint).
- ModelDialog.tsx: extract the fast/vision selection-key parsing into
encodeAuxModelSelector(); voice keeps using the model id directly.
Tests:
- config: malformed pin (catch branch) falls back instead of throwing;
authType-qualified visionModel binds to the matching provider only;
setVisionModel('')/undefined clears back to auto-select.
- modelCommand: authType-scoped vision rejection message.
- ModelDialog: non-image-capable pin emits the warning in history.
* test(cli): vision-mode coverage + register --vision completion i18n key
- i18n: register the `--vision` completion description key in en/zh/zh-TW
(was wrapped in t() with no entry, so non-English showed English).
- useModelCommand: test vision mode opens + suppresses fast, resets on close.
- modelCommand: test --vision rejects fastOnly/voiceOnly models.
Also corrects the stale "30-minute" wait-timer comment in
qwen-code-pr-review.yml (actual env timer is 10m; point to repo settings).
* fix(core): exclude primary model from explicit vision bridge selection
resolveVisionModelSelection lacked the m.id !== primary guard that the
auto-select path (selectVisionBridgeModel) already has, so pinning the
text-only primary via /model --vision would fire the bridge at a model
that cannot read images. Mirror the auto-select guard.
* chore(core): log vision bridge transcription for traceability
The transcription is no longer surfaced to the user or carried on the
result; log it at debug so a wrong primary-model answer can be traced to
a misread image vs a downstream hallucination.
* test: cover vision model helpers and vision-model dialog dispatch
Add direct unit tests for encodeAuxModelSelector (3 key shapes) and
isImageCapable (isVision/modalities/name-fallback), and a dispatch test
for the /model --vision dialog case mirroring voice-model.
* fix(cli): render vision bridge notice as a dim tip, drop doubled marker
The success/cancelled vision-bridge notice was added as MessageType.INFO
(primary-color `●` gutter) while its text also led with `🔎`, producing a
doubled `● 🔎` marker. Route success/cancelled notices to a new dim
`vision_notice` variant (secondary color, single `🔎` gutter glyph) and
strip the baked-in glyphs from the notice text so the renderer owns the
prefix. Failures keep the prominent red `✕` ERROR variant (also
de-duplicated).
* test(cli): assert VISION_NOTICE type for mid-turn vision bridge notice
* test(cli): cover vision_notice classification and rendering
* fix(vision): harden vision-bridge model selection + UX
Address review feedback on #5778:
- config: provider-aware isCurrentPrimaryModel() so a cross-provider namesake
(e.g. anthropic:shared-model vs an openai shared-model primary) stays an
eligible vision pin; resolveVisionModelSelection now also filters fast/voice-
only models (a settings.json pin bypasses the slash command) and warns on
every silent-drop path so a stale pin is debuggable.
- modelCommand/ModelDialog: reject pinning the current primary as the vision
model at set time (ignored at runtime) instead of persisting a dead pin.
- useModelCommand: vision mode suppresses voice so the dialog can't end up in
two specialized modes at once.
- HistoryItemDisplay: vision_notice gets 0 top margin like its notice peers.
- vision-bridge-service: log transcription metadata only (model + length),
never the raw text — screenshots carry tokens/PII and debug logs can be
shared in support bundles.
PR #5778.
* fix(vision): fail-closed bridge routing, ambiguity guards, i18n, settings, tests
Address the remaining #5778 review feedback:
- baseLlmClient/sideQuery/vision-bridge: add an opt-in `failClosed` to the
per-model resolve path; the vision bridge sets it so a missing cross-provider
credential fails the conversion instead of silently routing image payloads at
the text-only main generator (which BaseLlmClient otherwise falls back to).
- ModelDialog: reject a vision pin that maps to multiple same-provider endpoints
(the persisted authType:modelId can't disambiguate base URLs), mirroring the
voice-mode duplicate guard.
- modelCommand: wrap the non-interactive vision status message in t() with
en/zh/zh-TW locale entries.
- SettingsDialog: wire the Vision Model row into the sub-dialog dispatch so Enter
/ Right-Arrow open the picker (like fastModel).
- image-part-utils: document the multi-image drop / "don't read_file" contract.
- tests: fail-closed resolve paths, primary-guard fallback, vision initialIndex,
authType-qualified + malformed vision selectors, failure-path image removal.
PR #5778.
---------
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: yiliang114 <effortyiliang@gmail.com>
* feat(cua-driver): vendor trycua/cua driver with 1000-normalized coordinate support
Vendor libs/cua-driver from trycua/cua into packages/cua-driver as the
basis for qwen-code's computer-use backend, adding an opt-in relative
(1000x1000 normalized) coordinate mode for Qwen-VL clients.
- coord_norm.rs: 0-1000 <-> pixel conversion, per-(pid,window_id) size
cache, tools/list description rewrite (TDD, 27 tests)
- ToolRegistry: normalized field + invoke input/output hooks
- protocol.rs: system-instruction coordinate wording switched by mode
- serve.rs: daemon list path description rewrite (input_schema aware)
- main.rs: CUA_DRIVER_RS_COORDINATE_SPACE env seed
Default coordinate_space=pixels => zero behavior change for existing
pixel clients. Set CUA_DRIVER_RS_COORDINATE_SPACE=normalized_1000 to
enable. Excludes rust/target build output.
* feat(cua-driver): make normalized coordinate scale configurable
Add CUA_DRIVER_RS_COORDINATE_SCALE (default 1000) so the normalization
full-scale can absorb the Qwen 999-vs-1000 cookbook ambiguity without a
recompile. norm_to_px/px_to_norm now take an explicit scale; denormalize_args
reads the process-wide COORDINATE_SCALE seeded once at startup from env.
* ci(cua-driver): add cross-platform release workflow for vendored driver
Standalone GitHub Action that builds, signs, and releases the vendored
cua-driver under packages/cua-driver. Adapted from upstream trycua/cua
cd-rust-cua-driver.yml:
- macOS: universal binary (lipo arm64+x86_64), codesigned + notarized into
CuaDriver.app using qwen-code's existing secrets (MAC_CSC_LINK cert +
App Store Connect API key notarization); Developer ID identity is
auto-discovered from the imported cert.
- Linux: x86_64 + arm64, built in debian:11 for a glibc 2.31 floor.
- Windows: x86_64 + arm64, unsigned (no EV cert, matches upstream).
- Release: softprops/action-gh-release on cua-driver-rs-v* tags or manual
dispatch, prerelease.
Triggered by tag push (cua-driver-rs-v*) or workflow_dispatch.
* chore(cua-driver): rebrand vendored driver as qwen-cua-driver
Rename the vendored trycua/cua driver so the fork installs and runs
independently of any upstream trycua install:
- binary cua-driver -> qwen-cua-driver
- bundle CuaDriver.app -> QwenCuaDriver.app
- bundle id com.trycua.driver -> com.qwencode.cua-driver
Updates the cargo/uia manifests, Info.plist, bundle/proxy launch paths,
permission/health-report wording, the install/build scripts, and the
cross-platform release workflow.
* feat(cua-driver): finish relative-coordinate mode — toggle, scale, zoom/move_cursor
- CUA_DRIVER_RS_COORDINATE_SPACE is now a 1/0 toggle (via is_env_truthy);
default off keeps pixel mode byte-identical to upstream.
- Thread CUA_DRIVER_RS_COORDINATE_SCALE through every coordinate surface
(was hardcoded 1000): input denormalization already used it; now the
rewritten screenshot dims, the tool/param descriptions, and the agent
instructions track the configured scale too.
- Normalize zoom (window basis) and move_cursor (screen basis) inputs and
rewrite their descriptions, alongside click/double_click/right_click/drag.
- Fix zoom on downscaled (Retina) windows: apply the get_window_state resize
ratio so the crop lands on the region the agent saw. Normalized mode only;
pixel-mode zoom unchanged.
All coordinate behavior stays gated on the normalized flag, so the default
(pixels) path is unchanged from upstream.
* chore(cua-driver): add upstream-sync script (git subtree unusable here)
`git subtree split --prefix=libs/cua-driver` hangs on a commit deep in
trycua/cua's history, so the subtree add/pull workflow isn't usable for
the vendored driver (and a pull would re-split + re-hang every time).
Add scripts/sync-from-upstream.sh instead: it git-diffs two upstream refs
(never walks the full history, so it dodges the hang), reprefixes the
libs/cua-driver delta to packages/cua-driver, and `git apply --reject`s it
on top of our local changes — conflicts land as *.rej for manual fixup.
Record the vendored version in .vendored-from and document the migration +
sync method in the design doc.
* chore(cua-driver): exclude vendored driver from qwen-code ESLint
The vendored packages/cua-driver tree carries upstream JS (e.g. the
test-harness Electron app) that doesn't follow qwen-code's lint rules and
fails CI. It is not a workspace package (no package.json) and is not
qwen-code TypeScript, so add it to eslint.config.js global ignores —
alongside packages/desktop/** — the standard treatment for vendored code.
* fix(cua-driver): let start_session revive an idle-reaped session
Ports the fix from upstream trycua/cua#2035 into the vendored driver.
When a session is reaped for idleness, a subsequent start_session with the
same id failed instead of resuming it. Revive the ended session in place so
the agent can continue rather than getting a hard error.
* fix(cua-driver): retry daemon socket writes on EAGAIN
Ports the fix from upstream trycua/cua#2036 into the vendored driver.
A non-blocking daemon socket can return EAGAIN/EWOULDBLOCK mid-write when the
peer's receive buffer is momentarily full. The driver treated that as fatal
and dropped the connection. Add a bounded retry/poll loop (mirror of the
read-side socket_io helper) so transient back-pressure no longer kills the
session; only a real timeout or hard error fails the write.
* fix(cua-driver/linux): stop reporting bare "Clicked" for X11 synthetic clicks
Ports the fix from upstream trycua/cua#2025 into the vendored driver.
On X11, clicks are delivered via XSendEvent synthetic events, which many
toolkits (GTK/SDL/Allegro) ignore because send_event is set. The driver still
reported a flat success ("Clicked"), masking that nothing happened. Report
the synthetic-delivery caveat honestly so the agent can fall back instead of
assuming the click landed.
(platform-linux crate is not built on macOS; verified by clean upstream apply
and covered by upstream + release-workflow Linux CI.)
* fix(cua-driver/windows): list empty-/null-title top-level windows
Ports the fix from upstream trycua/cua#2021 into the vendored driver.
list_windows filtered out any top-level window whose title was empty or null,
so legitimate targets (splash screens, some Electron/game windows, tool
windows) were invisible to the agent and unclickable. Include empty-title
windows, using class name / process as a fallback label.
(platform-windows crate is not built on macOS; verified by clean upstream
apply and covered by upstream + release-workflow Windows CI.)
* chore(cua-driver): track cherry-picked upstream PRs; fix vendored-from
The vendored copy is actually at cua-driver-rs-v0.6.7 (workspace version and
all 0.6.7->0.6.8 delta files confirm it), but .vendored-from had drifted to
0.6.8 during an earlier sync-script trial whose code delta was not kept. Left
as-is it would make a future sync diff 0.6.8->newer and silently skip the real
0.6.7->0.6.8 fixes. Correct it back to 0.6.7.
Also record the four not-yet-merged upstream PRs we carry as cherry-picks
(trycua/cua#2021/#2025/#2035/#2036) in .vendored-patches.md, and have
sync-from-upstream.sh point at it so the next sync reconciles them.
* ci(cua-driver): satisfy repo yamllint on the release workflow
The vendored-driver release workflow tripped 114 quoted-strings violations
under the repo's .yamllint (quote-type: single, required). Single-quote all
string scalars to match every other workflow in .github/workflows.
While reformatting, the release-notes body also got its paragraph blank lines
collapsed and still referenced the old CUA_DRIVER_RS_COORDINATE_SPACE=
normalized_1000 value — restore the blank lines and update it to the current
0/1 toggle (default 0 = off; optional CUA_DRIVER_RS_COORDINATE_SCALE=1000).
* chore(cua-driver): sync vendored driver to cua-driver-rs-v0.6.8
First real run of scripts/sync-from-upstream.sh: it 3-way-applied the upstream
0.6.7->0.6.8 delta onto our local fork. 10/12 files applied cleanly; the 2
rejects (install.ps1, _install-rust.sh) were already-applied baked-version
bumps (0.6.6->0.6.7, our copies were already at 0.6.7), i.e. no real conflict.
0.6.8 brings: Wayland input path (platform-linux), linux health_report +
overlay tweaks, a platform-macos build.rs step, and dependency bumps. Version
moved to 0.6.8 across the workspace.
Verified our work survived the sync untouched: the relative-coordinate shim
(coord_norm/protocol) and all four cherry-picked PRs (socket_io/session +
linux/windows) are intact — in particular the 0.6.8 edit to platform-linux
tools/impl_.rs landed alongside our #2025 change with no collision. macOS
cargo check + 132 core tests green. (platform-linux/windows + the binary
integration test build only on their own runners; upstream CI covers those.)
* ci(cua-driver): add a dry_run gate to the release workflow
Mirror the desktop-release / release dry-run pattern: a workflow_dispatch
dry_run boolean input (default true). The cross-platform build + package jobs
always run and upload their artifacts; the GitHub Release job now publishes
only on a tag push or an explicit dry_run=false dispatch.
Lets us rehearse the whole build/package pipeline (dry_run=true, notarize=false)
and inspect the produced artifacts without cutting a release. A branch push
(no tag, not a dispatch) likewise builds without releasing.
Both the triage job and the PR-review job run the qwen agent directly on
the persistent self-hosted ECS pool with no per-run isolation. $HOME,
/tmp and the workspace are reused between runs, so a prior run's agent
session/memory (default ~/.qwen) or leftover draft comments
(/tmp/stage-*.md, which survive git clean) can bleed into the next run.
This surfaced on #5874: its triage posted #5872's review verbatim
(wrong author, wrong approver, wrong diff), while the same run's internal
stage actually exercised #5874 — the PR id was correct, the agent state
was stale.
Point QWEN_HOME at a per-run $RUNNER_TEMP/qwen-home on the Qwen step and
reset it (plus /tmp/stage-*.md) in the pre-run cleanup, for both jobs.
QWEN_HOME relocates the entire global qwen dir (storage.ts), so this
isolates sessions/memory/temp without touching $HOME and disturbing
git/npm. The tmux-testing job is already container-isolated and unchanged.
Refs #5882
* ci(qwen-resolve): support fork PRs and slim /resolve to conflict-only
Closes the fork gap #5779/#5862 left for the maintainer /resolve command, so it
can clear merge conflicts on community (fork) PRs, and narrows the command to
exactly one job: resolve the conflict and push it back.
- Fork PRs: fetch the head via refs/pull/N/head and push the resolved branch
back to the PR's head repository (via Allow edits by maintainers) instead of
bailing as unsupported. Validated end-to-end against a fork PR.
- Conflict-only: drop the build/typecheck/lint/test gate and npm install/refresh;
keep the structural checks (markers, index, merge-tree, default-merge, scope).
Test fallout is left to the PR's own CI and follow-up tasks.
- Push-failure classification: workflow_scope (the merge carries the base's
.github/workflows/** changes, which a token without the workflow scope cannot
push), permission (403 / 404), and moved (stale force-with-lease) each get an
actionable comment; the redacted git stderr is logged for diagnosis.
NOTE: the push bot's PAT (CI_DEV_BOT_PAT) needs the `workflow` scope, since
resolving merges the base in and that update touches workflow files.
Guard tests updated; 12/12 pass.
* ci(qwen-resolve): avoid PR head ref collisions
* ci(qwen-resolve): address review comments
- workflow_scope: anchor classification on GitHub's server phrase
'refusing to allow ... workflow' instead of a loose workflow.*scope, which
the attacker-controlled branch name in git's rejected-ref echo could trip.
- prepare: bail when the head repository was deleted (null headRepository →
malformed push URL).
- moved: include the run-artifact link, consistent with the other cases.
- permission: drop 'could not read' (matched transient network errors).
The conflict-resolution agent step runs with `sandbox: true`, which on
Linux needs docker or podman to launch the sandbox. The self-hosted ECS
pool ships no container runtime, so the job died at the agent step
(exit 44, "failed to determine command for sandbox") before it could
resolve anything — see run 28158417390.
Pin the job to ubuntu-latest, which ships docker and is ephemeral (a
good fit for running the untrusted PR's build/lint/test in the
verification gate). Update the cleanup and PAT-scrub comments that
assumed a reused self-hosted workspace; both steps stay as defensive
no-ops on hosted runners.
After #5842, CodeQL and the E2E suite were the only things running on every push to `main` — the per-commit, post-merge backstop. With merges landing back-to-back, those runs (CodeQL ~30min, three E2E jobs ~25/36/36min, the latter never cancel-superseded) stacked on the scarce hosted Linux pool and were a main driver of the recent runner-saturation incident.
- CodeQL moves to its own scheduled codeql.yml (nightly + workflow_dispatch). It was never a required check and findings still surface in the Security tab, so per-commit scanning bought little. ci.yml loses its now-empty `push` trigger as a result (every remaining job is gated to pull_request / merge_group).
- E2E gets event-scoped concurrency so back-to-back pushes to `main` cancel superseded runs (only the latest tree matters and it covers every merged change), plus a nightly full regression as the guaranteed signal in case a busy merge window keeps cancelling the push run. Manual workflow_dispatch added.
The merge_group gate (ubuntu + integration + mac + win) still validates every PR before it lands; this only changes the non-gating post-merge work.
Follow-up (separate PR): alert on E2E/CodeQL failure (a deduped ci-failure issue) now that they run unattended.
* ci: route the merge queue's Linux jobs onto ECS
The merge queue runs in the base-repo context but classify_pr was PR-only, so its ubuntu_runner output was empty in the queue and the Ubuntu Test + Integration jobs fell back to the shared hosted pool — piling onto the scarce hosted Linux runners exactly when the queue is busiest. Run classify_pr on merge_group too and route both the Ubuntu gate and Integration onto the same in-repo ECS pool as PRs; the MAINTAINER_ECS_RUNNER_DISABLED kill-switch and the hosted fallback are intact, and fork PRs are unaffected.
Also extend the checkout-verification guard to the merge queue: now that the queue's Ubuntu checkout runs on ECS behind the squid egress proxy, a stale-ref checkout could silently test the wrong tree, and a wrong-tree pass in the queue would merge bad code. One sub-second merge-base check.
Routing approach carried forward from #5853 by @wenshao (closed); this adds the merge-queue checkout guard on top.
* ci: fix verify-step wiring test and guard integration_cli on ECS
The merge-queue ECS routing renamed the test job's checkout guard to "Verify
checkout includes expected head commit", but no-ak-integration-ci.test.js still
asserted the old "Verify PR checkout includes head commit" name, failing the
wiring test. Update the assertion.
integration_cli now also routes to ECS (via classify_pr on merge_group) yet
lacked the protections the Ubuntu gate has. Mirror them: fetch-depth 1 (nothing
walks history; a full clone is the heaviest transfer on the ECS runner) and the
same stale-checkout guard, keyed on merge_group.head_sha.
* ci: mirror the Node 22 version probe into integration_cli
The self-hosted Node step claimed to mirror the Ubuntu gate but omitted the
major-version probe, so a non-22 Node on the ECS runner would run integration
tests with no log signal. Add the same warning-only check.
The `triage` job was pinned to `ubuntu-latest`, so `/triage` (and the
pull_request_target / issues triage paths) competed for the GitHub-hosted
runner concurrency cap. That cap is routinely saturated by the things that
*must* run hosted — the CI macOS/Windows matrix, e2e — so triage runs sit in
the queue for many minutes while the 31-runner self-hosted ECS pool sits almost
entirely idle. Comment-triggered triage never reached ECS because its routing
keyed on `github.event.pull_request`, which is null on `issue_comment` events.
Triage is a read-only analysis agent with the same security profile as the
`review-pr` job in qwen-code-pr-review.yml, which already runs on ECS: it checks
out the trusted base repo, never executes PR code, and reaches the PR/issue
only through the API. Route it to the ECS pool with the same
`MAINTAINER_ECS_RUNNER_DISABLED` fallback to `ubuntu-latest`.
The upstream `authorize` job intentionally stays on a hosted runner: it is
secret-bearing (CI_BOT_PAT) and runs before the permission gate, so it must
remain ephemeral. Only the gated, post-authorize `triage` job moves.
Add a defensive stale-worktree cleanup before checkout, mirroring `review-pr`:
self-hosted runners reuse the workspace, and the triage agent has
enter_worktree/exit_worktree, so an interrupted run could otherwise leave a
`.qwen/tmp/*` worktree that trips the next checkout. The step is a no-op on a
fresh hosted runner.
* ci: add qwen fix conflicts command
* ci: rename conflict command to resolve
* ci: handle resolve workflow failures
* ci: simplify resolve workflow reporting
* ci: fold resolve command into PR workflow
* ci: merge resolve command into PR workflow
* ci: keep PR review workflow name
* ci: reuse PR command authorization for resolve
* ci: narrow PR command authorization trigger
* ci(resolve): fix command injection, secret exposure, and edit-scope
Addresses the remaining /resolve review findings:
- Command injection (RCE): branch refs were inlined as ${{ }} into the
verify / show-artifacts run blocks; a branch named with `$(...)` or
backticks would execute on the runner that later holds CI_DEV_BOT_PAT.
Pass refs via env: and reference only "$BASE_REF" / "$HEAD_REF".
- Secret exposure: the agent step (with OPENAI_API_KEY) could run
PR-authored npm build/lint/test and exfiltrate the key. Drop
build/lint/typecheck/vitest from the agent coreTools (the credential-free
verification gate re-runs them) and install with `npm ci --ignore-scripts`
plus explicit patch-package so PR lifecycle scripts do not run.
- Edit scope: the verification gate now fails when the agent changed any
file the base branch did not, so a prompt-injected agent cannot smuggle
edits outside the conflict set. Prompt tightened to match.
Also test only real workspaces (guard packages/channels/<name> against the
non-workspace packages/channels path).
* ci(resolve): address review feedback on /resolve command
- test: import vitest globals so the lint gate stops reporting no-undef
in scripts/tests (this was the real CI-blocking lint failure)
- prepare: trap an unwritten decision and fail closed to "failed" so an
early gh/git error still reports back instead of a silent red run
- report: make the post-push result comment best-effort so a failed
comment POST can't leave the branch force-pushed with no explanation
- refresh: npm install instead of npm ci to tolerate lockfile drift
after a package.json conflict resolution
- verify: scan only resolution-touched files for leftover conflict
markers instead of git diff --check over the whole merged range, which
spuriously failed on pre-existing base whitespace
- prompt/artifact: use ${{ env.WORKDIR }} instead of hardcoded
/tmp/qwen-resolve
- authorize: drop the issue.state==open gate that also silenced /review
on closed PRs (resolve-pr keeps its own open guard)
- coreTools: document that the specifiers are advisory, not a security
boundary (sandbox + same-repo + no agent token are)
- test: assert the resolve-pr authorization/scope guards, that the agent
step carries no GitHub token, and the dry-run/workflow_dispatch paths
* ci(resolve): fail the verify gate when post-merge package tests fail
The build/typecheck/lint checks use `if ! cmd; then echo outcome=failed;
exit 1; fi`, but the per-package test loop ran `npm run test` bare under
`set -euo pipefail`. A test failure aborted the step before `outcome=fixed`
was written, leaving OUTCOME empty so the report posted a generic "did not
complete successfully" with no mention of which package failed. Mirror the
other gates: record the failing package and set outcome=failed.
* ci(resolve): avoid splitting multi-byte chars when truncating report bodies
`head -c 2000` cuts at a byte boundary, which can split a multi-byte UTF-8
character and leave a broken tail in the posted comment. Pipe through
`iconv -f UTF-8 -t UTF-8 -c` to drop the incomplete trailing sequence.
* ci(resolve): report agent infrastructure failures distinctly in the verify gate
The verify gate runs under always(), so when the resolve_conflicts agent step
fails at the infrastructure level (API timeout, model quota, action crash) it
still executed with an unmodified branch — the merge-tree check then misreported
"branch still has merge conflicts". Short-circuit on a non-success agent
outcome and name the real cause instead.
* ci(resolve): trim verbose review-fix comments to house length
* ci(resolve): least-privilege GITHUB_TOKEN for resolve-pr job
Drop the resolve-pr job permissions to contents:read only and route every
write through an explicit PAT, so the implicit GITHUB_TOKEN that PR-controlled
build/lint/test see in this job carries no write scopes:
- job permissions: contents:read only (was +issues/pull-requests:write)
- Acknowledge reaction: use CI_DEV_BOT_PAT instead of GITHUB_TOKEN
- Verification gate: set GITHUB_TOKEN='' for the PR-controlled step
Defense-in-depth: persist-credentials:false already keeps the checkout token
off disk, so this is least-privilege hardening, not a reachable-leak fix.
* ci(resolve): fix skip-report gating, route to ECS with cleanup, harden tests
- Report skipped request: add always() so the prepare-step EXIT trap's
decision=failed is reported instead of skipped on a crash.
- Report skipped request: make gh pr comment best-effort with a ::warning
fallback, matching the Report result step.
- Route resolve-pr to the self-hosted ECS pool (matching review-pr) and add a
cleanup step that wipes stale ${WORKDIR} artifacts; these runners reuse
workspace and /tmp and the verification gate builds untrusted PR code.
- Tests: pin the four verification-gate failure strings against regression.
* test(resolve): pin verify-gate failure checks and skip-report always() gate
Address review feedback: the verification-gate test now also pins the
agent-infrastructure-failure, missing-address-summary, and unresolved-index
guards; and the failure-paths test asserts the Report-skipped-request step keeps
its always() gate so an EXIT-trap decision=failed actually reports on a prepare
crash. The best-effort skip-comment suggestion was already addressed in
03795302a.
* ci(resolve): ECS kill-switch fallback, safe HTML strip, prompt hardening, test pins
Address review feedback on resolve-pr:
- runs-on honors MAINTAINER_ECS_RUNNER_DISABLED and falls back to a hosted
runner when ECS is toggled off, matching the review path (the verification
gate runs fine on an ephemeral hosted runner — better isolation, no cleanup).
- append_safe_file strips only active-content HTML elements instead of every
`<...>`, so TS generics (Map<string, number>) and JSX in the agent summary are
no longer garbled in the posted comment (GitHub sanitizes comment HTML anyway).
- the agent prompt reads the base branch name from context.md instead of
inlining ${{ base_ref }}, keeping a branch name out of the LLM prompt text.
- document that the scope guard's file-level granularity is intentional
defense-in-depth (real containment = sandbox + same-repo + no agent token).
- test: pin persist-credentials:false, GITHUB_TOKEN:'', sandbox, and the
--ignore-scripts install guards so a future edit can't silently drop them.
* ci(resolve): harden /resolve workflow against review findings
- prepare: arm the fail-closed EXIT trap before mkdir; reject CR/LF in
write_output so an attacker-set PR title can't inject GITHUB_OUTPUT keys
- report: scrub the bot PAT from .git/config after push (self-hosted runner)
- verify: surface a failed dependency refresh instead of a stale build error
- scope guard: use -z/sort -zu like the conflict-marker check
- prompt: require a Conventional Commit so the default merge message passes
- tests: bound the resolve-pr slice; assert SHA-pinned lease + no-cancel guard
---------
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: yiliang114 <effortyiliang@gmail.com>
* ci(release): drop --delete-branch from auto-merge (incompatible with merge queue)
main now has a merge queue, and gh pr merge rejects --delete-branch when one is enabled, failing the release workflow's final step. The queue owns the merge, so head-branch deletion is governed by the repo's auto-delete setting instead.
* ci(release): drop --delete-branch from remaining auto-merge workflows
release-sdk, release-sdk-python and desktop-release all run
'gh pr merge --auto --delete-branch' against main, which now has a merge
queue. gh rejects --delete-branch when a queue is enabled, so each would
fail at the auto-merge step on its next real release. Mirror the
release.yml fix: drop the flag and let the repo's 'Automatically delete
head branches' setting govern cleanup.
* ci(release): label release PRs skip-changelog to keep them out of notes
Every release workflow opens a 'chore(release): …' PR against main (version
bump + changelog sync). Those PRs merge before the next release, so GitHub's
--generate-notes picks them up as line items in the next release's notes.
.github/release.yml already excludes the 'skip-changelog' label, but the
release PRs were never tagged with it. Add --label 'skip-changelog' to each
'gh pr create' so the automated release PRs drop out of generated notes.
* ci(release): use --squash for Python SDK auto-merge
main's ruleset is squash-only; --merge would fail at the merge-queue
stage. Aligns release-sdk-python.yml with the other three release
workflows.
---------
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Now that the merge queue is live, each job should run in exactly one place instead of doubling up.
- CodeQL and E2E leave the merge queue (CodeQL gains a merge_group guard, E2E loses its merge_group trigger). Neither is a required check, so neither gated a merge — CodeQL just ran ~30min and got cancelled at merge every time, and the three E2E jobs burned ~25/36/36min of hosted time, for nothing. They keep running post-merge on push to main.
- macOS/Windows move to merge_group only (they were also running on push to main), and the ubuntu Test job stops running on push too. The queue already tests the merged tree, so re-running these gate jobs on the post-merge push is redundant.
Net: PR runs ubuntu; the merge queue runs ubuntu + integration + mac + win (the gate); push to main runs CodeQL + E2E (the backstop). No job runs in two of those.
* ci: stop running push CI on release/** branches
release.yml pushes version + changelog commits to the release branch.
With 'release/**' under the push trigger, that fired a full push-event CI
run (mac/win/CodeQL all run since event != pull_request) on the release
PR's head, blocking it from main's merge queue and double-running the
matrix. Nothing gates on it: release branches have no protection and
publish happens inside release.yml. Keep 'release/**' under pull_request
for backport PRs.
* ci: split platform test matrix into named jobs to unblock merge queue
The macOS/Windows tests are required status checks that only run in the
merge queue (if: event_name != 'pull_request'). As a single matrix job, a
skipped run collapses to one check named 'Test (${{ matrix.os }}, Node
${{ matrix.node-version }})' — so the required contexts 'Test
(macos-latest, Node 22.x)' and 'Test (windows-latest, Node 22.x)' are never
reported on the PR head and sit 'Expected' forever, leaving every PR BLOCKED
and unable to enter the queue (observed on #5830/#5832).
Split into two named jobs. A skipped named job reports under its exact name
(conclusion: skipped), which satisfies the required check on the PR head —
exactly how the merge-queue-only Integration Tests job already behaves — so
PRs can enter the queue, where these jobs run for real and gate the merge.
No PR-stage runner cost (skipped jobs spin no runner); no ruleset change.
* test(ci): update no-AK wiring test for split platform jobs
The test looked up the 'test_platforms' matrix job, which this branch
split into named 'test_macos' / 'test_windows' jobs. Assert the same
properties (no no-AK script, immutable PR-head checkout) on both new jobs.
These three jobs reran on every PR push but rarely caught push-to-push
regressions: macOS/Windows are the slowest, costliest runners and platform
breakage is rare per iteration, and CodeQL findings seldom change between
pushes. Gate all three off the pull_request event so they no longer sit on
every PR's critical path. They still run on push to main (and in the merge
queue once enabled), so platform and security regressions are still caught
before release. The only required status check, Test (ubuntu-latest, Node
22.x), is unaffected and keeps running on PRs.
Two independent reliability fixes on the Linux CI path:
- test (ubuntu, ECS-routed): fetch-depth 0 -> 1. Nothing in the job walks
git history (the verify guard checks head.sha == HEAD; schema/tests only
touch the working tree), so the full-history clone is dead weight. On the
self-hosted ECS runner it is the heaviest transfer and chokes the squid
egress proxy, flaking checkout with "Operation too slow" / connect
timeouts. depth 1 is enough; coverage diffing runs in a separate hosted
job that checks out on its own.
- codeql: add timeout-minutes: 30. Analysis normally finishes in ~7-9 min
(22 min worst case observed). Without a cap, a hosted runner that drops
its heartbeat mid-analysis leaves the job in_progress for the default 6h,
holding a scarce hosted Linux slot the whole time and starving the queue.
* ci: collapse pr checks into ubuntu gate
* ci: keep platform PR tests in matrix
* ci: update no-ak gate wiring test
* ci: bypass stale self-hosted git cache
* ci: refresh self-hosted checkout cache
* ci: refresh pr refs after cached checkout
* ci: fetch fresh pr refs via github url
* ci: retry stale pr merge checkout
* ci: fetch stale pr merge ref directly
* test: isolate qwen serve streaming home
* test: keep qwen serve fake server off proxy
* ci: check out PR head ref to avoid merge-ref lag
The Ubuntu gate kept failing on the self-hosted runner: the PR checkout
used github.ref (refs/pull/N/merge), which GitHub rebuilds asynchronously
and can serve stale for minutes after a push, so the verify guard saw a
tree without the PR head. The retry/refresh machinery added to work
around this could not help — its direct GitHub fetch fallback times out
on the self-hosted squid proxy.
Check out refs/pull/N/head instead (immutable, published the instant the
branch is pushed) for pull_request events in both the test and
test_platforms jobs, and drop the 6-step retry/verify/refresh block. A
single sanity guard stays to fail loud if the head is missing. Non-PR
events keep github.ref; the merge queue validates the merged result.
* ci: check out PR head SHA to avoid missing merge-ref checkout failures
* ci: skip coverage comment on cancelled runs to free concurrency slot
post_coverage_comment used if: always(), so on a cancelled run it still ran
on the hosted pool. During rapid pushes the cancelled run's coverage job sat
queued for a hosted runner, keeping the run non-terminal and holding the
branch concurrency slot — so the next push's required Test (ubuntu-latest)
check waited behind it (observed ~9 min, with thrash). Switch to !cancelled():
coverage still posts on test failure, but a cancelled run releases the slot
immediately. Its coverage would be stale anyway.
* ci(triage): route /triage comment runs to the ECS pool
`/triage` comments trigger the authorize+triage jobs on hosted runners,
which queue behind CI during peak hours (~2 min vs 4 s off-peak observed).
Route the comment-triggered triage job to the ECS self-hosted pool, which
is not bound by the GitHub-hosted concurrency limit.
Safe scope: only `issue_comment` events; the job is read-only and checks
out the base ref (never PR-head code). Gated on the same
MAINTAINER_ECS_RUNNER_DISABLED kill-switch as `authorize`, so a disabled
ECS pool falls back to hosted. PR/issues/dispatch triggers stay hosted.
* ci: fetch PR head by ref name to fix 'not our ref' checkout failure
* ci: align Test checkout ref with Lint (add branch_ref fallback)
On workflow_dispatch the Test job runs (its if: !cancelled() overrides the
implicit success() gate when classify_pr is skipped), so its checkout must
honor the branch_ref input like Lint does. Without it, dispatch runs validated
github.ref in Test while Lint validated the user-supplied branch_ref.
* ci: retry merge-ref checkout instead of pinning PR head; drop /triage ECS routing
Per review feedback: checking out refs/pull/N/head dropped merge-result
validation, and merge queue isn't enabled on main to backstop it. Keep merge-ref
semantics (github.ref) for the required Lint/Test checkouts and retry once to
absorb the transient merge-ref lag the PR-head change was working around.
Also revert /triage routing to ECS: that job runs an agent (sandbox:false,
shell/write tools, repo tokens) over untrusted PR/issue text, so it stays on
ephemeral hosted runners, not the persistent self-hosted pool.
* ci: back off before the merge-ref checkout retry
The merge ref (refs/pull/N/merge) is built asynchronously, so a back-to-back
retry can hit the still-unbuilt ref again. Sleep 10s before retrying so the
retry absorbs the multi-second build lag, not just instantaneous blips.
* ci: harden self-hosted runner routing per #5620 review
Follow-up to #5620 addressing the review findings:
- authorize (pr-review + triage): add the same-repo guard so fork-triggered runs keep CI_BOT_PAT on ephemeral hosted runners; only in-repo PR events use the persistent ECS runner.
- delay-automatic-review: route back to hosted — its 30-minute environment wait timer would otherwise hold a self-hosted ECS slot idle for the whole wait (GitHub allocates the runner before the timer).
- Lint (self-hosted): warn when Node major != 22 (parity with Test) and use 'npm ci --prefer-offline --no-audit --progress=false' for proxy resilience (parity with Test).
Not included: keeping classify_pr on hosted (the SPOF concern) — deliberately left on ECS; the MAINTAINER_ECS_RUNNER_DISABLED kill switch reverts it within ~1 min if ECS is down.
* ci: guard github.event.pull_request before deref in authorize runner selection
Addresses Copilot's review note on #5644. GitHub Actions expression property access is already null-safe (a missing github.event.pull_request yields null, not an evaluation error), so non-PR events (issue_comment/workflow_dispatch) already fell back to hosted. This makes the fallback explicit and silences the recurring reviewer concern; no behavior change.
The macos-13 (Intel) prebuild job routinely sits in the GitHub-hosted
runner queue for 20+ min while every other platform finishes in under a
minute, stalling the audio prebuild collection and the Release run.
macos-13 is the only Intel x64 macOS image and its runner pool is scarce
and on GitHub's deprecation path.
Drop it and cross-compile the x64 slice on the macos-14 arm64 runner via
`prebuildify --arch x64`; binding.gyp links only universal frameworks so
clang -arch handles it. Verified locally on Apple Silicon: the produced
darwin-x64 .node is Mach-O x86_64.
Downstream is unaffected — create-standalone-package.js gates on the
prebuilds/darwin-x64/*.node file, and collect merges via the prebuilds-*
glob.
Closes#5642
Co-authored-by: Qwen-Coder <noreply@qwen.ai>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
* ci(release): dispatch autofix for release failures
* ci(release): harden autofix dispatch
* ci(release): tighten autofix review safeguards
* ci(release): harden release autofix safeguards
* ci(release): keep autofix dispatch resilient
* ci(release): keep dispatch failure comments best-effort
* ci(release): keep autofix trigger scoped
* ci(release): queue release failures for autofix
* ci(release): avoid event-triggered autofix
* ci(release): dispatch autofix after failure issue
* fix(ci): filter unattended autofix candidates
* ci(release): deduplicate failure issues
* ci(release): respect autofix exclusion labels
* ci(release): harden autofix dispatch
* ci(autofix): drop unused comment fetch in ready-for-agent scan
Tier-1 scans type/bug + status/ready-for-agent and takes candidates
directly without the unattended (human-engagement) filter, so the
`comments` field was fetched and immediately discarded. Drop it from the
issue-list `--json` and the now-redundant `del(.comments)`. Tier-2 still
fetches comments for its own filter.
* ci(release): harden release-failure issue dedup
- Anchor reuse on the exact "Release Failed for <tag> on " title prefix
so a tag that is a prefix of another (v0.18.1 vs v0.18.10) can't reuse
the wrong release's issue (in:title is a fuzzy full-text search).
- Prefer a github-actions[bot]-owned match so a same-titled human/foreign
issue sorting first can't make us skip an existing bot issue and open
duplicates.
- Emit a :⚠️: when an existing issue carries an autofix exclusion
label so the skipped dispatch is visible instead of a silent green job.
* ci: route in-repo PRs' Linux test to self-hosted runner
Send the required Linux Test job to the self-hosted ECS runner for PRs whose head branch lives in this repo (which implies the author had write access), while forks and push/merge_group runs stay on GitHub-hosted runners. This lets maintainer changes skip the shared hosted Linux queue without ever running untrusted fork code on self-hosted infrastructure. The MAINTAINER_ECS_RUNNER_DISABLED repo variable forces everything back to hosted if the ECS runner is unavailable.
* ci: reuse pre-installed Node on self-hosted runners, route Lint to ECS
The self-hosted ECS runners cannot reliably download the Node tarball from nodejs.org through the egress proxy (actions/setup-node aborts mid-download), which failed the Test job routed there. On self-hosted runners, reuse the machine's pre-installed Node instead of re-downloading it every run; hosted runners keep using actions/setup-node. Also route the Lint job to ECS for in-repo PRs — scripts/lint.js already skips installing actionlint/shellcheck/yamllint when they are on PATH, so no lint.js change is needed.
* ci: trim verbose runner-routing comments
* ci: run classify_pr gate on ECS for in-repo PRs
* ci: quote classify_pr/lint runs-on expressions
* ci: keep Lint on hosted runners (ECS proxy truncates linter downloads)
* ci: route Lint and review/triage gate jobs to self-hosted runner
Lint goes back to ECS (linters are now pre-installed on the runners, so scripts/lint.js skips downloading them). The pr-review gate jobs (authorize, review-config, delay-automatic-review, ack-review-request) and the triage authorize gate also move to ECS so the ECS heavy jobs (review-pr, tmux-testing) are no longer bottlenecked by the shared hosted queue. All respect the MAINTAINER_ECS_RUNNER_DISABLED kill-switch.
Standalone archives shipped only curated dist/ entries, so the esbuild-external
@qwen-code/audio-capture addon couldn't be resolved at runtime — streaming voice
was unavailable in standalone installs (batch only, and only with SoX on PATH).
create-standalone-package.js now bundles the addon into lib/node_modules (where
the bundled lib/cli.js resolves bare specifiers): the trimmed package.json
(install hook removed; type/exports kept for ESM resolution) + dist + only this
target's prebuild (win-x64 -> win32-x64) + its zero-dep runtime dependency
node-gyp-build. Targets without a matching prebuild (e.g. local builds) ship
without it and degrade to SoX/arecord as before (warns, doesn't fail). The
release pipeline already downloads prebuilds before packaging.
Refs: #5502, #5590.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>