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>
* feat(voice): voice dictation with native capture, streaming, and biasing
Add voice dictation for the prompt input:
- /voice [hold|tap|off|status] command + general.voice.{enabled,mode,language,protocol} settings; push-to-talk via Space, /model --voice to pick the model
- Native microphone capture (@qwen-code/audio-capture, miniaudio N-API) with arecord/SoX fallback, silence auto-stop, cold-start warm-up, and macOS permission query
- Batch transcription via DashScope Qwen-ASR (OpenAI-compatible chat/completions + input_audio) with language + keyterm biasing and an echo guard
- Live streaming over the DashScope realtime WebSocket (fun-asr-realtime / paraformer-realtime-v2) with interim text and an input-level waveform, behind voice.protocol=dashscope-realtime
- Rich VoiceIndicator UI (state, level meter, live partial transcript)
- Cross-platform prebuilds via prebuildify + node-gyp-build and a CI matrix
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* feat(cli): route voice ASR by model
* feat(cli): polish voice realtime parity
* ci: fix voice workflow checks
* fix(cli): address voice review blockers
* fix(cli): harden voice transcription failures
* fix(cli): address voice PR review blockers
* fix(cli): harden voice review follow-ups
* fix(voice): handle realtime review blockers
* fix(cli): add voice command i18n keys
* fix(cli): address voice review blockers
* fix(cli): address voice review follow-ups
* fix(voice): harden review edge cases
* fix(voice): address release and realtime review blockers
* fix(voice): address realtime suggestion followups
* fix(voice): handle realtime review followups
* fix(voice): address realtime review blockers
* fix(voice): address review feedback
* fix(voice): address recorder review feedback
* docs(voice): document ssrf guard boundary
* fix(voice): address review feedback
* fix(voice): preserve warm recorder session safety
* fix(voice): address stream review suggestions
* fix(voice): address multi-round review findings
Realtime/streaming:
- Salvage an already-committed transcript when the WebSocket closes right
after finish() instead of rejecting the whole dictation
(qwenAsrRealtimeSession, voiceStreamSession) + regression tests.
useVoiceInput state machine:
- Single-shot finalize guard so a tap-stop racing the silence auto-stop can't
double-stop the recorder and surface a spurious failure.
- Reset mountedRef on (re)mount so StrictMode (DEBUG) can't freeze the voice UI.
- Widen the hold-mode first-press release window above common key-repeat delays.
Model selection:
- Reject ids with no ASR transport at /model --voice and in the model dialog via
a new isSelectableVoiceModel; move resolveVoiceTransport into voiceModel so the
record-time config resolver stays transport-agnostic (+ tests).
macOS mic permission:
- Surface the not-determined state in voice warmup so the first dictation isn't
silently lost behind the TCC dialog.
Native packaging:
- Make the audio-capture native install non-fatal (falls back to SoX/arecord) so
a voice-only build failure can't break npm ci.
- Download audio-capture prebuilds before building standalone release archives.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(voice): address review blockers
* fix(voice): update batch recording audio level
* fix(voice): tap-mode transcript loss, stream leaks, and dead keyterm cleanup
- Tap-mode dictation submitted a stale empty buffer.text, wiping the just-
inserted transcript and sending nothing: thread the resulting prompt text
through onSubmit(text) instead of reading buffer.text back synchronously
after the async insert (useVoiceInput, InputPrompt).
- Streaming finalize leaked the WebSocket session when recorder.drain() threw:
abort the session before propagating the error.
- voiceStreamSession: reject the connect promise when 'task-finished' arrives
before 'task-started' instead of hanging forever in 'transcribing'.
- Remove dead keyterm enrichment (project/branch/recent-file paths were
unreachable after the privacy fix) and the now-inert CJK echo guard, plus the
tests that asserted that removed behavior; fix the misleading "OpenAI prompt
field" comment.
- Add the missing 'Voice Model' and macOS mic-permission i18n keys to
en/zh/zh-TW (they were falling back to English; check-i18n doesn't flag keys
absent from en).
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(voice): reject partial stream transcripts
* fix(cli): let dialogs consume voice keys first
* fix(voice): reject incomplete qwen realtime transcripts
* fix(voice): handle stream review blockers
* fix(voice): salvage qwen realtime transcript on close
* fix(voice): sanitize streamed transcript text
* test(audio): run audio capture tests in CI
* fix(voice): address review blockers
* fix(voice): report stream close while recording
* fix(voice): reduce keyterm echo false positives
* fix(voice): handle realtime close diagnostics
* fix(voice): address realtime review blockers
* fix(lint): allow legacy voice filenames
* chore(cli): rename voice files to kebab-case
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* feat(ci): add on-demand tmux real-user testing to triage workflow
Add a tmux-testing stage to qwen-triage.yml: a write-permission user runs
`@qwen-code /tmux` on a PR (or dispatches with tmux_pr=N) to launch the
changed app in a real tmux TUI session and exercise the affected flow,
catching interactive regressions that diff-based review can't.
- Gated on the PR AUTHOR having write permission (whose code runs), reusing
the authorize job; no separate fork check.
- Executes untrusted PR code with minimal blast radius: contents:read, no
GitHub token in the agent env, persist-credentials:false.
- Proxy bypass for the model call only.
- A separate publish-tmux job (clean runner, write PAT, no PR-code checkout)
posts the verdict back to the PR — keeping the write credential isolated
from the code execution.
* fix(ci): address tmux-testing review findings
- Gate the workflow_dispatch tmux_pr path on the PR author's write
permission via the authorize job, so dispatching never runs an
unauthorized contributor's code on the self-hosted runner.
- Make workflow_dispatch triage vs tmux mutually exclusive (tmux_pr
set skips the triage job), matching the documented input contract.
- Pin GITHUB_TOKEN/GH_TOKEN empty in the untrusted-code step so no
inherited repo-scoped credential reaches the agent.
- Serialize concurrent tmux runs per PR; skip PRs whose merge ref is
unavailable (CONFLICTING); clean the runner workspace on exit.
- publish-tmux now also reports infrastructure failures instead of
staying silent, and the comment fence widens past any backtick run
in untrusted output so it cannot break out and render markdown.
- Correct the proxy-bypass comment to describe actual behavior.
* refactor(ci): collapse tmux gating into one decision output
- Fold should_test + has_tui into a single tri-state `decision`
(skip | na | run); the five repeated step guards become
`decision == 'run'`, and the standalone `Mark not applicable`
step is gone (the n/a verdict is set inline in the resolve step).
- Resolve PR state + file list in one `gh pr view` call instead of
two.
No behavior change: skip stays silent, na posts n/a, run drives the app.
* fix(ci): wait out UNKNOWN mergeability before deciding
Right after a /tmux comment GitHub may not have computed PR
mergeability yet (mergeable=UNKNOWN), and refs/pull/N/merge is only
current once it has. Retry the resolve up to 5x (3s apart) until it
settles, so a freshly-pushed PR isn't checked out from a stale or
missing merge ref.
* fix(ci): harden tmux publish comment and mergeability/exit-code handling
- publish-tmux: render artifacts in HTML-escaped <pre> instead of a
backtick fence; removes the grep crash on backtick-free content under
set -euo pipefail and closes the </details>/fence-breakout injection
- tmux-testing: skip when mergeability stays UNKNOWN after retries
- classify exit 137/139 (OOM/segfault) as infra-error, not test fail
- upload-artifact: continue-on-error so a pre-write crash isn't masked
* ci: harden tmux testing workflow
* fix(ci): address tmux testing review follow-ups
* fix(ci): report tmux prepare failures
* fix(ci): use latest qwen CLI for tmux testing
* ci(release): report required Test checks on release PRs and auto-approve
Release-sync PRs skipped the matrix Test job at job level, so the required "Test (os, Node 22.x)" contexts were never reported and the PR could not merge. Move the skip to step level so the job still expands its matrix and concludes as a work-free pass. Also add a CI_DEV_BOT_PAT approval on the release PR to cover one of the two required reviews.
* ci: single-quote the test job if expression for yamllint
After dropping the inner 'true' comparison, double quotes are no longer required, so yamllint's quoted-strings rule wants single quotes.
* ci(e2e): stop running the E2E matrix on every PR push
#5211 added a pull_request trigger so capability/contract regressions
surface at PR time. In practice this fires the full E2E matrix (Linux
sandbox:none + docker + macOS) on every same-repo PR push, and the
scarce macOS hosted runners saturated the org's hosted pool — jobs
across CI / PR Review / Triage queued for a long time while the
self-hosted ecs runners sat idle.
Drop the pull_request trigger. E2E still runs on push to main and in
the merge queue (merge_group), so regressions are still caught in the
base-repo context (with secrets) right before merge — the same gating
model #5224 uses for CLI integration tests. Fork PRs were already
skipped (no secrets), so they lose nothing.
The concurrency block and fork guard are left in place; they are no-ops
without the pull_request trigger and harmless on push / merge_group.
* ci(lint): quote pattern expansion to fix actionlint SC2295 in qwen-autofix
actionlint (via shellcheck SC2295) flagged ${BRANCH#${BRANCH_PREFIX}}:
the inner expansion used as a strip pattern must be quoted separately, or
it is treated as a glob. Quote it as ${BRANCH#"${BRANCH_PREFIX}"} so the
prefix is stripped literally. This was failing the Lint check on every PR.
Replace the issue-only scheduled-issue-autofix workflow with a single
qwen-autofix workflow that runs the whole autonomous-fix lifecycle and acts
as one identity, qwen-code-dev-bot (via CI_DEV_BOT_PAT, no fallback).
A router job forks each scheduled tick: the review phase sweeps the bot's open
PRs every 4h, and the issue phase locates and fixes one new bug every 12h (at
00/12 UTC). The review phase fans out over every PR that has new, unaddressed
feedback or a base conflict, triages each comment by severity (verify-then-fix
for critical/merge-blocking; engineering judgment for suggestions, without
deviating from the PR's direction — low-value or over-engineered ones are
explained, not implemented), resolves any conflict with main, re-runs the same
deterministic verification gate, force-pushes, and reports the outcome on the
PR. A per-PR round cap and an evaluation watermark keep the 4h cadence cheap
and prevent bot-on-bot loops.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Integration tests (integration-tests/) previously ran only in the nightly
Release pipeline, so a PR that broke them merged green and the failure stayed
hidden until release time. Add a merge_group-gated job that runs the existing
test:integration:cli:sandbox:none suite, catching regressions before the PR
lands on main.
Gated on github.event_name == 'merge_group' so it does not touch the per-PR
critical path, and is a no-op until merge queue is enabled on main. Reuses the
OPENAI_* secrets already wired for the release integration jobs.
Refs #5219
* fix(ci): gate PR review and triage on write permission
Replace the per-path author_association checks (and the same-repo-only fork
check in triage) with a dedicated `authorize` job that resolves the triggering
principal — the PR author for automatic PR events, the commenter for /review
and /triage commands, the requester for review_requested — and verifies it has
admin/maintain/write permission via the collaborators API.
This lets fork PRs by write-permission authors qualify for automatic review and
triage (author_association reports such authors as CONTRIBUTOR from a fork, so
they were previously skipped), and tightens command triggers to real write
permission instead of the author_association proxy. The gate fails closed: any
API error or non-write permission denies the run.
The authorize job uses CI_BOT_PAT because reading collaborator permission
requires write/maintain/admin access; it runs no agent, checks out nothing, and
processes no untrusted PR content, so holding the PAT there is safe.
External actors without write permission still cannot trigger the model-calling
jobs.
* fix(ci): isolate unauthorized triage triggers
* fix(ci): report permission gate API failures
* fix(e2e): add daemon_status to serve capabilities baseline; run E2E on PRs
The `qwen serve — capabilities envelope` integration test hard-codes the
expected advertised-feature list. PR #5174 added the `daemon_status`
capability to SERVE_CAPABILITY_REGISTRY (and the server.test.ts unit
baseline) but did not update this integration test, so the list drifted
by one entry and the E2E job began failing on main.
Root cause it slipped through: E2E only triggered on push to main /
merge_group, never on PRs, so this stale assertion was never exercised
before merge. Add a pull_request trigger (targeting main) so capability/
contract regressions surface at PR time, and key concurrency on the PR
number so superseded PR runs cancel.
* fix(e2e): guard fork PRs from secretless runs; dedupe push/PR triggers
Addresses review feedback on #5211:
- Skip the e2e matrix on fork PRs. Forks have no access to repository
secrets (OPENAI_*, DOCKERHUB_*), so without a guard every fork PR
produces 3 guaranteed red jobs and wastes CI minutes. A head-repo
guard runs the jobs for same-repo PRs (and push / merge_group) but
skips them for forks.
- Key concurrency on the head ref (github.head_ref || github.ref_name)
so a push to a feat/e2e/** branch and a pull_request from that same
branch share one group and cancel each other, instead of running the
full matrix twice for the same change. Keeps the feat/e2e/** push
trigger (the no-PR e2e iteration escape hatch) intact.
- Stop cancelling in-progress main runs so every main commit produces a
complete e2e result (matching ci.yml's policy and the very signal that
surfaced this bug); still cancel superseded PR / feature-branch runs.
---------
Co-authored-by: jinye <djy1989418@126.com>
* ci(autofix): prioritize recent unattended bugs over stale ones
The scheduled autofix scan gated candidates on `updated:<14 days ago`,
which restricted the pool to stale issues and let aging bugs surface
ahead of fresh reports despite the `sort:created-desc` ordering. Drop
that staleness gate so the newest unattended bugs lead the pool, and
make recency the tiebreaker in the assessment prompt when candidates
are comparably confident.
The remaining guardrails are unchanged: candidates must already be
labeled `type/bug` (triaged), have no assignee, no linked PR, no human
comments, and not carry the skip/in-progress/need-info/need-retesting
labels — so this prioritizes recent bugs without grabbing issues a
human is actively working on.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* ci(autofix): add recency guardrails
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: 易良 <1204183885@qq.com>
* fix(ci): bypass proxy for qwen model calls in PR review
The legacy qwen-code-pr-review.yml had no proxy bypass: it relied on the
runner's NO_PROXY, which qwen does not honor when building its proxy
agent, so model calls went through the corporate proxy and the review
job died with 'terminated'.
Port the configure_qwen_network helper from qwen-pr-triage.yml into the
review step:
- append OPENAI_BASE_URL host to NO_PROXY/no_proxy
- clear HTTP(S)_PROXY for the qwen process itself
- wrap child gh/git on PATH to restore GitHub proxy
- print qwen_http_proxy=disabled / child_git_github_proxy diagnostics
This is a hotfix; it becomes obsolete once the old workflow is removed
in favor of the new triage pipeline (which already carries the bypass).
* fix(review): put model footer on its own line in summary reviews
The no-findings summary review templates appended the model attribution
footer with a leading space, so it rendered on the same line as the body
(e.g. 'CI failing: review-pr. _— qwen via Qwen Code /review_'). The
inline-comment template already separates the footer with a blank line.
Use a real blank line before the footer in the -f body examples. Unlike
the JSON --input path used for inline comments, gh -f does not interpret
\n, so a literal line break is required for the footer to land on its
own line.
* fix(ci): clean stale review worktrees before checkout
Self-hosted runners reuse the workspace, so an interrupted review can
leave a stale .qwen/tmp/review-pr-* worktree or qwen-review/* branch that
makes the checkout warn or recreate. Add a best-effort pre-checkout step
that prunes them; it never fails the review job.
* fix(ci): guard node availability and RUNNER_TEMP in proxy bypass
Address review feedback on #5168:
- check `node` exists before parsing OPENAI_BASE_URL, and fail with a
clear message if the host cannot be parsed (was a cryptic failure
under set -e when node is absent)
- use ${RUNNER_TEMP:-/tmp} fallback for proxy_bin, matching LOG_PATH
above, so an unset RUNNER_TEMP does not abort under set -u
The scheduled issue autofix workflow's Publish PR step fell back to the
default GITHUB_TOKEN because AUTOFIX_BOT_TOKEN was never configured.
GitHub blocks GITHUB_TOKEN from opening pull requests ("GitHub Actions
is not permitted to create or approve pull requests"), so the run failed
at the final step even though a fix had already been verified and the
branch pushed.
Use the qwen-code-ci-bot PAT (QWEN_CODE_BOT_TOKEN, falling back to
CI_BOT_PAT — the same secrets the issue follow-up bot uses) so the PR is
opened by qwen-code-ci-bot. As a real-account token it bypasses the
restriction and also triggers CI on the created PR, which GITHUB_TOKEN
would not. Add a guard that fails with a clear message when neither
secret is configured.
Co-authored-by: Qwen-Coder <noreply@alibabacloud.com>
The review-pr job only checked qwen's exit code, the tee status, the
timeout sentinel, and an empty log. When the model connection drops
mid-review, qwen still exits 0 and emits a terminal stream-json result
event with subtype=success / is_error=false whose text carries the
inlined '[API Error: ...]'. All existing checks pass, so the job goes
green without ever posting a review comment.
Inspect the terminal result event explicitly: fail when it is missing,
when is_error is true or subtype is not success, or when the result text
contains an inlined API error. A failed check now triggers the existing
fallback-comment step instead of a silent green success.
Refs #5052
Migrate the built-in Computer Use tool surface from open-computer-use
(npm/npx) to cua-driver-rs (native Rust driver, trycua/cua).
- Replace the 9-tool ocu surface with the full 35-tool cua-driver surface
(page/CDP, cursor, session, recording, config, app lifecycle, …),
generated from the live `cua-driver mcp` tools/list — pinned to v0.5.2.
- Per-platform signed + notarized binary distribution: download into
~/.qwen/computer-use/ with SHA-256 integrity verification, a three-tier
Windows unzip fallback, headers/idle timeouts, and a bounded retry loop.
- macOS TCC permission flow via CuaDriver.app (com.trycua.driver), polled
one-at-a-time through the no-gate status daemon.
- Mirror cua-driver assets on the qwen-code-assets OSS bucket with a
push-to-main sync workflow guarded by checksums.
- Gate high-risk tools (kill_app, launch_app, start_recording, set_config,
replay_trajectory, page JS execution) to an explicit confirmation type so
AUTO_EDIT cannot silently auto-approve them; AUTO defers to the classifier,
YOLO auto-approves.
* chore(desktop): drop dead NOTARIZE env flag from mac signing paths
electron-builder (>=24) auto-notarizes via notarytool whenever APPLE_ID,
APPLE_APP_SPECIFIC_PASSWORD, and APPLE_TEAM_ID are present in the env. The
NOTARIZE=true flag set in the release workflow, build-dmg.sh, and
scripts/build/darwin.ts was never read by electron-builder, and the
build-dmg.sh comment claiming it enabled notarization was misleading.
Remove the no-op and document the actual auto-detection behavior.
* ci(desktop): notarize via App Store Connect API key instead of Apple ID
Switch the macOS desktop release notarization path from the Apple ID +
app-specific password method to the App Store Connect API key method,
which is more robust (no 2FA, no password expiry) and reuses the notary
key already provisioned for the org.
The signing step now reads APPLE_NOTARY_API_KEY_P8_BASE64,
APPLE_NOTARY_KEY_ID, and APPLE_NOTARY_ISSUER_ID, decodes the .p8 to a
temp file, and exports APPLE_API_KEY/APPLE_API_KEY_ID/APPLE_API_ISSUER,
which electron-builder (>=24) consumes to notarize via notarytool.
Published mac releases now require those notary secrets plus
APPLE_TEAM_ID.
* feat(ci): add auto-generated CHANGELOG.md synced from releases (#4872)
Add a Keep a Changelog CHANGELOG.md that is fully derived from the project's
GitHub Releases, so users no longer have to dig through commit history to see
what changed between versions.
- scripts/generate-changelog.js: fetch releases via the gh CLI, keep only
stable vX.Y.Z tags (nightly/preview omitted), and re-group each release's
auto-generated "What's Changed" list into Added/Changed/Fixed/Performance/
Documentation/Other sections by the conventional-commit prefix every PR title
uses. Zero runtime deps (node builtins + gh).
- release.yml: regenerate and commit CHANGELOG.md on stable releases; the
commit rides the existing release-branch PR into main.
- package.json: add 'npm run changelog'.
- .prettierignore: skip the generated file.
- Seed CHANGELOG.md from the current stable release history.
Closes#4872
* ci(release): make CHANGELOG regeneration non-blocking
Add continue-on-error to the CHANGELOG step. Its only realistic failures
(the gh API read and the git push) are transient, and the generator rebuilds
from the full release history each run, so a skipped update self-heals on the
next stable release. This keeps a changelog hiccup from blocking the
version-bump PR to main that follows.
* fix(ci): harden changelog generator per review
Address review findings on the changelog generator:
- Command injection: fetchReleasesJsonl built a shell string with the repo
interpolated; switch to execFileSync (no shell) plus an owner/name format
check so --repo can never be a shell payload.
- Empty-response clobber: if the API returns zero stable releases (rate limit,
auth, 5xx), refuse to overwrite CHANGELOG.md and exit 1 instead of committing
a header-only stub. Paired with set -euo pipefail in the workflow so the
failure is visible and the non-blocking step self-heals next release.
- Arg parsing: switch getArgs() to parseArgs() (already imported) so a --dryrun
typo errors instead of silently overwriting, and -h works as documented.
- Breaking changes: capture the conventional-commit ! marker and prefix the
entry with **BREAKING** (the repo uses feat()!:/refactor()!: in practice).
- Bot authors: ENTRY_RE now accepts a trailing [bot] so GitHub App authors
(e.g. @dependabot[bot]) are not dropped from release notes.
Regenerates CHANGELOG.md (two entries now flagged **BREAKING**).
* refactor(ci): simplify changelog generator
Quality-only cleanup (output is byte-identical; 23 tests green):
- Single source of truth for sections: derive TYPE_TO_SECTION and
SECTION_ORDER from one SECTIONS list so they can't drift.
- Parse each entry once: formatRelease now reuses the categorize() result for
the noise check, section lookup, and formatEntry (was parsed up to 3x).
- Drop the redundant sortKey field; sort directly from version.
- Collapse the duplicated double-.map() setup in the selectStableReleases test.
* ci(review): align automated review with bundled skill
* ci(review): trigger when bot review is requested
* ci(review): disable deployment records for review delay
* ci: sync actionlint version env
* ci(review): authorize requested review trigger
* ci(review): centralize review bot login
* ci(review): narrow review config trigger
* ci(review): isolate comment review concurrency
* ci(review): surface reviewer permission check failures
* fix(ci): use exit 0 on permission check API failure
When the GitHub API call to check requester permission fails, the job
should still complete successfully since should_review=false is already
set. Using exit 1 marks the job as failed, which blocks the downstream
review-pr job even with always() — a transient API outage would silently
prevent all reviews.
* fix(ci): only cancel-in-progress on synchronize events
Restrict cancel-in-progress to push (synchronize) events so that
review_requested events (adding a human reviewer) don't accidentally
cancel an in-progress bot review run sharing the same concurrency group.