mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-12 14:44:15 -10:00
feat/btw-qodercli-alignment
25
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
274c8a5283 |
fix(ci): report timed-out main CI runs instead of letting cancelled hide them (#16280)
* fix(ci): report timed-out main CI runs instead of letting cancelled hide them
main-ci-failure-issue.yml only admitted `failure` conclusions, but a job that
hits its execution cap concludes `cancelled` — so timed-out main runs never
filed an issue. That is how #16214 (the E2E docker leg rebuilding the sandbox
image past its 60-minute cap on cold mainland hosts) hid for weeks (#16272).
Admit `cancelled` at the trigger, then discriminate in the live step: a
cancelled run is reportable only when a job carries the execution-time-timeout
annotation; a plain concurrency supersede is skipped, and an unreadable
annotation fails safe toward not reporting. The download projections include
cancelled jobs so the timed-out leg is named in the report. The infra
classifier is unchanged, so a pure-timeout run (no failure jobs) files rather
than reruns, and normal failure handling is untouched.
Meta-tests: the trigger pin and routing matrix now reflect the admitted
cancelled conclusions; three new live-step tests witness timeout→report,
concurrency→skip, and unreadable→skip; the download-selector and jobs-fetch
pins are updated, as is the ci-platform-lanes trigger pin.
* fix(ci): classify cancelled runs by positive evidence, fail open when unknown
Review on #16280: real concurrency cancels carry the runner shutdown / higher-
priority / operation-canceled annotations, not just "operation was canceled",
and a timed-out job can carry a shutdown annotation alongside the timeout. The
previous discriminator only looked for the timeout text and skipped everything
else, so it rested on "absence of timeout = concurrency" and dropped unreadable
cancellations — re-creating the hiding problem #16272 fixes.
Classify by positive evidence on both sides instead: a timeout annotation
reports (even with a coexisting shutdown annotation); a concurrency annotation
with no timeout skips; neither, or unreadable jobs/annotations, fails open and
reports like the rest of the live step. Fixtures cover the shutdown and
higher-priority concurrency forms, timeout+shutdown coexistence, and the
fail-open paths; breaking the concurrency signature reddens the skip tests.
* fix(ci): address #16280 review — trust gate, renderer, successor dedup, docs
R1-4 (security): only trusted triggers (push/schedule/dispatch on main) run the
cancellation classifier; PR-triggered cancellations stay on the supersede path
so contributor-authored annotation text cannot drive the PAT-backed reporter.
R1-1: renderFailureComment now keeps cancelled jobs, so a timed-out leg is named
in the issue/PR comment even when no job concluded failure (the #16214 shape).
R1-2: factor the classifier into cancelled_class with a total_count receipt and
explicit unreadable handling; unknown fails open with a warning instead of
asserting a supersede it never established.
R1-3: a timeout-backed cancelled successor owns the older run's result (same
shared classifier), preventing a duplicate report; log wording corrected.
R1-6: the classifier runs after the stale-attempt check so a stale event is
attributed to staleness, not to a cancellation cause.
R1-7: the test stub resolves the check-run id (job id != check-run id) and
applies the caller's --jq with real jq; RUN_CONCLUSION env is pinned; fixtures
cover shutdown / higher-priority / coexisting timeout / unreadable / PR arms.
R1-5: sync the two design docs (both languages) with the new behaviour.
* fix(ci): move the cancelled-run classifier out of the workflow body
main-ci-failure-issue.yml grew to 63933 bytes, 6793 over its recorded
57140 (allowance 4096), so the workflow-size ratchet failed. Move the new
cancelled_class helper into .github/scripts/ci/classify-cancelled-run.sh,
which is what the ratchet asks for ("long steps into .github/scripts/"),
and trim review-narrative prose to the load-bearing why. Behaviour is
unchanged: both call sites invoke the script.
The workflow-shape pin that read the classifier's attempt-jobs fetch out
of the workflow now reads the script.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmw1wuw3xco
* fix(ci): do not report a cancelled run that ran no job (#16280 R1-2, R2-1, R2-4)
Critical R1-2: the classifier returned unknown (fail-open report) for a
cancelled run whose attempt has no cancelled job at all — a run displaced while
queued certifies 0==0 and reads no annotation, so it filed a "Main CI failed"
issue for a run that tested nothing. Track whether any cancelled job was seen;
none means supersede (concurrency), while unknown now only covers cancelled jobs
whose annotations are unreadable/unmatched or uncertifiable pages.
R2-1: tests for the certification false branch (partial pages -> unknown) and
the no-jobs case (-> concurrency). R2-4: a negative successor case with a real
id whose cancelled job is a supersede, exercising the == 'timeout' false branch.
* fix(ci): report a cancelled run that also carries a failed job (#16280)
R3-1 (Critical): the classifier read only `cancelled` jobs, so a run whose
job already concluded `failure` before the rest was cancelled classified as a
deliberate supersede and dropped the regression. Read `failure`/`timed_out`
jobs first and fail open on them.
R1-2 (Critical): a cancelled run whose jobs all stopped before their first
step still fell through to `unknown`, which reports. Treat "no cancelled job
ran a step" as nothing-executed, preserving the zero-job behaviour.
R3-2: give that outcome its own token and case arm, so the caller's notice no
longer claims an annotation this path never read.
R3-4: the annotation-error test now uses two cancelled jobs, because with one
the fall-through prints the same `unknown` as the guard and cannot see it
removed.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmw2mktggdp
* test(ci): correct successor-gate comment and pin the classifier's full fetch
R3-3: the comment no longer claims the gate admits only failures. R2-4: pin the
classifier fetch including ?per_page=100 and --paginate so flag-stripping reddens.
* fix(ci): keep a failed cancel-classification read from deferring the report
The classifier printed `unknown` both for a run it read and could not match
and for a read that failed. Crediting `unknown` as the successor's own
verdict would then let a transient API failure drop the only report of a
regression, because the successor's gate may have read the same run as
concurrency and skipped. Print `unreadable` for those read failures and
keep the ownership arm on `timeout`/`unknown`.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmw4alb71g0
* fix(ci): keep a confirmed timeout over a sibling's failed annotation read (#16280 R5-1)
The per-job annotation read exited unreadable before the verdict block, so
a sibling job's failed read buried timeout evidence another job already
carried — the same hiding this PR removes, one layer over. Remember the
failed read, keep scanning, and fall back to unreadable only when no
timeout was confirmed; a concurrency supersede is likewise not certifiable
over an unread sibling. Adds real-script regression tests (stubbed gh)
that go red on the pre-fix script.
* test(ci): exercise the successor jq projection end to end (#16280 R5-5)
The cancelled-successor deferral binds successor_id/successor_attempt from the
workflows/runs --jq projection, but every stub handed back a pre-formatted
string, so dropping the id field stayed green while production bound the
attempt number as the id. The new case returns raw JSON and applies the
caller's filter with real jq (verified red with the id field dropped), plus a
shape pin on both projected fields.
* docs(ci): align the design docs with the cancelled-run verdicts (#16280 R4-1)
The R4-1 fix widened the successor-ownership rule from `timeout` to
`timeout | unknown`, and the classifier now skips on `nothing-executed` as
well as `concurrency`, but both design docs this PR touches still described
the pre-fix rule: only a timeout-backed cancelled successor owns the result,
and a cancelled run reports only when a job carries a timeout annotation.
Restate the shipped rule in all four files (both languages): the two verdicts
that stay silent, the fail-open `unknown`/`unreadable` reports, a cancelled
run that also carries a `failure`/`timed_out` job, and the `unreadable`
successor that deliberately does not take ownership because a failed read
certifies nothing about what that run concluded.
Docs only — no workflow or script bytes change, so the workflow-size ratchet
is unaffected.
* ci: record main's 501-byte growth of main-ci-failure-issue.yml (#16280)
Merging main into this branch brought in
|
||
|
|
1b1f4455d9 |
ci: build the PR tree once and shorten the required path (#16450)
* ci: build the PR tree once and shorten the required path The no-AK integration gate, the four web-shell smoke shards and Lint & Static each rebuilt the tree test_build already uploads, and the build cache rarely helped: most runs logged "29 of 29 workspaces changed" because the arch-keyed cache was split across the mixed x64/ARM64 pool and a prefix restore returned the newest entry instead of the base commit's. - test_build restores the build its base commit saved (ci-build-cache.mjs key --at), drops runner.arch from the key, and runs the serve fast-path closure and core subpath checks against the built tree. - The no-AK integration gate unpacks that tree and runs as three shards; integration_no_ak_result keeps the required check name. - The web-shell smoke unpacks the same tree instead of building; consumers recopy the opentui native assets for their host (host-assets). - The standalone runtime regression moves from unit shard 1 to shard 4. - A java_only profile skips the TypeScript lanes for changes confined to the Java packages; Lint & Static runs the scripts/tests suites that name them. - Drafts skip the integration gate and the browser smoke; ready_for_review starts the full run. * ci: check the settings schema in test_build, which has the core dist Lint & Static only had core's dist because the serve fast-path check built it; with that check moved, Generate settings schema failed to import it. * ci: keep the standalone regression on unit shard 1 Moving it did not shorten shard 1: the regression runs in the side lane, in parallel with the CLI and core suites, and shard 1's time is its CLI slice (576 s against 147 s on shard 4 for the same 181 files). That imbalance is left to duration-based sharding. |
||
|
|
56fec7f3f5 |
ci: shard the Linux unit tests and web-shell smoke, and reuse cached builds (#16133)
* ci: shard the Linux unit-test lane eight ways behind one build The Linux Test job ran 45-59 minutes: a ten-minute prepare build, then the CLI and core suites back to back. A new test_build job runs the prepare build once and uploads its outputs; eight shards install without the build, unpack them and run --shard=N/8 of every Vitest suite. Test (ubuntu-latest, Node 22.x) becomes an aggregate that passes only when the build and every shard pass. * ci: record ci.yml's growth from the test_build prelude test_build carries the same pre-checkout cleanup, checkout and install prelude as the test shards; ci-platform-lanes.test.js pins the two copies identical. * ci: record the Linux lane's two new jobs in the PR-stage budget test_build and the test_ubuntu aggregate add two jobs; the declared timeouts drop from 305 to 295 minutes. * ci: shard the web-shell E2E smoke four ways The smoke lane ran 44-46 minutes on every recent PR, about 33 of them in the browser tests at two workers, and is the longest lane once the Linux unit tests are sharded. Each shard runs --shard=N/4 of the smoke; the transcript gate runs on the first. web-shell E2E Smoke (ubuntu-latest, Node 22.x) becomes an aggregate over the shards. * ci: bound the unit shards' tree download and retry it once A pool host stalled 17 minutes in download-artifact while the other shards took under a minute. * ci: rebuild only the workspaces a change reaches scripts/ci-build-cache.mjs hashes each workspace's tracked build inputs (tests excluded) with its workspace dependencies' hashes and the shared root inputs. test_build and the web-shell smoke shards restore the newest cached build and rebuild only workspaces whose hash changed. A push to main always builds from scratch and saves, so a pull request starts from a clean build of main. * docs(ci): describe the incremental build cache * ci: keep the build cache path the same on every pool host actions/cache versions an entry by its path; under runner.temp the path differs per host, so a run could never restore another host's save. * ci: save the ARM64 build cache from the smoke lane, and run the aggregates on the shards' runners The smoke shards run on the ARM64 pool, which test_build may not, so the first shard saves the clean build it made when its restore found nothing. The two aggregate checks queued 9 and 13 minutes for a hosted runner after their shards finished; they now take the shards' runners. * ci: bound the sharded test step and skip shards when the build fails Address the first automated review round on the Linux sharding lane: - Gate every build-consuming shard step on `test_build` success. `!cancelled()` stays true when a needed job fails, so a failed build still started all eight shards, each paying a full install before dying at the missing artifact and reporting artifact plumbing as its failing step. - Drop the test step cap to 45 minutes. The job cap is 60, so the old 110 could never fire; a step timeout keeps the diagnostic steps below it alive while a job timeout does not. - Keep the built-tree artifact for a week so a late "re-run failed jobs" still finds it, and correct the trigger comment that still named three post-merge jobs when five now accept push. Co-authored-by: Qwen-Coder <[email protected]> * ci: keep disk telemetry for a failed build and correct the design doc `test_build` installs with the disk sampler and then builds for up to its whole timeout, but the sampler dies with the install step's trap and no collector ran, so an ENOSPC build (or the OOM that took a shard host) kept no evidence. Give it the same failure-gated dump and last-step upload as `lint_and_static`, under its own artifact name. The incremental-lint design still said the test job keeps normal prepare behaviour; this lane now installs with `QWEN_SKIP_PREPARE=1` and unpacks the tree built once by `test_build`. Correct both language versions. Co-authored-by: Qwen-Coder <[email protected]> * ci: share the built-tree definition and the build preflight, harden the guards The archive step re-implemented `builtPaths()` from `scripts/ci-build-cache.mjs`, so the tree the shards unpack had a second definition that could drift from the one `pack` stores. A `paths` mode now prints that list for `tar -T -`, and a new test pins the four artifact-name literals that tie the producer to the eight consumers. The cached branch of `build` also hand-reproduced a subset of `scripts/build.js` and so skipped its ink-patch preflight: the same missing `frame-controller.d.ts` self-healed on a cache miss and surfaced as ~50 tsc errors inside ink's declarations on a hit. The preflight is now a function reachable as `scripts/build.js --preflight` and runs before both branches. The remaining review suggestions were all meta-test gaps: the standalone-runtime expression and the `test_build`/`test` prelude were guarded by nothing that compares them in order, `ci-build-cache.mjs`'s three CI modes were never executed by a test, and the infra-classification filter's step name was not joined to `ci.yml`. Each now has an assertion that goes red when the guard is removed. Co-authored-by: Qwen-Coder <[email protected]> * ci: bound the build steps so the failure collectors can run Round-2 review found four issues in the previous round's fixes and one gap in the new build tests. A job timeout cancels the job and skips the failure-gated collectors, so the `test_build` steps that can block now carry caps below the job's 45 minutes: the install, the build, the pack and the archive. The install caps are identical across `test_build`, `test` and `lint_and_static` so the prelude parity guard still compares the three copies field for field. A test pins every blocking cap under the job ceiling. The `--preflight` early exit was only asserted as a string, so it could be renamed with nothing going red while the cache-hit path silently ran the full build. A spawned case now copies `scripts/build.js` into the fixture and asserts the flag exits without generate, a workspace build or the schema step. The CLI mode tests write extension-less `#!/bin/sh` stubs and join PATH with ':', which cmd.exe cannot resolve; the Windows lane collects that file and downgrades its failure to a warning, so it would have carried a permanently red block. The suite is excluded on win32 alongside the other bash-fixture suites. The `test_build`/`test` intersection grew to 16 with the disk collector, but the floor stayed at 15, leaving exactly one shared step uncompared; the floor now matches the membership. The cache-hit-with-changes branch — the code that decides what is rebuilt and what is deleted — gains a case: a changed workspace is purged and rebuilt by directory, and an untouched workspace keeps what the cache restored. ci.yml's recorded size baseline is updated to the current file so the next PR gets the full growth allowance back. Co-authored-by: Qwen-Coder <[email protected]> * test(ci): keep cache fixture call logs outside the archived tree Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: 易良 <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
eea44208b8 |
ci: run every suite but CLI and core in the side test lane (#15570)
* ci: run every suite but CLI and core in the side test lane
The Linux test job's main lane ran the CLI suite and then, one after
another, core, about twenty smaller workspace suites and scripts/tests,
while the side lane (web-shell, then the standalone regression) went idle
long before the CLI suite finished. The main lane now runs only the CLI
and core suites; the side lane runs every other workspace's test:ci, the
standalone regression and scripts/tests, each at half the Vitest worker
cap. Every side-lane suite runs even after another fails.
* ci: print the side lane of a test step killed before its replay
The lane script replays the side lane's log only after the main lane
ends, so a step timeout or cancel lost every suite but the CLI and core
ones. The script now writes that log to SIDE_LANE_LOG when the caller
names one; ci.yml names it under runner.temp, outside the TMPDIR the step
removes, and a failure()/cancelled() step prints whatever is left of it.
The lane test also checks that the main lane's output is present before
it checks its order against the replay, and a comment that still placed
scripts/tests in the main lane now names the side lane.
* ci: keep the lane script's log out of a nested run
scripts/tests runs `npm run test:scripts:unit`, which runs the lane script
again, inside the step that names SIDE_LANE_LOG. The nested run inherited
that name and truncated, replayed and removed the outer side lane's log, so
the outer replay had nothing to print. The script now unsets the variable
once it has read it, and the lane test drives a nested run with a named log
to pin that: the nested run must not see the caller's log, and the outer
side lane's own output must still reach its replay.
The print-step test also pins the `if [ -f "${side_log}" ]` guard around the
cat, which is what keeps that step quiet on an ordinary red test job.
|
||
|
|
1df5031e70 |
perf(ci): overlap web-shell and standalone tests with the CLI suite (#15438)
* perf(ci): overlap web-shell and standalone tests with the CLI suite The Linux Test job ran every workspace suite, the standalone runtime regression and scripts/tests one after another. The CLI suite keeps its workers busy for 13-20 minutes, while web-shell's suite spends most of its run on App.test.tsx alone and the standalone regression starts one runtime process at a time, so those two added their mostly idle minutes straight to the job. Run them in a side lane beside the other workspaces instead. .github/scripts/ci/run-unit-test-lanes.sh starts web-shell and the standalone regression in the background, runs every other workspace's test:ci (listed with npm pkg get, in npm's workspace order) and then scripts/tests, and replays the side lane's log after the main lane so the two never interleave. The step runs the script under the same setpriv wrapper, so every test command still runs without the DAC override capabilities. test:scripts now reaches its Vitest run through a new test:scripts:unit script with a trailing --, so the main lane can run it without the standalone regression while test:ci, test:ci:workspaces and test:scripts run what they ran before. * fix(ci): name the cause when the lane script cannot start a lane - Fail with an ::error:: line when mktemp cannot create the side lane's log file, instead of running the main lane alone and dying in tail. - Check the exit status of `npm pkg get` and of the parser. A failing `npm pkg get --json` prints an error object, which used to parse as an empty listing and report "Found no workspaces". - Note in the header that the replay puts side-lane failures after the main lane's in main-failure-signature.mjs's title and search markers. - Lane test: clear the caller's NODE_OPTIONS, assert the main lane's output is present before checking its order, and cover the failed listing and the missing temp dir. - Cross-check npm's workspace resolution against the selector in scripts/workspaces.js. * fix(ci): halve web-shell's worker cap beside the main lane Each Vitest run applies VITEST_MAX_FORKS / VITEST_MAX_THREADS on its own, so running two lanes at once doubled the step's worker cap, and on the hosted runners, where no cap is set, put two all-CPUs pools on 4 vCPU. The lane script now runs web-shell at half the cap, at least 1, and at 2 where the cap is unset; the main lane keeps the full cap. - Refuse a workspace name with whitespace in it: the main lane's arguments are read one per line, so a line break would split a name into other arguments, flags among them. - Lane test: web-shell and the main lane's suites now wait for each other, so a side lane serialized before or after the main lane fails; record each call's worker caps; run the script against npm's real listing and compare its main lane with the selector the release pins use; cover a name with a line break in it. - ci.yml: the standalone runtime cache comment names the lane script's side lane, not test:scripts, as what runs the regression in this job. - ci-variables.md: say the cap applies per Vitest run and what the two-lane step runs at its peak. - release-workflow.test.js: follow test:scripts through test:scripts:unit to vitest; #15102 pinned the shape before the split. |
||
|
|
0b8af17b04 |
fix(ci): file new main CI failure signatures as their own issues (#15405)
* fix(ci): file new main CI failure signatures as their own issues The aggregate comment stream hid new regressions: every failure, including never-before-seen ones, landed as a comment on a month-old issue whose title still named the first recorded failure. A run that names its failing tests now files or reuses a per-signature issue, deduped by the test markers embedded in the body; only a run without any test identity shares the workflow's aggregate issue, whose title is refreshed with the latest occurrence date on every record. Resolves #15398 * fix(ci): close the aggregate hijack edge cases found in review The closed-issue lookup's body filter crashed jq on any closed per-signature body (pipe-into-not precedence), silently filing a duplicate aggregate; the mixed closed population is now pinned by a test that fails without the fix. Test-marker matches are also scoped to the owning workflow: filings embed a main-ci-failure-scope key, the retitled body scan requires it, and accretion back-fills it so a human retitle can never drop an issue out of the scan. A matched-body re-read failure now aborts loudly instead of under set -e silently. * fix(ci): harden the adoption and regression paths found in round two A transient body read on the true aggregate no longer skips ahead to the next admissible per-commit filing; adoption now abandons the title list for the read-free body-marker fallback. A regressed test reopens its resolved per-signature issue (closed bodies are scanned for the test markers, keyed by the workflow scope) instead of filing a duplicate, and the accretion edit re-reads the tracking issue's live body before replacing it so a concurrent human edit is appended to, never clobbered. Also drops two empty-array guards bash 5 makes redundant, the redundant first-line slice on the closed lookup, and one test that pinned an input combination the helper cannot emit. * fix(ci): prefer exact titles, retry writes, and accrete only unowned markers An exact machine title now certifies the aggregate without a body read, closing the window where a transient read failure on an unmarked candidate manufactured a duplicate aggregate. Comment/create/reopen writes share the lookups' three-attempt retry so a single transient no longer loses the occurrence. Marker accretion consults the sibling trackers' bodies first: a marker another per-signature issue owns is never pulled into the oldest match, which would shadow the sibling for every later run. * fix(ci): drop the truncated candidate body a failed scan leaves behind The accretion sibling corpus reuses candidate files with -f, so the empty file a failed gh issue view leaves by redirection read as an already-fetched empty body, silently defeating the ownership screen and re-admitting a sibling-owned marker onto the matched issue. The scan now removes the truncated file, forcing a refetch whose persistent failure suppresses marker accretion as designed. Reopen and create retries gain the same pins the comment retry already had. * fix(ci): screen every adoption candidate and widen marker ownership Machine titles sort first but no longer certify adoption on their own: a human can retitle a per-signature issue into the exact shape, so every candidate passes the same body screen, exact kind first. Marker accretion now also consults scoped open bodies outside the title family, so a human-retitled tracker keeps its markers too. Documents why a create retry that mints a same-marker duplicate still beats losing the record. |
||
|
|
57f88a93e5 |
fix(ci): reuse open workflow failure issues (#15256)
* fix(ci): reuse open workflow failure issues * fix(ci): single-quote the concurrency queue value yamllint's quoted-strings rule (quote-type: single, required: true) rejects the bare `max` scalar, which is what failed the Lint & Static lane at .github/workflows/main-ci-failure-issue.yml:342:14. The value is unchanged after parsing: `queue: max` and `queue: 'max'` both yield the string "max", so the workflow semantics and the parsed-document assertions in scripts/tests/main-ci-failure-issue-workflow.test.js are unaffected. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmvjvf9h5qa * fix(ci): publish the tracking issue before the cosmetic label edit A non-zero exit from the decorative `--remove-label` edit aborted the writer step under the `set -euo pipefail` this branch adds, losing both the recurrence comment and `issue_url`. report_pr is `always()`-gated and reads `issue_url` unconditionally, so it posted the merged PR's failure summary with the [Tracking issue] link silently omitted. Restore the ordering the deleted comment documented: url and comment first, then the retirement. Also drop the `issues: read` grant the analyze job no longer uses now that the plan step is gone, and pin the paginated lookup's page merge, which no fixture exercised because every one fed a single JSON document. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmvk8a84dqp * fix(ci): preserve provenance and lifecycle of failure aggregates --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
c4f0acdef8 |
feat(extensions): add bounded generated HTML and JavaScript views (#14338)
Co-authored-by: qqqys <[email protected]> |
||
|
|
c64163bcea |
feat(managed-agent): ship systemd units for the single-host topology (#14633)
* feat(managed-agent): ship a configuration template in the reference distribution The reference archive carried no application.yml, so the Runtime credential key ring — which cannot be expressed through QWEN_MANAGED_AGENT_* variables because relaxed binding normalises map keys — forced every rotating deployment to create a file and pass --spring.config.additional-location by hand. Ship config/application.example.yml and have both launchers load config/application.yml from the bundle root when it exists, leaving an operator-supplied SPRING_CONFIG_ADDITIONAL_LOCATION in effect. * feat(managed-agent): add a read-only --check preflight The reference distribution listed its prerequisites in prose, but the server evaluated them only at startup or on the first Turn: MySQL grants, the credential key ring, auth/bind combinations, Harness reachability, broker credentials, directories and ports. Intercept --check before Spring starts and report each input as PASS/WARN/FAIL/SKIP, exiting 1 on any failure. The command binds the same configuration as the server, probes /capabilities, and compares the Flyway version with the release manifest when one is present. * feat(managed-agent): ship systemd units for the single-host topology The server reference required a supervisor that leaves Runtime workers running across a Broker restart, but the reference distribution shipped no unit that did so. Add control-plane and Hosted Harness units, environment templates, and an install guide. The control-plane unit uses KillMode=process and StateDirectory; the Harness uses KillMode=mixed. Include the templates in the archive and guard their required directives with a structural test. * fix(managed-agent): pass the distribution config through the environment The shared launcher injected --spring.config.additional-location before the operator's arguments, so the operator-recovery entry, which dispatches on args[0] and the exact argument count, could no longer run inspect, prepare or complete. Export SPRING_CONFIG_ADDITIONAL_LOCATION instead and leave the argument vector untouched for both launchers. Also make the template's active key pair use the mapped placeholders, so a copied example still honours the documented environment override. * docs(managed-agent): match the preflight design to the launcher fix The preflight design still said the launcher adds a command-line config option. The launcher now exports SPRING_CONFIG_ADDITIONAL_LOCATION, so update both language versions. * test(managed-agent): isolate the launcher test from an ambient config variable The test spread process.env, so a machine that already exported SPRING_CONFIG_ADDITIONAL_LOCATION changed the launcher's behaviour and failed the new assertions. Remove the variable from the base environment; the explicit operator-override case still sets it. * fix(managed-agent): align the preflight with startup validation The preflight could report PASS for a database without the performance_schema grant, ignored command-line overrides, dropped earlier external YAML files when several were listed, and could not find the release manifest in the repackaged executable JAR. It also hand-copied auth and Workspace tool-path rules that disagreed with the startup validators. Delegate auth to BrokerSecurity, mirror the Workspace tool-path admission, fail on a missing grant, load command-line arguments and every external YAML with unique property sources, and resolve the manifest from the outer JAR. * fix(managed-agent): reject the static provisioner for Workspace files validateWorkspaceFiles refuses Workspace files on the static provisioner, but tool-path reported only WARN. Make it FAIL and keep the not-evaluated warning for other provisioners. * fix(managed-agent): close the remaining preflight review gaps Resolve relative class-path entries before locating the manifest, bind the full server and spring.mvc properties before the auth guard, and require durable local-process execution plus verified recovery for a Workspace creation root. * fix(managed-agent): wire the Harness Broker flags and keep EnvironmentFile semantics Expose QWEN_HOSTED_HARNESS_BROKER_ARGS in the Harness unit so the tool path can pass the Broker URL and token, and run the preflight through systemd-run so it reads the same EnvironmentFile as the service. * fix(managed-agent): run the startup validation in the preflight The preflight bound ManagedAgentProperties with Binder, which never runs the @PostConstruct startup validation. Configurations that the server rejects at startup -- an approval timeout below the ten-second floor, an unknown bound-session isolation class, SSH enabled without Workspace files -- printed PASS and exited 0. ManagedAgentProperties now exposes the validation as a callable method and the preflight reports its verdict as a `validation` row. * fix(ci): keep the systemd READMEs on the full profile The packaging test names distribution/systemd/README.md and its Chinese counterpart, so classify-profile.test.mjs requires them in TEST_READ_MARKDOWN. Without the entry the helper battery failed with both files listed as unregistered lightweight Markdown, reddening Lint & Static for the systemd stack. * fix(managed-agent): boot the systemd template and authenticate the preflight The shipped server.env.example enabled the Hosted Harness without the Managed Session Store, which startup refuses, so following the README restart-looped. The template now enables the store and sets its base URL and the Runtime workspace ID. The preflight probed /capabilities without the Harness token, so --check printed FAIL 401 against a healthy authenticated Harness. It now sends the configured bearer token and reports a rejected token separately from an unreachable Harness. The README installs application.yml as 0600 owned by the service account instead of 0640 root:group: the control plane reads it as that account and the preflight rejects group-readable secret files. It also documents the ProtectHome constraint on the CLI location and the port-advance case. |
||
|
|
f8b3031f73 |
fix(ci): repair Java gates and report failures on PRs (#14522)
* fix(ci): surface Java failures on PRs and manual main runs
* fix(ci): address review findings on PR failure reporting
- Gate the PR failure comment on infra_only like the issue path: an
infrastructure-only death is rerun once, not reported, and the comment
has no correction path once the retry goes green.
- Tolerate a missing or truncated run-jobs.json in the comment command so
an API failure degrades to naming no job instead of failing analyze and
losing both the PR comment and the issue.
- Drop the leftover 'main-branch ... on main' wording from the per-commit
issue body now that merged-PR failures reach it.
* fix(managed-agent): keep daemon recovery waits re-driven and deflake hosted tests
The daemon refuses a lifecycle phase with hosted_lifecycle_recovery_required
while it settles a previous prompt. The coordinator classified that refusal
as a hook refusal and published the operation RECOVERY_BLOCKED, which the
lifecycle store then refused on every later attempt even after the daemon
recovered. Treat it like the other self-clearing waits: budget-exempt, with
the daemon's own code once past the budget, still re-driven by the recovery
scan.
Deflake the gate's remaining timing-sensitive spots: give the Hosted close
the dispatch backoff's own window, allocate fault-gate ports below the
ephemeral range, widen the action renewal lease, and size the SSE stall test
off a 1200ms idle budget so a loaded runner can still answer inside it.
The E2E freeze loop now waits up to 5s for InnoDB transactions to drain.
* fix(ci): resolve fork approval runs in the Java failure reporter
An approval run records the base repository as head_repository even when
the PR head lives in a fork, so the owner:branch listing filter found
nothing and fork approval failures produced no comment, no issue and no
diagnostic. Resolve by exact head SHA instead, fail soft with a warning
when the listing errors or nothing resolves, exclude closed-unmerged PRs,
gate the infrastructure rerun to destinations that file, and publish the
issue url before the route edit can abort the step.
* fix(ci): preserve PR failure reports across issue and runner errors
* test(sdk-java): wait for the re-registered attachment's heartbeat
The close-by-id success arm asserted a new heartbeat inside a fixed 60ms
window while the heartbeat interval is 10ms. A loaded runner delayed the
periodic task past that window and failed the assertion on main, though
the attachment was still beating. Poll for the next beat with the same
5s bound the other heartbeat waits use.
* docs(managed-agent): name the recovery wait in the public contract and pin the wrapped refusal
The contract's failure_code description enumerates the codes a
recovery_blocked lifecycle operation reports; the daemon-recovery wait
this change published added a third code without touching that text or
the contract version. Name hosted_lifecycle_recovery_required in both
the public and WebShell descriptions and bump the contract to 1.63.0
with a version note; regenerate the WebShell types.
The recovery-refusal test threw the refusal directly, but the SDK wraps
it in MutationOutcomeUnknownException once the non-idempotent detach may
have been delivered — the wrapped shape is why daemonRefusalCode walks
the cause chain. Parameterize the test over the wrapped shape so a
regression that stops unwrapping fails it.
* fix(ci): cover SDK lock holds and refresh InnoDB freeze probes
* test(managed): allow writer grant latency in failover fixtures
* fix(managed-agent): name the refusing store route and pair writer grants with store heads
The Hosted failover lane failed with the store's shared writer-conflict
text and no way to tell which operation with which grant was refused
(review #14522's discriminator: a grant the wake never re-acquired vs a
grant the store already judged superseded).
ManagedSessionStoreHttpError now carries the refusing store route in its
own field (public messages pass through unchanged). On a 409 the writer
annotation captures, sanitized (boot id, never a token), the grant the
writer presented and the store-head generation/revision/committed/state
verdict probed at the same instant — reaching it from a commit retry and
from the renewal path that otherwise swallows the refusal silently. The
daemon's recovery print and turn-failed print render status, stable
remote code, route and that pairing.
Also log the sanitized acquiredWriterGeneration and the restore-head
match pair on acquire/restore, so a healthy takeover proves from both
sides that the previous boot's writer stayed fenced.
* test(managed): establish journal ownership before broker outage
* test(cli): wait for startup and saved-plan recovery before asserting
* test(sdk-java): wait for heartbeats instead of fixed windows
* test(managed-agent): isolate automation fault fixtures and await deadlock recovery
* test(ci): pin the awaitility fail-fast guard, not its exact prefix
`HostedTeamLeadMySqlIT`'s waiting helper now configures
`ignoreExceptionsInstanceOf(CannotAcquireLockException)` before chaining
`failFast`, so the class no longer contains the literal
`await().failFast("The case's @Timeout interrupted it"` and the
hosted-process-ci guard failed on the merged head.
The invariant is that every wait goes through the helper that fails on the
`@Timeout` interrupt; the single-`await()` assertion still pins that. Match
the failFast guard itself so configuring awaitility first stays legal.
* fix(ci): record the current workflow size in .size-baseline
The ratchet is meant to turn workflow growth into one reviewed line, but
main-ci-failure-issue.yml landed at 23219 bytes against a recorded 20935,
so the 2284-byte remainder rode inside the 4096-byte allowance unrecorded
and the grow-by-adding-a-step path stayed invisible.
Record the byte size the file actually has at this head.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvdoiklbf5
* fix(managed-agent): normalize boolean literals in schema checks
* test(managed-agent): isolate destructive schema validation
* fix(ci): release cancelled fast PR gate concurrency
* fix(ci): release PR gate when tier selection is cancelled
* chore(web-shell): regenerate managed agent API types
The merge of origin/main brought a new
`hosted_lifecycle_recovery_required` clause into the Managed Agent OpenAPI
description, but the committed `managed-agent-api.ts` was resolved by hand
instead of regenerated, so `managed-agent-api.test.ts` drifted from its
generator and failed the required Test lane.
Regenerated with `node scripts/generate-managed-agent-api.mjs`; the only
change is the aligned description line.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmveoyarfh8
* fix(managed-agent): restore the daemon recovery wait in failure_code docs
The
|
||
|
|
3dca42c11e |
fix(ci): skip application tests for Markdown-only and CODEOWNERS-only changes (#14284)
* fix(ci): avoid full application tests for CODEOWNERS-only changes Keep ownership-only changes on lightweight CI without downgrading automatic review. * docs(ci): document GITHUB_CI_ONLY_FILES membership rule * fix(ci): skip application tests for Markdown-only changes on any path Only docs/**.md and a few root prose files classified as docs_only, so a PR touching nothing but a package README, a .qwen skill or AGENTS.md still ran the full unit and no-AK integration suites. Markdown on any other path now selects github_ci_only: application tests are skipped and automatic review stays at full effort, since much of that Markdown is runtime instructions that must not share docs_only's review downgrade. Markdown under src/ or a fixtures tree keeps the full profile: it ships in packages or feeds test contracts, and colocated tests assert its content (bundled SKILL.md tests, the review skill parity check). Mixes of docs, such Markdown and helper files also avoid application tests. * test(ci): pin CODEOWNERS mixed with docs to the lightweight profile Docs, test-free Markdown and helper files in any combination now avoid application tests, so CODEOWNERS plus README no longer needs full CI. * Revert "test(ci): pin CODEOWNERS mixed with docs to the lightweight profile" This reverts commit |
||
|
|
c1b012539b |
fix(ci): repair daily failure reporting and contract guards (#14238)
Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
d5c22d336b |
ci(sdk-java): guard Flyway version uniqueness and watch red main runs (#12965)
* ci(sdk-java): guard Flyway version uniqueness and watch red main runs
Two in-flight PRs each added a V16 managed-agent-server migration and
merged 23 minutes apart; each branch was green in isolation, and Flyway
refused to boot the merged result, leaving main red for two hours while
three more merges stacked on it (#12940).
A new flyway-migrations job runs scripts/check-flyway-migrations.js on
every SDK Java pull request and push: a filename scan of the two
migration locations that share one version sequence, failing in seconds
and naming both files when two claim one version. Versions compare the
way Flyway compares them — numerically per segment — so V016 collides
with V16, and a location rename that would empty the guard fails instead
of passing vacuously.
The main-CI failure watcher now watches the SDK Java workflow, whose
path-filtered push run on main is the only signal for a merge result
neither PR could fail, and the failure-signature helper reads Surefire's
per-test error lines, so a red Java lane files one deduped issue naming
the failing tests instead of a per-commit stub.
Refs #12940
* ci(sdk-java): harden the Flyway guard and dedupe its main-red issues
Review on #12965:
- compare versions across every module of one invocation — Flyway resolves
classpath:db/migration across jars, so a version claimed in two modules
collides exactly like two files in one module
- fail when the SQL location yields no migration even if the Java location
still does, so renaming one location away can no longer pass vacuously
- escape %/CR/LF in module names and file paths before emitting ::error::
(the runner parses workflow commands from stderr too, and the job scans
contributor-controlled filenames on pull_request)
- match the migration suffix case-insensitively, as Flyway does
- keep the fully qualified Surefire id as the dedupe identity so same-named
tests in different packages no longer merge into one issue; the cosmetic
package strip moves to shortenForTitle
- give the guard's ::error:: line a stable flyway:<module>:<version>
identity, so a standing red dedupes to one issue instead of one per
stacked merge, and render the failed jobs in the test-keyed body
- pin the workflow_run watch entry to sdk-java.yml's name key
Co-authored-by: Qwen-Coder <[email protected]>
* ci(sdk-java): fix the Flyway dedupe on runner-rendered logs and close review findings
The Actions runner consumes ::error:: workflow commands and writes
##[error] into the downloadable job log, so the dedupe pattern anchored
on ^::error:: never matched production input and every merge stacked on
a standing red would have opened a fresh autofix issue. The anchor now
accepts both forms, the guard's two non-collision failure modes (moved
migration location, missing module directory) get their own stable
module+mode identity, and the synthesized flyway id is written to be
read since it doubles as the issue title and body bullet.
The issue body's "- Failed jobs:" block is now machine-owned like
"## Also failing": stripped and re-emitted from the current run on every
merge instead of freezing the lanes recorded at filing time, with the
empty-list case pinned against a dangling heading.
The guard itself now reports a multi-module collision once after all
modules are scanned, naming every claimant (a second line would file a
second issue), never prints a clean summary for a claimant, and only
requires the SQL location of a module that owns a db/migration*
directory — which lets the workflow invocation cover runtime-broker, the
module that shares the classpath:db/migration namespace without carrying
migrations today.
escapeWorkflowCommand moves to scripts/release-script-utils.js (the
byte-identical copy would have drifted from the tested original), and
the filename-forgery tests gain a runner-decoding oracle and a
capability probe so the Windows lane skips instead of failing on
filenames Win32 cannot hold.
Co-authored-by: Qwen-Coder <[email protected]>
* ci(sdk-java): close the guard's blind spots and validate its dedupe captures
Review and sandboxed verification on #12965 found the moved-location
probe keying on the two exact directory names: a rename outside the
migration* family (db/migration -> db/changelog) passed silently with
the whole sequence inside it, a renamed Java location printed "all
versions unique" from a scan that skipped it, and a Java-only module
red-lit as "moved" with its real collision never named. The probe is
now derived from the LOCATIONS table: a versioned-migration file
anywhere under a source root but outside the configured location fails
with the files named, the sibling check is per location and fires only
when the location directory itself is gone, and a repeated module
argument no longer fabricates a collision of a file with itself.
The invocation now names qwencode too — managed-agent-server depends on
it at compile scope, the same namespace premise that admitted
runtime-broker — and the .size-baseline entry is re-recorded at the
file's current size.
On the consumer side, both Flyway patterns feed their captures through a
module-shape validation before they become a dedupe identity, so a
filename-borne forgery rendered by the runner's annotation decode can no
longer inject a second identity into the issue this files, and the
guard's run-level diagnosis is ordered ahead of first-seen test ids so a
mass failure in another lane cannot crowd it past the search and body
caps. A closure pin keeps the spawned guard importable in the
dependency-free helper lane.
Refs #12940
* ci(sdk-java): close the Windows-drive forgery and split the stray-file message
The drive-prefix alternative in the module validator re-opened the
forgery it closed: backslash and colon are ordinary characters in a
POSIX filename, so a migration name could supply a `C:\evil` prefix and
pass the path-shape check. No lane that runs this consumer ever
produces a backslashed module — the producer and analyze jobs are
ubuntu-latest and the suite runs only on the Linux lint_and_static
lane — so the branch is dropped, backslashes are rejected outright, and
the fixtures hand the guard a slash-bearing argument (a no-op on POSIX)
instead of teaching the consumer a shape only a forgery can produce.
The unsafe-character half of the validator is now pinned for
whitespace, backticks and backslashes across both call sites.
A populated location with a migration-shaped stray elsewhere under the
source root no longer prints "found no migration under": the guard
names the stray as the cause, and the consumer's config identity learns
the new phrase, so the standing red still files one issue. The dep-free
closure pin now walks the guard's relative-import closure instead of a
hardcoded depth-one list, so a new hop carrying an npm import cannot
slip through.
Refs #12940
* ci(sdk-java): re-record the sdk-java.yml size baseline after merging main
The rebase onto
|
||
|
|
64dd058c9c |
ci(pnpm): install with pnpm everywhere and retire package-lock.json (#11859)
* ci(pnpm): install dependencies with pnpm in CI and release CI tests the graph release ships only if both install it the same way, so this moves both, keeping every `npm run` script and `npm publish` as they are. - ci.yml (7), release.yml (3) and release-vscode-companion.yml (2) install with `corepack pnpm install --frozen-lockfile`; release keeps --ignore-scripts plus its explicit postinstall and generate steps. - setup-node drops the npm cache and sets package-manager-cache: false, which setup-node v5+ needs once package.json declares a pnpm packageManager. - A composite action caches the pnpm store on the three always-hosted jobs; the ECS pool keeps its store on local disk. - version.js no longer lets npm reify node_modules: --no-workspaces-update on both `npm version` calls, and a package-lock-only refresh in step 9. - The notices generator, prepare-package's sharp pin and the standalone release's native package specs read the installed tree or pnpm-lock.yaml instead of package-lock.json keys. - New size baselines for the three workflows, which were already over them. * chore(vscode): regenerate NOTICES from the pnpm-installed tree Generated in CI (pnpm Worktree Smoke on ubuntu-latest) by the ported generator. Same entry set as before except the five packages npm and pnpm resolve differently: fdir 6.5.0, picomatch 4.0.5, mdurl 2.1.0, @types/node 22.20.1 and mime-db 1.52.0 (form-data's pinned copy). No entry lacks a license text or repository. * fix(pnpm): resolve undeclared zod peers to the repo's zod, as npm does The first CI run on this branch failed the export renderer's size budget: 1,930,015 bytes against 1,930,000, where the same commit built on npm's tree comes to 1,858,186. Comparing both trees' esbuild metafiles and prebuilt bundles put the whole difference in web-shell's transcript.js (+100,903 bytes pre-minify), and the added code is zod 4 plus the @modelcontextprotocol/ext-apps schemas; the sdk daemon bundle is identical. web-shell bundles ext-apps, whose zod and MCP SDK are peers, and declares no zod itself, so pnpm filled the peer with the highest locked zod (4.4.3) while npm serves the hoisted 3.25.76. Declaring zod 3.25.76 as a root devDependency makes pnpm resolve every such peer to it (resolve-peers-from-workspace-root is on by default), which is npm's hoisting behaviour. mobile-mcp keeps its own zod 4.4.3, as under npm; the npm tree does not change. * fix(pnpm): keep npm's root @types/node so root-level typechecks match The no-AK integration gate typechecks integration-tests/, which takes Node's types from the root node_modules. npm hoists @types/node 20.19.1 there, because acp-bridge and core pin it; pnpm, with no root declaration, hoisted 22.20.1, whose spawnSync overloads type process-registry.ts's stdout as string | NonSharedBuffer and fail TS2322. The same code passes on every npm-installed PR. Declaring 20.19.1 as a root devDependency puts npm's copy back where a root-level tsconfig looks. The npm tree does not change. * ci(pnpm): install with pnpm in the remaining workflows The follow-up to switching CI and release: every other workflow that installs the root workspace now uses the pinned pnpm with --frozen-lockfile, so no job tests a graph that release does not ship. - e2e, tui-parity, repo-hygiene, web-shell-visuals, serve-ab, sdk-java, windows-runner-smoke, qwen-autofix, the SDK releases, finalize-release, sync-release-to-oss, the root install in desktop-release and cd-cua-driver, and the sandbox Dockerfile. - setup-node drops the root npm cache (package-manager-cache: false); hosted jobs restore the pnpm store through the shared composite action, and qwen-autofix keeps af-012's rule that the persistent pool restores no remote cache. - serve-ab and web-shell-visuals install a separate base checkout that can predate pnpm-lock.yaml, so those installs fall back to npm ci there. - Out of scope, unchanged: packages with their own lockfile (cua-driver's TypeScript SDK, desktop-shell, live-host, mobile-mcp), and the triage lanes with npm-cache.yml and the npm audit gate, which move with the package-lock.json retirement. - New size baselines for the touched workflows; qwen-autofix stays under the 470000-byte gate. * fix(pnpm): link the workspace packages that root-level code imports npm links every workspace package into the root node_modules, so integration-tests/ and scripts/, which are not workspace packages themselves, can import @qwen-code/* by name. pnpm links a workspace package only where it is a declared dependency, and the root declared none, so the no-AK integration gate could not find @qwen-code/qwen-code-core. The root now declares the seven packages that root-level code imports at runtime as file: devDependencies: acp-bridge, channel-base, the CLI, core, sdk, web-shell and web-templates. npm already links them, so its tree does not change (letting npm re-resolve the lock reproduces the same root entry); .pnpmfile.mjs rewrites them to workspace:*, which pnpm links at the root. * ci(pnpm): move triage, the store producer and the root audit to pnpm - qwen-triage's verify and tmux-testing lanes install the PR with `corepack pnpm install --frozen-lockfile` from a restored pnpm store. The one retry clears node_modules first, because pnpm install, unlike npm ci, keeps it. - npm-cache.yml becomes pnpm-store.yml, keyed on pnpm-lock.yaml. - security-checks audits the root workspace with `pnpm audit`, which reads the lockfile, so the root install step goes. The vendored lockfiles keep npm, and the retry recognises pnpm's endpoint error too. - qwen-autofix drops the hosted-only store cache: its heavy jobs forbid local `uses: './...'` actions, and the file sits at the size gate. - cd-cua-driver: setup-node@v4 has no package-manager-cache input. - package-assets fixtures install sharp rather than listing it in package-lock.json, matching how prepare-package now resolves it. - AGENTS.md, CONTRIBUTING.md and the npm workspace guide install with pnpm. * feat(review): install pnpm repos in qwen review's build/test qwen review installed dependencies only for a repo with package-lock.json and reported a pnpm repo as unsupported, so reviews of qwen-code would lose their build and test once its npm lockfile goes. - lockfileInstaller() picks npm or pnpm from the committed lockfile. With both, `packageManager: pnpm@...` chooses pnpm; anything else keeps npm. - A pnpm repo installs with `corepack pnpm install --frozen-lockfile` through the same disk, budget and sandbox path as `npm ci`, and counts as installed once node_modules/.modules.yaml exists. - Scripts still run through `npm run`, and the npm path is unchanged. * ci(pnpm): retire the root package-lock.json pnpm-lock.yaml is now the only root lockfile. - check-lockfile keeps the pnpm integrity check and Playwright parity, which now reads pnpm importers and snapshots. The npm-vs-pnpm agreement and build-approval checks go: pnpm 11's strictDepBuilds and every CI install's --frozen-lockfile enforce what they covered. - pnpm-lock-freshness.yml goes; it regenerated pnpm-lock.yaml from package-lock.json. - Release: version.js drops its package-lock refresh, the release commit stages pnpm-lock.yaml and the SDK release stages no lockfile, so neither `git add` fails on the missing file. `npm version` in the SDK and mobile-mcp releases passes --no-workspaces-update so it cannot reinstall the root with npm. - cd-mobile-mcp installs from the root pnpm lockfile. setup-node in cd-mobile-mcp, qwen-code-pr-review and desktop-release no longer keys an npm cache on the root lockfile. - check-desktop-isolation reads pnpm importers; build.js, build_sandbox.js and preflight install with pnpm. - pnpm-lock.yaml, pnpm-workspace.yaml and .pnpmfile.mjs join the autofix supply-chain class and repo-hygiene's deny list; pnpm-lock.yaml joins autofix's generated-diff exclusions, sdk-java's path filter and the platform-sensitivity manifests. - .gitignore lists /package-lock.json so an npm install cannot commit it back. Tests and docs follow. * fix(standalone): read the node-pty versions from the pnpm lockfile * chore(vscode-ide-companion): regenerate NOTICES.txt after the lockfile swap * chore(vscode-ide-companion): regenerate NOTICES.txt from the pnpm dependency tree The committed NOTICES.txt was generated from an npm install and does not match the tree produced by the pnpm install the release pipeline now uses (659 resolved dependencies, differing @types/node and mime-db versions). Regenerating with the pinned pnpm install makes the freshness check reproducible. * fix(ci): drop the npm cache from the daily dependency audit actions/setup-node's npm cache keys off the root package-lock.json, which this branch retires, so the scheduled job would fail at Set up Node before the audit ran. No pull_request run reaches this workflow to catch it. The root tree is never installed here either: pnpm audit reads pnpm-lock.yaml directly. Matches the sibling workflows that already set package-manager-cache: false; scripts/tests/security-workflows.test.js now pins it. * chore(ci): stop banking main-side drift in the workflow size ratchet The ratchet exists to turn a workflow's growth into one reviewed line. This branch re-recorded entries for 17 workflows it never touches, several upward by 0.4-2.8 KB - drift that happened on main, banked into the manifest without review. Restore those to their merge-base values, so .size-baseline changes only for the 22 workflows this branch actually edits. The entries were not needed to go green: ci.yml passes the PR's base commit, so a file that drifted on main and is otherwise untouched earns a warning, not a failure. * fix(release): initialize standalone state before main * test(browser-use): read Playwright pin from pnpm lock * fix(ci): address pnpm migration review blockers * fix(desktop): stop reading the retired root lockfile --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
32f51a330b |
chore: restore regression coverage after WebUI retirement (#11101)
* chore(web-shell): follow up #9812 review findings and deferred suggestions Address the remaining review suggestions from the @qwen-code/webui retirement (#9812) and the deferred items tracked in #11076: - Fix the 01-architecture.md adapter diagram path (missing session/). - Correct the followup-suggestions doc: generation fires on end_turn, and headless/SDK daemon clients should opt out to avoid the per-turn LLM cost. - Pin playwright to 1.61.1 to restore version parity with @playwright/test. - Restore file: scheme rejection coverage in Markdown.test.ts. - Pin the historical sessionStorage key prefix with a literal assertion. - Cover the legacy exported JSONL rejection and ChatRecord happy path. - Restore the shell-substring classifier trap with a live path. - Point useDaemonFollowupSuggestion docs at ChatEditor, not the deleted InputForm. - Replace the theme-toggle timing-race assertion and exercise the unhandledrejection fail-closed path in the document browser gate. Closes #11076 * fix(test): poll the theme class instead of a matcher vitest lacks The browser gate imports expect from vitest and drives playwright's Locator directly, so toHaveClass does not exist on the assertion and typecheck:integration failed with TS2339 on this line. Poll the class attribute through the same expect.poll idiom the neighbouring assertions use, which keeps the retry the assertion was switched to and needs no @playwright/test dependency. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-conflict/jmtooas8sln * fix(test): assert the exported theme class exactly `document-main.tsx` toggles `dark` and `light` on <html> mutually exclusively and nothing else writes `documentElement` classes, so the attribute is always exactly one of them. Matching `/light/` as an unanchored substring would also accept `dark light`, which is the theme-toggle breakage this poll exists to catch. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtow5pgslz * fix(test): realpath the exporter main check and pin its JSONL guard Node realpath-resolves `import.meta.url` for the ESM main module but leaves `process.argv[1]` as invoked, so comparing the raw spellings made a symlinked invocation skip `main()` and exit 0 having written nothing — a silent no-op the harness reads as success. Compare realpaths instead and export the predicate so both directions are pinned. Also extract the input gate into `assertRenderableJsonl` so the legacy exported-JSONL rejection can be asserted directly: the previous tests only pinned the two predicates feeding it, so deleting the guard and losing the remediation hint kept every test green. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtow5pgslz * docs(followup): name the real ACP stop reasons, drop "unconditional" The parenthetical this PR added named `interrupted` as a daemon stop reason. The ACP `StopReason` union is `end_turn | max_tokens | max_turn_requests | refusal | cancelled`, so there is no `interrupted`; an interrupted turn reports `cancelled`. Name the four suppressing values the union actually defines so an integrator writing `stopReason === '...'` gets a branch that can fire. "unconditional" was wider than the code and contradicted the condition list on the same page: `Session.ts#maybeEmitFollowupSuggestion` returns early on the stop reason, the todo stop guard, `ui.enableFollowupSuggestions`, PLAN approval mode, a missing chat, and a non-model last history entry. Point at those conditions instead; the cost advice still holds because the `enableFollowupSuggestions === false` return sits before `generatePromptSuggestion` is reached. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtp6vibomd * test(web-shell): drop the duplicate historical-key assertion `persists under the literal historical key prefix`, added on this branch, became a byte-equivalent twin of `writes under the historical WebUI key`, which reached main with #11107 and arrived here through the merge at this PR's head: same `persistStableClientId('client-a', 'session-a')`, same literal `qwen-code-webui-client-id:session:session-a`, same expectation. One fact was reported under two names, so a prefix rename reddened two tests and an auditor could not tell which copy was load-bearing. The surviving test's comment already carries the rename rationale, so dropping the copy loses no coverage. Verified with `cd packages/web-shell && npx vitest run client/daemon/session/clientLifecycle.test.ts`: 19 passed before, 18 passed after. Mutating SESSION_CLIENT_ID_STORAGE_PREFIX to `qwen-code-webshell-client-id:session:` after the dedupe still reddens 3 tests (15 passed), including the surviving `writes under the historical WebUI key`; reverting the mutation returns to 18 passed. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtp6vibomd * test(integration): drop the unreachable unhandledrejection arm The block this PR added after the CDN-failure loop could not reach the `window.addEventListener('unhandledrejection', showLoadError)` handler its comment claimed to exercise. `page.route('**/*', route => route.abort('blockedbyclient'))` fails the `<script id="transcript-renderer">` load, the template's capture-phase `error` listener (document-index.html:45-56, matching `event.target.id === 'transcript-renderer'`) calls `showLoadError()` first, and `setContent(..., { waitUntil: 'load' })` only resolves after that subresource has failed - so `data-render-complete='error'` and the alert text were already in place before `page.evaluate` dispatched anything, and showLoadError's one-shot guard (`if (document.body.dataset.renderComplete) return;`, document-index.html:30) made the synthetic rejection a no-op. The block was a strict duplicate of the loop's own `rendererBody === null` iteration: identical all-abort route, identical two assertions. Exercising the handler for real needs a renderer body that passes the document's pinned SRI integrity and then rejects during boot; no body this gate can serve passes SRI, so the arm cannot be made honest here. Deleting it returns the file to origin/main's state for this test - the handler and its missing coverage are both pre-existing on main (packages/web-templates is not in this PR's diff), so no coverage this PR was responsible for is lost. Verified: `npm run typecheck:integration` reports 0 errors in this file (6 remain, all in packages/** against generated/dist paths absent from an unbuilt worktree: channels/feishu, cli/src/generated/git-commit.js, web-templates/src/generated/*); `npx prettier --check` and `npx eslint` clean on the file. The browser gate itself was not executed locally - it needs the full build chain plus Chromium. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtpfg5ewmp * ci(review-context): route concurrent-runner changes to the helper-tests lane This PR added `integration-tests/concurrent-runner/export-html-from-chatrecord-jsonl.test.mjs`, the first `node:test` file under `integration-tests/`, and wired it into CI through the `HELPER_TESTS` env list in `.github/workflows/ci.yml`. The tracked review-context manifest still mapped the whole `integration-tests/**` path to `recommendedTests: ['integration-tests']`, whose vitest lane collects `**/*.test.ts` only (`integration-tests/vitest.config.ts:21`), so a future PR touching only the exporter and its `.mjs` test is told to run a lane that cannot collect the file - the same silent drop `HELPER_TESTS` exists to prevent. Add a narrower rule for `integration-tests/concurrent-runner/**` recommending `helper-tests`. Matching rules merge rather than first-match, so the lane list for that directory becomes the union and every other `integration-tests/` path is untouched. The pinned `expectedManifest` in `manifest-repository-context.committed.test.ts` moves with it, which is what that pin is for. Verified through the real provider (tsx importing `packages/cli/src/commands/review/lib/manifest-repository-context.ts`) because the packages/cli vitest globalSetup gate refuses to run on this unbuilt worktree: concurrent-runner .mjs -> ["helper-tests","integration-tests"] concurrent-runner .js -> ["helper-tests","integration-tests"] chat-transcript-document.test.ts -> ["integration-tests"] (unchanged) all 10 rules co-matching -> non-null, 80 relatedPaths, and every `paths` probe matched some rule Also checked the committed manifest deep-equals the test's pinned literal (10 rules on both sides), and that dropping the new rule breaks that equality, so the pin is not tautological. `npx prettier --check` and `npx eslint` clean on both files. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtpfg5ewmp * fix(deps): sync pnpm lockfile for Playwright pin --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
5b470befa0 |
ci: make the Prettier lane a real gate (#11117)
* chore: format the 35 files CI had been silently rewriting `scripts/lint.js --prettier` and the release workflow's `Format Project` step both run `prettier --write`, which reformats in place and exits 0. Neither is followed by a dirty-tree check, so the rewrites are discarded when the job ends and these files have stayed unformatted on `main`. This is the output of `prettier --write` over exactly those files, so the next commit can turn the check into a real gate without failing on a backlog. No behaviour change: every hunk is whitespace, quoting or wrapping. Verified with prettier 3.6.1, the version pinned in package-lock.json. `prettier --check .` over the whole repo reports these 35 and nothing else. Refs #11109 * fix(ci): make the Prettier lane a real gate `runPrettier()` ran `prettier --write .`. Prettier exits 0 after reformatting, and no caller checks the tree afterwards, so the lane passed on unformatted code and CI threw the rewrites away — the 35 files in the previous commit are what it had been silently fixing and discarding. ESLint does not cover the gap: eslint.config.js loads eslint-config-prettier, which disables formatting rules rather than enforcing them. With this change the only enforcement point outside the husky pre-commit hook actually enforces something. `npm run lint:all` now reports unformatted files instead of fixing them; `npm run format` is the fix. Prettier's own failure message says so. Refs #11109 * fix(release): drop Format Project, it writes and discards The step ran `npm run format` (`prettier --write .`) and nothing in release.yml inspected the tree afterwards — no `git diff --exit-code`, no `git status --porcelain` anywhere in the file. The rewrites died with the job, so the only failure it could produce was Prettier crashing on an unparsable file, which the Run Lint step reports anyway. It cost 13m58s on a quiet host and 20m45s on a contended one, against a 30-minute cap that `quality_static` hit twice today (runs 33957952281 and 33963757913, both cancelled mid-lint with nothing failing). The remaining steps are `npm ci` (1m44s) and `lint:ci` (~8m20s), so the job now has real headroom instead of finishing 24 seconds over the wall. Formatting is now gated where it belongs: the previous commit makes the PR-CI Prettier lane `--check`. Refs #11109 * fix(ci): keep prettier --check out of .qwen/ and the pre-paint theme script Two things the `--write` → `--check` flip broke that `--write` had hidden. `.qwen/` is gitignored as `.qwen/*` and holds machine-written state — pending-skills/, auto-skill-*/, learned-skill-*/, computer-use/installed.json. Prettier does not read .gitignore, so it was walking in and silently reformatting whatever a contributor had there; under `--check` that turns into a failed `npm run lint:all` on files git does not even track. CI never saw it (fresh checkout, tracked files only), which is exactly why it went unnoticed. Adding `.qwen/` to .prettierignore also drops the one tracked file under it — skills/find-simplifications/SKILL.md, force-added past .gitignore — from the previous formatting commit, since a directory the gate should not police should not be reformatted for it either. The inline theme script in web-shell's index.html is restored to its original single line behind `<!-- prettier-ignore -->`. It is a render-blocking script in <head> that applies the stored theme before first paint; the expanded form ships ~600 more bytes on the critical path and buys no readability the comment above it does not already give. A change about CI hygiene should not be reshaping that. Refs #11109 * fix(ci): keep prettier --check out of vendored packages/mobile-mcp eslint.config.js has ignored `packages/mobile-mcp/**` since it landed — "vendored mobile-next/mobile-mcp; has own eslint config" — and .prettierignore already carries its sibling `packages/cua-driver/`, listed one line above with the same rationale. mobile-mcp was simply missed. `--write` hid that: it reformatted the vendored package's own eslint config to our house style every run and threw the result away. The formatting commit in this PR would have made that permanent, widening the diff against upstream on the next sync for a package this repo has already decided not to lint. That file is reverted here and the directory is ignored. Same class as the .qwen/ fix in the previous commit: the gate reaching somewhere the repo had already decided it should not. Refs #11109 * fix(ci): mirror eslint's remaining global ignores in .prettierignore `--check` makes .prettierignore load-bearing in a way `--write` never was: under `--write` a file prettier should not touch was reformatted and the result discarded, so nobody noticed; under `--check` it fails the lane. These three are the rest of eslint.config.js's global ignores, added for the reasons it already states. `scripts/codemod/fixtures/` is the one that matters: those files are "intentionally non-idiomatic ink input/output", so the obvious response to a `--check` failure there — reformat to house style — would corrupt the fixtures the codemod tests assert against. The trap is not live today (all three currently satisfy prettier), which is precisely why it is worth closing before someone meets it. Refs #11109 * chore: format converter.test.ts, which drifted onto main since this branch This branch merged main 32 commits after its base and `prettier --check .` found one new offender. It is the same gate failing the same way: nothing enforces formatting, so `--write` reformatted this file on every CI run since it landed and threw the result away. Worth noting as evidence rather than noise — the backlog this PR fixes was not a one-off from before someone got careless; it accrues continuously, and it accrued again while the PR was open. Refs #11109 * Revert "fix(release): drop Format Project, it writes and discards" This reverts the release.yml half of this PR. #11195 removes that step on its own, in one file and four lines, and two PRs racing on the same deletion helps nobody. The reasoning and the evidence are unchanged and now live on #11195: the dry-run release dispatched from this branch with the step removed (34022919438) went 18/18 green in 65m43s, quality_static finished in 7m00s against main's 8m57s, and Run Lint was 4m52s vs 4m43s — nothing downstream ever read what the step produced. Dropping release.yml from this PR also widens who can review it. It was the only file here owned by @pomelo-nwu and @wenshao alone; what remains under CODEOWNERS is /packages/core/, whose owner list is six people. The Prettier gate no longer waits on the two maintainers who must sign off on release workflow changes. Refs #11109, #11195 * fix(ci): make the gate resolve the file set npm run format resolves Two review findings, one root cause: the gate was checking a different set of files than the tree it is supposed to police. **The `.qwen/` ignore rested on a false premise (R1-1, @chiga0).** Prettier 3.6's `--ignore-path` already defaults to `[.gitignore, .prettierignore]`, so every machine-written path that entry's comment named — `pending-skills/`, `auto-skill-*/`, `learned-skill-*/` — was outside the gate before it via `.gitignore:32`. What the entry actually excluded is the corpus `.gitignore` deliberately re-includes: `.qwen/commands/**`, `skills/**`, `agents/**`, `team-memory/**`, `review-context.json` — 57 tracked, hand-authored files. The gate then passed because the surface had been narrowed, not because the tree was formatted, and contributors editing those docs could ship them unformatted with both enforcement points silent. The entry is dropped and the first commit's formatting of `skills/find-simplifications/SKILL.md`, reverted alongside it, is re-landed. **The gate and the remedy used different CLIs (R1-2).** `runPrettier()` ran the classic `prettier --check .` while `npm run format` runs `prettier --experimental-cli --write .`. The classic CLI reads only the repo-root `.gitignore`; the experimental one also honours nested ones, and this repo has 23 of those. Verified on a scratch tree: given `pkg/.gitignore` containing `lib/`, classic reports `pkg/lib/nested.js` and experimental skips it. So the lane could go red on a locally built package's output that `npm run format` provably cannot clear. The gate now uses the same CLI as the remedy it names. Refs #11109 * fix(ci): let the pre-commit hook cover what the gate now checks, and pin it **The hook was narrower than the gate (R1-5).** `lint-staged` formatted only `*.{js,jsx,ts,tsx}` and `*.{json,md}`, but the gate checks every extension Prettier has a parser for. Of the 35 files the first commit formatted, 29 (83%) are in classes the hook never touched — 13 `.yml`, 12 `.mjs`, 3 `.css`, 1 `.html`. Left alone, the backlog this PR just cleared would re-accumulate, and now as a red CI step on someone else's PR rather than as silent drift. `.mjs`, `.cjs`, `.yml`, `.yaml`, `.css` and `.html` join the Prettier-only group; ESLint's globs are unchanged, since the gap is about formatting coverage, not lint coverage. **Nothing pinned the flag (R1-4).** `scripts/tests/lint.test.js` exercised only the linter-directory helpers, and `ci-platform-lanes.test.js` asserts that the `Run Prettier` step exists and is profile-gated, not what it runs. A refactor, or a well-meant "make lint auto-fix again", could restore the silent pass with every suite green. Two assertions now pin it: that `runPrettier()` checks rather than writes, and that it uses the same Prettier CLI as `npm run format`, so the two cannot drift apart again the way R1-2 found them. Refs #11109 * docs: correct the instructions and comments the --check flip falsified **Three tracked instructions to automation agents now say the opposite of what the code does (R1-9), and one is fed to a CI lane today** — `.github/workflows/qwen-triage.yml` re-pins `verify-pr` from the base commit and hands it to the verifying agent. - `verify-pr/SKILL.md` told agents never to run `scripts/lint.js` with no arguments because it would rewrite the working tree under their A/B harness. That is no longer why. The prohibition is kept — it is still right — but re-grounded in what the no-arg form actually does now (`setupLinters()` wipes the linter temp dir and re-downloads three pinned binaries), and `--prettier` joins the enumerated non-mutating checks it was the only flag missing from. - `find-simplifications/SKILL.md` and its `references/land.md` told the simplification agent that a formatting diff "carries zero signal" because CI runs `--write`. An agent believing that would skip `npm run format` after a deletion changed line shape — on the very extensions the hook does not cover — and go red on formatting it was told could not matter. - `update-ecs-runner-qwen-workflow.test.js`'s comment justified parsing two matrix layouts by "CI runs `prettier --write .` before this suite". CI no longer rewrites anything; the two layouts are still worth parsing, so the comment is re-grounded in the checked-in form versus older branches. **The index.html rationale overstated its own number (R1-8, @chiga0).** It claimed the expanded script ships "~600 more bytes"; measured against this repo's Prettier 3.6.1 and its `tabWidth: 2`, it is +258 source and +8 gzipped here, +287/+20 by the reviewer's method — either way roughly half what was claimed, and "~600" is what the same expansion costs at `--tab-width 4`. Since the prose ships verbatim inside the built `index.html`, the fix is to drop the contested figure and keep the comment short. The `<!-- prettier-ignore -->` guard itself stays: it is load-bearing. Refs #11109 * chore: format classify-profile.test.mjs, drifted again after commit 1 Third instance in this PR's lifetime, and the sharpest: commit `b1b94a8762` formatted this exact file as one of the 35, and `main` reintroduced unformatted content in it within a day. The other two were `converter.test.ts` at the first merge and this file at the second. The gate is the fix; until it lands, every merge from `main` re-opens the same hole. Refs #11109 |
||
|
|
133a1a39de |
ci: skip product tests for ECS updater maintenance (#10548)
* ci: classify ECS updater maintenance as CI-only Co-authored-by: Qwen-Coder <[email protected]> * fix(ci): harden CI-only classifier trust Co-authored-by: Qwen-Coder <[email protected]> * ci: record ci.yml growth in the size baseline The ci-only-profile wiring added real workflow content; combined with prior main growth this pushed ci.yml 4853 bytes past its recorded 98043, over the 4096 allowance. Bump the ratchet to the current 102896 so the check reflects the reviewed growth. Co-authored-by: Qwen-Coder <[email protected]> * test(ci): gate the classifier trust test to the Linux lane The fail-closed trust test drives the workflow step through `bash -c` with a Unix ':'-joined PATH and an extensionless shebang `gh` stub, which the Windows lane cannot express. Gate it the same way this file's other bash-driven cases already do, keeping the Linux lane as the authoritative coverage (scripts/tests/vitest.config.ts). Co-authored-by: Qwen-Coder <[email protected]> * ci: log the reason behind the classifier trust verdict The permission lookup discarded all diagnostics (2>/dev/null || true) and the log printed only the verdict, so an API outage, an empty permission, and a real below-write permission all produced the identical "PR classifier trusted: false" line. Record which branch made the decision and print it alongside the verdict; the can_trust_pr_classifier output and its fail-closed semantics are unchanged. Bumps the size baseline for the added lines. The trust test's helper now captures the step's stdout, and a new Linux-gated case asserts the denial reasons stay distinguishable. Co-authored-by: Qwen-Coder <[email protected]> * test(ci): pin the remaining classifier trust reason branches The R1-3 reason logging added six per-branch reason strings, but the 'not a pull_request event' and 'PR author missing from the event payload' branches were unreachable from the tests: runClassifierTrust hardcoded GITHUB_EVENT_NAME and PR_AUTHOR, so dropping or swapping either assignment shipped with every test green. Give the helper optional eventName / prAuthor overrides and extend the reason test to assert both branches stay denied and distinguishable; the merge queue exercises the first one on every merge_group run. Co-authored-by: Qwen-Coder <[email protected]> * fix(ci): run PR classifier from trusted base Compute the CI profile once from an immutable base-SHA checkout, then pass the validated output to product jobs without executing the PR head's classifier. All missing, failed, or unknown classifications continue to fall back to full CI. Co-authored-by: Qwen-Coder <[email protected]> * fix(ci): prepare trusted classifier checkout Clean shared-runner residue before checking out the trusted base classifier and update the workflow test fixture to mirror its isolated path. Co-authored-by: Qwen-Coder <[email protected]> * fix(ci): sweep leftover trusted-ci-classifier before checkout actions/checkout keeps an existing checkout directory when its origin URL matches and runs `git checkout --detach` inside it without a core.hooksPath mitigation, so a leftover trusted-ci-classifier from an earlier classify_pr run on the shared ecs-qwen pool would execute whatever hooks residue planted there — code execution through the classifier checkout the sweep already defends. Add the path to the pre-checkout sweep loop in every ci.yml copy (the byte-identity pin keeps them in lockstep) and in qwen-code-pr-review.yml, per the documented third-residue-name contract, and pin the removal behaviorally. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmthmo4kw80 * test(ci): pin the shared-pool ownership restore step's existence The ordering assertion alone survives removing 'Restore workspace ownership': restoreIdx becomes -1 and cleanIdx > -1 still holds, while root-owned leftovers from a containerised job defeat the sweep and the checkout again — the EACCES incident class the step exists for. The step is new in classify_pr on this branch, so pin it explicitly on every shared-pool job. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmthmo4kw80 * fix(ci): pin the profile producer id and log the degraded fallback Two silent degradations in the new profile wiring: - The classify_pr job output resolves against step id ci_profile, but nothing pinned the producer step's id or its GITHUB_OUTPUT write: a rename empties the output and every consumer silently runs the full profile. Pin both on the producer step. - When classify_pr fails (continue-on-error) the consumers coerce the empty output to full and log the exact same line as a legitimate full classification. Emit a ::warning breadcrumb when the producer output is empty, on both consumer copies, and pin it. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmthmo4kw80 * test(ci): pin the profile consumers' allowlist and output write The consumer loop pinned every load-bearing line of both `Use trusted CI profile` steps except the allowlist case line and the GITHUB_OUTPUT write. If a later edit drops the echo in either consumer, steps.ci_profile.outputs.ci_profile becomes empty and every downstream profile gate mis-compares. Extend the pinning test to cover both consumer copies of those two lines (mutation-probed on all four consumer lines). Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmthzj3978p * docs(ci): explain the third residue name in the review sweep comment The sweep loop grew to three names (adding trusted-ci-classifier), but the comment above it still said "those two known names". Update the count and add the explanatory paragraph for the third name, mirroring the ci.yml copy: the residue comes from ci.yml's classify_pr base-SHA checkout on the shared pool, and actions/checkout would reuse it and run git (hooks included) inside it. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmthzj3978p * fix(ci): run only dependency-free helper tests in github_ci_only lane (#10548) The github_ci_only helper step runs before any dependency install — the setup-node and `npm ci` steps are gated on the full profile — but it ran the entire HELPER_TESTS list, 8 of whose suites import the `yaml` npm package. A PR classifying into this lane (e.g. a fork PR touching only .github/workflows/update-ecs-runner-qwen.yml, which this PR admits to the fast lane) failed closed with ERR_MODULE_NOT_FOUND on a fresh runner. Split the list: the fast lane now runs HELPER_TESTS_DEP_FREE (suites whose import closure is node: builtins only); HELPER_TESTS keeps the full list for the full-profile helper step, and the merge queue's full run re-checks what the fast lane skips. A pinning test in ci-platform-lanes keeps the yaml suites out of the fast lane. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtire6cja9 * fix(ci): drop yaml-dependent helper suite from dep-free test lane web-shell-visuals-publish.test.mjs statically imports the `yaml` npm package, but HELPER_TESTS_DEP_FREE runs before any dependency install (npm ci is gated to the full profile), so the suite failed with ERR_MODULE_NOT_FOUND on fresh runners (review feedback from qqqys). Remove it from the dep-free lane only; the full-profile helper lane still runs it. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtj6easob2 * fix(ci): keep yaml-dependent visuals suite out of github_ci_only lane web-shell-visuals-publish.test.mjs statically imports `yaml`, so the dependency-free github_ci_only lane can never execute it. Listing it in GITHUB_CI_ONLY_FILES meant a PR touching only that suite got every required check green without the changed file running once, because the full-profile helper step is gated on ci_profile == 'full'. Drop the entry so such PRs keep the full profile, and pin the classification with a test assertion (verified red with the entry, green without). Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtjj99h9bs * fix(ci): grant classify_pr pull-requests read scope and pin the fast-lane exclusion F1: classify_pr had no job-level permissions block, so its token carried only the workflow-level {checks: write, contents: read, statuses: write} grant. The classifier wrapper calls repos/{}/pulls/{}/files and repos/{}/pulls/{}, which 403 without pull-requests scope and fall back to full CI. Grant contents: read + pull-requests: read on the job and pin the block with an assertion in the no-ak integration test. F7: the fast-lane pinning test enumerated 8 of the 9 yaml-importing suites; add web-shell-visuals-publish.test.mjs (the suite the F6 commit excluded from HELPER_TESTS_DEP_FREE) and fix the comment count, so re-adding it to the fast lane fails the test. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtjlef91bw * ci: give the github_ci_only fast lane a single dependency-free owner lint_and_static's 'GitHub CI helper checks' step ran the full HELPER_TESTS list under the github_ci_only profile, where every dependency install in that job is gated on ci_profile == 'full' - so the 9 suites that statically import `yaml` died with ERR_MODULE_NOT_FOUND on a fresh hosted runner. This branch newly routes ECS-updater and actionlint PRs into that profile, which made the fast lane deterministically red on exactly the flow it exists to serve. Repoint that step at HELPER_TESTS_DEP_FREE and drop the duplicate copy in the `test` job: ci-platform-lanes.test.js reserves that step name for lint_and_static, so the copy also broke the shared-prelude byte-identity guard and the out-of-test pin. Select the fast lane by its gate rather than by the list it consumes, so a regression back to env.HELPER_TESTS cannot drop out of the filter. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmto8kxx1l2 * ci: keep the ECS updater workflow guard reachable from its own fast lane Routing .github/workflows/update-ecs-runner-qwen.yml into GITHUB_CI_ONLY_FILES left its guard unreachable on exactly the PRs that change it. The 21-case vitest suite under scripts/tests/ only runs under the full profile — test:scripts is gated on ci_profile == 'full', and test_macos / test_windows fire only on merge_group|schedule|workflow_dispatch — while the dependency-free node:test replacement was a strict 7-case subset. Renaming the matrix job name template, which breaks the reporter script startswith(poolPrefix) filter, or dropping the issues:write permission from report_failure, merged with every check green. Port the remaining 14 cases into the dep-free suite behind the same win32/jq capability gate the bash replays already used, and delete the vitest copy so the two cannot drift. The suite imports node: builtins only, so it stays inside the HELPER_TESTS_DEP_FREE contract. Verified locally: 21/21 pass, 0 skipped; renaming the matrix job name template reddens "filters the run jobs by the prefix the matrix job actually uses", and dropping issues:write reddens "reports a failed fleet update only when a pool actually failed". Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmto8kxx1l2 * style: apply prettier to the ported ECS updater guard suite The ported assertions were hand-converted from vitest and drifted from the repo Prettier style, which the full-profile Run Prettier step would have flagged on this PR. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmto8kxx1l2 --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
948872b148 |
fix(web-shell): close the four deferred #9812 review follow-ups (#11107)
Each of the four suggestions deferred out of #9812 was left with nothing pinning it, so the behaviour each one protects could regress silently. Pin the historical sessionStorage key. `clientLifecycle.test.ts` only ever round-tripped through `SESSION_CLIENT_ID_STORAGE_PREFIX`, so renaming that constant moved the read and the write together and left every test green, while a tab that persisted its id under the WebUI-era key lost it across the migration. Three assertions now spell the key literally, including its percent-encoded session suffix. Make the ChatRecord export script testable and cover both paths. It had no exports and ran `main()` at import, which is why the legacy-JSONL rejection — a behaviour flip #9812 introduced — had no test. The input gate is now `selectChatRecords`, the render is `renderHtmlFromObjects(objects, api)` with the export API passed in, and `main()` runs only as the process entry point. The CLI behaves as before. The new suite asserts the rejection message verbatim and that the renderer is never reached, so removing the throw fails rather than falling through to the neighbouring error. Restore the substring-trap fixture the retirement deleted, under a live path. `Shellfish.tsx` guards the keyword being the head of a stem, which is a different loosening from the `web-shell` compound case already covered. Replace the deleted `InputForm` in the public hook's docblock with `ChatEditor`, which declares the three props, naming `ChatPane` and `App` as the in-tree hosts. Verification brief for the two vitest-backed items is committed alongside; item 3 was run here, mutation included, and the mutant survives without the new fixture. Refs #11076, #9812. Co-authored-by: yiliang114 <[email protected]> |
||
|
|
077c57d200 |
refactor!: retire @qwen-code/webui (#9812)
* feat(transcript): add cross-host document export pipeline Establish a shared transcript model and document-mode projection so Web Shell, VS Code, and HTML export can consume the same stable conversation semantics without changing interactive rendering. - Preserve daemon and ACP segment identity across replay and normalization - Add export-safe previews and a versioned transcript document builder - Add document-mode Web Shell rendering with bounded Mermaid processing - Lock direct-daemon and ACP behavior with contract fixtures - Cover render and export equivalence in integration tests * feat(transcript): wire real VS Code and HTML export consumers Move transcript prevalidation onto real product paths while preserving the legacy VS Code timeline as the default fallback. - Add version-bound document HTML with strict CSP and schema checks - Add opt-in ACP projection with stable source identity - Reuse product export code across hosts and the integration runner - Remove test-only production probes and consolidate shared validation Note: Browser, host-action, VSIX, and packaging gates remain pending. * build(deps): sync transcript workspace dependencies Keep the npm lockfile aligned with the VS Code and HTML export package manifests added by the transcript consumer migration. - Lock the VS Code Web Shell workspace dependency - Lock Web Templates SDK, Web Shell, and React build dependencies * feat(vscode-ide-companion): reuse WebShell transcript UI behind experimental flag Bridge ACP session/update notifications into the shared SDK daemon transcript reducer and render the result with the WebShell transcript component, gated on qwen-code.experimental.webShellTranscript (default off). The WebShell renderer and its heavy transitive dependencies (echarts, mermaid, shiki, codemirror, katex) are lazily loaded via esbuild code splitting, so the default configuration keeps the ~700KB webview bundle unchanged. * fix(vscode-ide-companion): grant wasm-unsafe-eval for shiki WASM when WebShell transcript enabled * fix(transcript): harden export and identity paths Resolve review findings across document export and the VS Code ACP timeline while preserving default interactive and readonly semantics. - Preserve stable text and non-text identity across live and replay - Harden export projection, budgets, URL handling, CSP, and nonces - Make document rendering complete, inert, and browser-validated - Keep VS Code transcript scope, theme, copy, and flags reactive - Restore fixture, schema, hash, and compatibility contract locks Note: The overall gate remains failed pending VSIX, host-action, and packaged-artifact evidence. * feat(vscode-ide-companion): adopt WebShell transcript as default timeline Drop the experimental flag and the legacy MessageList renderer. The companion timeline now always renders through the shared WebShell transcript component, fed by ACP session/update notifications via the SDK daemon transcript reducer (lazy loaded through esbuild code splitting). The flag-gated wiring is removed: the qwen-code.experimental.webShellTranscript setting, the conditional CSP/body attribute in WebViewContent, and the legacy MessageList path in App.tsx (~850 lines). The webview CSP now grants wasm-unsafe-eval unconditionally for Shiki's Oniguruma WASM. * fix(vscode-ide-companion): reset WebShell transcript state on session switch The experimental useAcpTranscript hook only consumed transcriptUpdate messages, so its reducer state survived session boundaries. When the extension switched sessions it kept the webview mounted and replayed the newly-selected session through ACP, causing the previous session's blocks to merge with the new replay (e.g. user text "alpha" from session A leaked into session B as "alphabeta"). Reset both the reducer state and the rendered blocks on the same boundaries the legacy message flow uses: qwenSessionSwitched (sent before the ACP replay of the selected session) and conversationCleared (new session). Adds a regression test that replays two sessions with a switch between them. * fix(vscode-ide-companion): harden WebShell transcript session boundaries - reset the transcript on `conversationLoaded` too, closing the same cross-session leak the previous commit fixed for `qwenSessionSwitched` and `conversationCleared` (agent reconnect posts only this boundary) - track the active session id and drop late `transcriptUpdate` frames whose `sessionId` no longer matches, so a previous session's trailing frames cannot contaminate the next session's timeline - seed the transcript from cached messages carried by `qwenSessionSwitched` so offline restores and load-failure fallbacks render their history instead of a blank timeline - dispatch `assistant.done` on `streamEnd`/`sessionLoadComplete` so the final assistant/thought block of a turn (or history replay) does not stay `streaming: true` forever * fix(transcript): harden identity and document export Close the latest review findings across transcript identity, VS Code rehydration, and the HTML export security boundary. - Terminate discrete ACP segments and bind automatic turn provenance - Reset VS Code transcript scopes across replay and reconnect lifecycles - Align Markdown sanitization and envelope budgets with document rendering - Run browser gates with Chromium and complete third-party notices * fix(vscode-ide-companion): adopt live ACP session id after load-failure fallback * fix(vscode-ide-companion): echo user prompt into WebShell transcript * fix(vscode-ide-companion): keep WebShell transcript expanded and clear of the composer * fix(vscode-ide-companion): surface local error and interrupt notices in the transcript area * fix(vscode-ide-companion): restore file-link opening from the WebShell transcript * fix(vscode-ide-companion): restore contributed copy commands for the WebShell transcript * fix(vscode-ide-companion): add localOnly marker to TextMessage state type * fix(vscode-ide-companion): restore /insight progress card and report link in the transcript UI * fix(vscode-ide-companion): finalize in-flight tools on timeout and pin session-switch seeding guard Map streamEnd reasons timeout/session_expired onto the reducer's error reason so abandoned mid-tool turns no longer spin forever (ceuI). Add qwenSessionSwitched cases with no messages field and an empty cache array; the no-messages case fails when the seeding guard is forced true, pinning its false side (ceuN). * fix(vscode-ide-companion): remove unreachable editMessage backend and dead submit options The user-message edit/rewind UI was dropped in the WebShell-transcript migration, leaving editTargetTurnIndex/onSubmitted options in useMessageSubmit and the full editMessage/rewind flow in SessionMessageHandler unreachable. Remove the dead options, the editMessage dispatch case, the rewind/snapshot flow with its recovery branches, and their tests (R1-8 direction b). * fix(vscode-ide-companion): drop write-only loadingMessage bookkeeping The waiting-message renderer was removed with the WebShell transcript migration and the user prompt is echoed into the timeline at send time ( |
||
|
|
f6a7eee341 |
fix(ci): name the failing job when a main CI run reports no test result (#10855)
* fix(ci): name the failing job when a main CI run reports no test result (#10852) The main-CI failure filer dedupes on the failing tests it extracts from the run's job logs and falls back to one issue per commit when it extracts none. That fallback body named nothing but the commit, so a lane that dies before printing a test result files an issue nobody can triage from its own text: the nightly Windows lane has been red on all nine runs since the schedule was added, each one filing a fresh stub, and an autofix round spent on yesterday's stub ran out of budget with nothing to work from. The analyze job already reads the run's job list to find the failed job ids, and that list carries the job name and the failing step. Pass them to the helper and render them in the fallback body, tolerating their absence the same way a missing log is tolerated. The marker, the title and the per-commit dedupe are unchanged: this adds the identity the run reported, it does not change how issues are matched. * fix(ci): keep an errored jobs API call out of the filed issue body (#10852) `gh api` writes a non-2xx response body to stdout before exiting non-zero, so the `> failed-jobs.tsv || true` the failed-job list was fetched with captured a REST error object that the renderer then filed as a job name — in an issue labelled `autofix/approved` for the autofix agent to consume. Fetch the run's job list once into a file and derive both projections from it with local jq: an error body has no `.jobs`, so both passes emit nothing and the body degrades to the shape it had before this change, byte for byte. The single fetch also removes the second hand-synced copy of the "which jobs count as failed" predicate the step carried, and the failed fetch now warns like the sibling log-download loop does instead of degrading in silence. Failed steps are written one per TSV field rather than comma-joined: four step names in this repo contain commas (`Extract metadata (tags, labels) for Docker`), and a comma-joined wire shreds them into steps that never ran. Adds producer-side pins to the workflow-shape suite. The consumer pins were also satisfied by the `--jobs` flag line, so deleting the fetch or mutating its jq projection kept the suite green while the body went back to the contentless stub this change exists to replace. * test(ci): pin the jobs-fetch shape the failure watcher degrades on (#10852) Five mutations of the restructured jobs fetch kept the workflow-shape suite green while breaking the watcher at runtime. Each was reproduced before pinning, by replaying the extracted step under a stub `gh` that models real `gh`'s error contract (error body on stdout, then a non-zero exit): dropping the fetch's `if !` guard or the projection's `|| true` ends the step before the issue is planned, so a transient jobs-API error files nothing at all. Dropping `--paginate` caps the one fetch that feeds both projections at page 1, and flipping the id projection's `select` or its `.id` either downloads no failed log or 404s every download. The writer pin carries `|| true` instead of gaining a fourth assertion, and the `failed-jobs.tsv` substring pin it strictly subsumes is gone: both producer mutants that pin existed to catch were re-run after the removal and still red. * test(ci): bind the failure watcher's job-list producers to their consumers (#10852) Seven substring pins of the restructured jobs fetch each matched a fragment of the contract without binding it to anything, so eight re-wirings of the same script kept the workflow-shape suite green at 9/9 while breaking the watcher at runtime. Each was reproduced against the pre-round pins first: moving the TSV producer below the `analyze` invocation files the contentless stub this watcher exists to fix, relocating `--jobs` to `plan` drops the section silently because `plan` never reads `options.jobs`, swapping the two jq projections hands `mapfile` whole TSV lines so every log download 404s, renaming `job_ids` on one side only returns every run to the per-commit fallback, pointing a projection at a path nobody writes degrades with no signal, and moving the warning echo out of its `then` block reports a failed fetch on one that succeeded. Each pin is now scoped to the step that owns it and spans a producer together with its consumer, with the line continuations collapsed first so the pins read like the shell they pin rather than like this file's indentation. Seven assertions became six and all eight mutants red; the `--paginate` one was already killed by the previous round's fetch pin and stays killed. Also drops the nullish guard in `parseFailedJobs`: its one caller passes `readFileSync(path, 'utf8')` and no other read site exists, so it is the same unreachable defence the previous round deleted rather than gave a synthetic test. --------- Co-authored-by: Shaojin Wen <[email protected]> Co-authored-by: 易良 <[email protected]> |
||
|
|
fc874dfe0b |
fix(ci): retry sandbox image builds and file an issue when a release build fails (#9916)
* fix(ci): retry sandbox image builds and file an issue when a release build fails The v0.22.0 tag build died on a transient ETXTBSY during `npm ci` and was never retried, so ghcr.io/qwenlm/qwen-code:0.22.0 was never published while npm already served 0.22.0. Every sandbox-based CI lane (/resolve, sandboxed review, autofix) then crashes with "manifest unknown" until the image exists. Add one bounded retry to the buildx step: the first attempt carries continue-on-error so a successful retry turns the job green, and the retry gates on the first attempt's outcome alone (a failure() gate would read false once continue-on-error absorbs the first attempt). The publish condition is shared through one job-level env so the two build steps cannot drift. Add a follow-up job that files or updates one issue per version when both attempts fail — for tag pushes and for publishing dispatches alike, since the issue body itself recommends that dispatch as the recovery path. Dedup uses an exact body marker matched client-side, because GitHub search tokenizes the colon out of the marker and never finds these issues. Extend the existing workflow gate test to pin the retry contract and the issue-job gate. Fixes #9898 * fix(ci): move the image-build failure issue logic to .github/scripts/ The workflow-size ratchet rejects growth past the recorded baseline +4096 bytes; the inline issue-filing step grew build-and-publish-image.yml by ~5.2 KB. Move the step body to .github/scripts/image-build-failure-issue.sh (the gate's own recommended remedy), leaving the job as a thin env + script call. No behavior change; the gate test now pins the script call and reads the dedup contract from the script. * fix(ci): grant the failure-issue job contents permission and normalize dispatch versions * test(ci): pin the failure-issue gate and retry step invariants * fix(ci): gate the failure-issue job on the exported publish decision * fix(ci): skip the failure-issue job for versionless publishing dispatches * test(ci): pin the PUSH_IMAGE value and the login gate at the definition site * test(ci): replay the image-build failure-issue script under a gh stub * fix(ci): describe release build job failures without asserting a buildx cause * fix(ci): preserve annotations and recorded runs when updating the failure issue * test(ci): pin the dedup label on create and the open-state filter on lookup * fix(ci): document the pre-first-step gap in the failure-issue gate A build job that fails before its first step runs (runner provisioning failure) never executes publish-decision, so push_image stays empty and file-failure-issue is skipped despite failure() being true. Closing the gap structurally would restate the publish predicate and re-introduce the drift this PR removes, so document it on the job comment instead: future "failed publish, no issue filed" investigations start here, and a scheduled npm-vs-GHCR reconciliation remains the backstop. * fix(ci): record build-and-publish-image.yml's shipped size in the workflow size baseline The retry logic and failure-issue filing steps added by this PR grew the workflow from 4638 to 8887 bytes, past the 4096-byte allowance. Record the new size so the size gate passes. * fix(ci): harden the image-build failure reporter per review round 4 (#9916) - Replace GNU-only `head -n -1` with POSIX `sed '$d'` so the stranded-heading strip no longer corrupts the body on BSD userland (R4-1). - Skip the bash replay suite on win32, where backslash RUNNER_TEMP and the ';'-separated PATH cannot express it; the YAML pins still run there (R4-2). - Re-check head readability AFTER the normalization strip, which can itself empty the head and used to drop the narrative permanently (R4-8). - Admit only recorded-run shapes into the machine block so a bullet-shaped human annotation is no longer reordered into it or clipped by the cap (R4-13). - Remove the marker-restore branch: with the run shape pinned, every body that matched the dedup carries its marker in head+tail, so it was unreachable (R4-9). - Cross-reference the sibling split/merge contract in both implementations (R4-5), disable SC2016 with rationale on the literal-backtick formats (R4-6), and document the label-removal residual gap on the job (R4-11). - Behavioral witnesses: run-cap, stranded-heading, marker-survival, empty-head and empty-after-strip prose fallbacks, and the annotation shape; each guard mutation-probed red. Pin the dedup label on the list call too (R4-10). * fix(ci): document the version-marker dedup gap on the failure-issue job (#9916) Round 4 removed the unreachable marker-restore branch (R4-9) but left its residual gap undocumented: the dedup lookup only finds the tracked issue while the version marker survives in the body, so a human edit deleting the marker orphans the issue and the next failure files a duplicate. Fold the marker into the job's existing known-gap note alongside its sibling, the scope/ci-cd label (R4-11), and record the workflow's new size. |
||
|
|
519e824dc7 |
fix(ci): give the macOS and Windows lanes a trigger again (#9370)
* fix(ci): give the macOS and Windows lanes a trigger again Both lanes are gated on `merge_group`, and no merge queue is enabled on this repository — the `main` ruleset carries only deletion, non-fast-forward and pull_request rules, no status check is required, and merges land as squashes. The last `merge_group` run of anything was 2026-07-02. So the gate was an off switch: the lanes reported as "skipped" on every pull request, which reads as agreement, and nothing ever reached them afterwards. The only signal this repository has about a host that is not Linux with a GNU userland had been silently off for six weeks, which is how #9220 shipped a GNU-only `realpath -m` in a workflow guard with the suite that pinned it red on every Mac. Three triggers now, in cost order. A pull request whose diff a new classifier recognises as platform-sensitive: shell scripts of every dialect, workflow and composite-action YAML and the scripts they call, the script layer and its tests, the test-runner configuration that decides which suites run where, the root manifests, and source paths whose segments name a platform-coupled subsystem. It is a net, not a proof — it cannot see a platform assumption inside an ordinary source file, and no path rule ever will — so every unknown answers "sensitive": an unreadable listing, an unparsable entry, a fork pull request, a truncated file list, or the classify job failing outright all end as "run the lanes". Only a confident `false` skips them. The merge queue, if it is ever enabled again, unchanged. And a nightly run on `main` for everything the path list cannot see. Every other job in the workflow excludes `schedule` explicitly, so a nightly is exactly two jobs, and 'Qwen Code CI' joins the workflows the main-failure watcher opens autofix issues for — a red lane nobody is told about is the same silence the queue gate produced. That watcher gains a trigger-level `branches: ['main']` filter so the CI workflow's pull-request completions do not raise an event there just to skip. The classifier runs in its own small hosted job rather than as a step in `classify_pr`: that job's outputs pick the Linux runner for the whole run, and this one needs a checkout — on a pool whose workspace other jobs have poisoned before. It checks out the pull request's BASE commit, never the head: it runs before any review and executes a script from the tree it checks out. Its listing goes through the existing classify-pr-profile.sh wrapper, extended with a mode argument, because that wrapper's whole point is that one PR is never listed twice and classified differently in two places. Twelve tests pin the wiring — the triggers, the fail-safe direction of the gate, the base-commit checkout, that a nightly stays two jobs, and the alerting — and ten more pin the classifier itself, including the substring traps (`Shellfish.tsx`, `cryptic.ts`, `plateauDetector.ts` must not drag both lanes in) and every fail-safe path. Mutation-checked: flipping the gate to `== 'true'`, dropping the schedule from a lane, dropping the nightly guard from the ubuntu job, pointing the checkout at the head, dropping the classifier's test from the helper list, and removing the schedule trigger are each caught. * fix(ci): gate the Windows lane's checkout verification per trigger The first thing the revived triggers hit was not a test failure but the lane's own plumbing. `test_windows` verifies its checkout with `verify-checkout-head`, and the input was written when this lane ran in the merge queue alone: `expected_sha: github.event.merge_group.head_sha`, with no event gate. On a pull request that expression is empty, the action refuses an empty SHA, and the lane went red in 63 seconds without running a test — the first Windows run in six weeks, failing on the trigger rather than on the code. Give it the event-aware shape the Ubuntu gate already uses, and skip it where there is nothing to verify: the scheduled and dispatch runs check out a branch by name, not a head commit. Pinned generally rather than by name: for both lanes, any step whose inputs read a `github.event.<event>` context must be gated to that event, in the step's own `if` or in the expression itself. Restoring the old spelling turns that test red. * fix(ci): close review round on the revived platform lanes (#9370) * fix(ci): pin the Windows lane routing to the canonical trust policy (#9370) * fix(ci): host-probe the macOS lane's bash and GNU dependencies (#9370) The revived macOS lane ran the shared suite for the first time since the merge queue went dark and failed on exactly three tests, identical across three consecutive runs: the bite-check block and the baseline A/B green path die with `mapfile: command not found` (a bash >= 4.4 builtin; macOS ships 3.2), and the health-probe repair case trips over BSD `mktemp -u` attempting to create where GNU's print-only `-u` just names the canary. Both suites pin scripts that only ever execute on Linux runners — ubuntu-latest or the Linux ECS pool — so the defects cannot exist in production; the suites just cannot run those scripts on a macOS host. Follow the convention #9220's fix established for the realpath case in the same file: probe the host capability, not the platform, and skipIf the three dependent tests where it is absent. A Mac with a newer bash or GNU coreutils fronting PATH keeps the coverage; the Linux lane runs all three unconditionally. The large bite-check test body is re-indented by prettier around the new skipIf wrapper; no other token changed. * fix(ci): stop the subsystem rule matching compounds that name something else The platform-sensitivity classifier split a path segment on dashes and underscores anywhere, so `packages/web-shell/**` matched the `shell` keyword — one of this repository's largest packages, a browser UI with no host coupling, summoning both expensive lanes on every change to it. That is the cost the gate exists to avoid, spent on the wrong diffs. A keyword now counts when it NAMES the thing: a whole path segment (`src/sandbox/**`, `platform/paths.ts`, `shell.ts`) or the head of a hyphen/underscore stem (`pty-host.ts`). Not a trailing part of a compound, which belongs to whatever the leading word names, and still not a substring inside a longer word. Pinned both directions, including a directory that IS named for the subsystem wherever it sits (`web-shell/components/shell/**` stays sensitive). Mutation-checked: dropping either rule, or restoring the split-anywhere spelling, turns the suite red. Also drops a wrong issue citation in the watcher test's comment: the nightly comes from this change, not from the wipe-guard back-port. * test(ci): pin the gate as a disjunction and the watcher's name binding Two mutation-survivable gaps in this PR's own tests. The clause-presence assertions left a connective mutation alive: `||` → `&&` between two event clauses keeps every asserted string in place and makes the gate unsatisfiable for every trigger — both lanes silently off again, which is the state this PR exists to end. Read the event group and require it to be a disjunction, allowing `&&` only inside the pull-request clause that binds to the classifier output. And the watcher's binding to this workflow is by display name: `workflow_run.workflows` matches the watched workflow's `name:`, so renaming ci.yml unhooks the nightly's alerting silently. Pin both sides. Both checked by mutation: flipping one `||` and renaming the workflow each turn a named test red. * fix(ci): close the classifier's CRLF gap and widen the lane step scan Three findings from this round, all in the direction of the tests and the parser being less clever than they claimed. The classifier's JSONL reader split on `\n` while its sibling splits on `/\r?\n/`. Every suffix rule here is end-anchored, so one carriage return on a CRLF listing would leave `build.sh\r` and classify a script-layer change as ordinary source. Matched to the sibling, with a fixture on both the JSON and the raw-line path. The per-trigger step scan serialized only `with:` inputs, so the same defect wearing an `env:` or `run:` key escaped it; it now reads all three. And the nightly blast-radius guard tested for the MENTION of an allowlisted event rather than the IMPOSSIBILITY of `schedule` — a job gated `pull_request || schedule` satisfied it while running every night. It now requires the absence of an explicit schedule clause too. * style(ci): format the runner-routing suite The Windows trust-policy matrix added last round left the file outside prettier's style, which the repository's lint step fails on; main's copy is clean. Formatting only — the nine assertions are unchanged and still pass. * fix(ci): gate the two mapfile-crossing gate tests on the host probe (#9370) * fix(ci): keep pull requests off the persistent Windows pool and cover native audio (#9370) A pull_request run executes the workflow YAML from the PR's own merge commit, so the test_windows runs-on trust clause it evaluated could be rewritten by any PR the lane admits. Every pull request now runs on hosted windows-2022 unconditionally; the pool is reached only by the post-approval merge queue, schedule and dispatch, guarded by the kill-switch. The routing tests and the exact-line pin are re-pointed at that enforceable shape. Also add audio to the platform-sensitivity classifier's subsystem keywords: packages/audio-capture is a node-gyp workspace compiled per-host on exactly the two revived lanes, but its native sources (.cc/.mm/.gyp) carried no rule and a PR touching only them skipped both lanes. The workspace directory now classifies sensitive; an ordinary .cc elsewhere stays ordinary source. Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: qwen-code-dev-bot <[email protected]> Co-authored-by: qwen-code-dev-bot <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
f4802031d0 |
perf(ci): run docs-only automatic reviews at medium effort (#8648)
* perf(ci): run docs-only automatic reviews at medium effort
A 1-line docs PR costs the same 57-180 minute high-effort review as a code
change, and on a diff with zero source lines the passes medium drops - the
adversarial personas and the reverse audit - have no failure mode to hunt.
Counterfactual analysis over six dissected CI runs showed the one case
where those passes caught a real Critical was a source PR, which this gate
never touches: classification reuses the Test workflow's conservative
classify-profile.mjs (docs/**.md(x) + root prose only; markdown under any
src/ tree stays full, matching the review skill's own source rule), and
any fetch or classifier failure falls back to the full review.
Only the automatic pull_request_target review downgrades; every explicit
request (workflow_dispatch, @qwen-code /review) keeps full high effort.
Because an effective --comment forces high and medium never posts, the
downgrade drops --comment and a new step relays the review CLI's verbatim
"Review complete:" line - its machine-readable completion contract - as a
single PR comment, with a pointer for requesting the full review. The
docs-only budget is the size-aware timeout halved with a 90-minute floor.
* perf(ci): address review feedback on the docs-only medium gate
All nine review suggestions, each verified before fixing:
- review_requested is an explicit ask: the AUTO_REVIEW flag now excludes
that action (authorize write-permission-checks its requester), so a
maintainer requesting the bot's review gets the full high-effort run.
- The fetch-and-classify wrapper is extracted to
.github/scripts/ci/classify-pr-profile.sh and consumed by both ci.yml
and the review gate, so the classifier's input contract lives in one
place; distinct exit codes preserve each caller's fallback messages.
- Neither completion-line fallback mints the reserved "Review complete: "
prefix anymore, and the relayed line passes a strict not-posted
disposition allowlist - on this never-posts path any posted-form
disposition is false by definition (the measured phantom APPROVE
posted), so it falls back to a neutral non-scrapable form.
- The relay upserts by its marker (mirroring the queued-acknowledgement
step) instead of stacking a comment per push, retries the POST/PATCH
three times, and never fails the job - a failed relay after a
successful review must not trip the failure fallback into announcing
a review failure that never happened.
- The Chinese relay copy no longer parses as "发行" and renders
high-effort as 高强度 rather than 高档.
- The qwen-review docs-only-medium marker is registered in all six
BOT_COMMENT_FILTER sites in qwen-autofix.yml, so clean docs-only
relays cannot select PRs into autofix rounds as actionable feedback.
- The gate's behavioral invariants are pinned in
scripts/tests/qwen-pr-review-workflow.test.js by executing the
extracted bash: prompt-branch order (--effort medium instead of
--comment), the halve-with-90-minute-floor arithmetic, the
completion-line allowlist including the phantom shapes, AUTO_REVIEW
exclusivity, the six-site marker registration, and the shared-wrapper
routing in both workflows.
* perf(ci): harden the docs-only gate against round-2 review findings
Thirteen findings across two review passes; every fix is executed by a
test rather than asserted as text where the finding was behavioral.
- The relay marker exclusion in qwen-autofix.yml is author-scoped at all
six filter sites: only the relay bot's own marker comment is filtered,
so a human quoting the marker stays actionable feedback.
- classify-pr-profile.sh guards the 3,000-file listing cap (any mismatch
against the PR's declared changed_files classifies full), uses
mktemp+trap instead of a fixed path on the shared persistent pool, and
ships its own node:test suite (renamed source→docs pins the projection
contract; exit codes 2/3 pinned) registered in HELPER_TESTS.
- classify-profile.mjs restricts reserved root prose basenames to inert
extensions - README.js / SECURITY.ts / LICENSE.sh classify full.
- The completion-line allowlist binds to pr-<number> and to the only
verdict a medium run can produce (Comment, not posted) - a stale line
for another PR or an Approve-shaped injection falls back to neutral.
- A dedicated review_completed output gates the relay: the state/head
guards exit 0 without running the review, and outcome==success alone
would have announced a review that never ran.
- The relay upsert filters by the authenticated bot login, re-resolves
the comment id on every attempt, and falls back to POST when the PATCH
target is gone - a participant posting the marker can no longer capture
the upsert, a transient listing failure no longer mints duplicates.
- The gate and relay are now executed under stubbed executables in
qwen-pr-review-workflow.test.js (docs_only/full/failure/explicit
scenarios; POST/PATCH/never-fail branches), the AUTO_REVIEW pin covers
both guard halves, and the marker contract is pinned producer-side and
filter-side.
* perf(ci): fix the medium Request-changes swallow and the stale docs badge
Round-3 review findings (2 Critical, 8 test-gap Suggestions), each fix
executed by a test where the finding was behavioral:
- The completion-line allowlist accepts `Request changes, not posted` -
compose-review caps only Approve at medium, so a docs-only run that
verifies a Critical legitimately emits Request changes, and the old
Comment-only allowlist swallowed exactly the blocker-finding outcome
into the neutral fallback. Target binding to pr-<number> is unchanged
and now pinned by a test, as is the last-line selection over a stale or
injected earlier completion line.
- A stale docs-only badge can no longer outlive its revision: the full
automatic review path now supersedes the bot-authored marker comment
(strikethrough + superseded note) via a new --update-only mode that
never mints a badge where none existed.
- The marker+author upsert protocol is extracted to
.github/scripts/upsert-bot-comment.sh - one implementation shared by
the relay and the supersede step (the per-step copies had already
drifted), with its own node:test suite covering the author scope, the
per-attempt re-resolution (deleted-mid-retry falls back to POST), and
the --update-only no-op; registered in HELPER_TESTS.
- The classify-pr-profile gh stub now applies the wrapper's own --jq
argument with real jq over API-shaped fixtures, so the projection
contract is genuinely under test (negative control: dropping `status`
turns the renamed-source scenario red).
- New pins: review_completed wiring end to end (run-step emit + both
consumers' if clauses), the auto_review output->env wiring at both
links, and both AUTO_REVIEW guard halves.
* perf(ci): never let a failed lookup mint or keep a stale docs badge
Round-4 review findings (1 Critical, 7 Suggestions):
- The upsert script no longer conflates failed lookups with empty
results: the authenticated login, the listing, and the jq extraction
are all resolved inside the retry loop as one prerequisite chain, an
attempt whose prerequisites failed retries instead of falling through
to POST (the shape that minted a permanent duplicate badge off one
transient 5xx), and --update-only exits 1 on a failed lookup so the
supersede warning fires instead of a false no-op success. New tests
pin the failed-listing-then-PATCH path, the persistent identity
failure, the update-only failure exit, and the update-only PATCH.
- Supersede now covers every path that owes the correction: a FAILED
full review and an EXPLICIT requested review (the badge's own CTA)
both retire the badge, gated only on docs_only_medium == 'false' -
empty on runs that failed before classifying, so a badge is never
superseded on ignorance. The body is cause-neutral: it asserts only
that the badge described an earlier revision.
- The marker literal is defined once per step (MARKER variable, the
qwen-triage convention) and shared between body and lookup argument;
a pin requires the definition and --update-only on the supersede
invocation.
- New behavioral pins: the Approve verdict stays rejected by the
allowlist, github_ci_only never downgrades (CI helpers are
executable), and review_completed's emit position is asserted AFTER
the closed-PR and stale-head guards (the hoist mutant survived
position-independent contains checks).
* perf(ci): make docs_only_medium three-valued and pin the untested guards
Round-5 review findings (1 Critical, 6 Suggestions):
- docs_only_medium no longer conflates "determined not docs-only" with
"never determined": the output is three-valued ('' when the
classification failed or never ran), so a transient classifier failure
or a dispatch dry-run can no longer retire a still-accurate badge. The
supersede condition names its two licensed paths explicitly - a
POSITIVE not-docs-only determination (without requiring review
success), or an explicit comment-mode review that completed (the
badge's CTA; report-mode dry runs retire nothing).
- The count-mismatch fallback in classify-pr-profile.sh logs to stderr,
so a systematic divergence is distinguishable from every PR genuinely
classifying full.
- Six probed surviving mutants now each turn a test red: the supersede
body is executed (existing-badge PATCH and the never-fail guard),
ci.yml's rc-handling fragment is executed (exit 0/2/3 with the
full fallback), the changed_files fetch failure exits 2, duplicate
badges PATCH the last (newest) comment, and the relay's POSTed body
must carry the marker that keys both the upsert and the supersede.
* perf(ci): bind the docs badge to the reviewed head and retire it on failure
MDX pages are executable (imported components, expressions), so the
classifier no longer treats them as inert docs-only changes. The relay
and supersede writes re-read the live PR state/head immediately before
the mutation and skip unless the PR is still open at the reviewed SHA,
the badge body names that SHA, a failed docs-only review now retires the
singleton badge instead of leaving the previous revision's outcome
visible, and the retired wording is cause-neutral (an explicit review
can complete on the very head the badge describes).
Co-authored-by: Qwen-Coder <[email protected]>
---------
Co-authored-by: verify <verify@local>
Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
|
||
|
|
43c35e533b |
fix(ci): keep the post-merge E2E signal on main alive (#7795)
* fix(ci): stop cancelling in-progress E2E runs on main Merges land on main roughly every 18 minutes (median) while a full E2E run takes about 40, so cancelling the in-flight run on every push starved the suite: across the last 100 push runs, 67 were cancelled and only 25 ever reported a result. The nightly regression was the only reliable signal, and each cancelled run still burned roughly 10 minutes on three runners before dying. Turning cancellation off for main hands the coalescing to GitHub's concurrency queue, which keeps at most one pending run per group and cancels the previously pending one. The queue therefore collapses to the newest tree on its own while the in-flight run always finishes, so every result covers the batch of commits merged since the last one — bisect that range when it goes red. Dev branches keep cancelling superseded runs, where only the latest push matters. * fix(ci): dedupe main CI failure issues by failing test The autofix issue for a red main was deduped on the commit SHA, so a standing failure opened a brand new issue on every merge: one broken E2E test on 2026-07-26 produced six duplicate issues in twelve hours, each pointing the autofix agent at an unrelated commit. The failing tests are now identified from the logs of the failed jobs and used as the dedupe key, with one marker per test so a failure set that grows still matches the issue that already tracks part of it. Later commits hitting the same failure are appended to that issue as recurrences (bounded, newest first) instead of opening another one, and notes written by a human or the agent are preserved when the machine-owned trailer is refreshed. Runs with no identifiable test — an install or build break — keep the previous per-commit behaviour. * fix(ci): keep the failure analysis out of the bot-PAT job Identifying the failing tests means running a helper from the repository, and the workflow deliberately checked out nothing so that the job holding the bot PAT could never execute repository code — an invariant its own test enforces. Rather than weaken it, the work is split: a read-only job checks out the tree, reads the failed run's logs, finds any issue that already tracks the failure and renders the title and body, handing both over as outputs. The job with the PAT keeps checking out nothing and only writes what it was given. The invariant test now asserts that separation — the PAT job runs no repository code and holds only issue write — instead of banning checkout everywhere in the file. * fix(ci): harden main CI failure dedupe per review (#7795) - Match the `[run <id>]` link text instead of the run URL when deduping recurrences: `/301` is a substring of `/3010`, so the URL match silently deleted an unrelated run's line. - Rebuild the machine-owned "## Also failing" section from the live failure set on every merge so a test that has since been fixed drops out instead of being listed forever. - Assert the analyze checkout is SHA-pinned with persist-credentials disabled and pin the failed-job log-download paths in the workflow test. - Add an e2e workflow test guarding the cancel-in-progress expression that keeps in-progress runs on main from being cancelled. * fix(ci): bound the issue body and randomize the heredoc delimiter (#7795) * fix(ci): test the runCli --existing merge path (#7795) * fix(ci): address review feedback on e2e signal PR (#7795) - Assert the full cancel-in-progress expression including && so a mutation to || is caught by the e2e-workflow test - Filter the capped-summary line from missingTests so it is not rendered as a fake bullet under Also failing --------- Co-authored-by: qwen-code-dev-bot <[email protected]> Co-authored-by: qwen-code-ci-bot <[email protected]> Co-authored-by: qwen-code-dev-bot <[email protected]> Co-authored-by: Qwen Code Bot <[email protected]> |
||
|
|
183ad54b11 |
ci: add lightweight PR profiles (#6186)
* ci: add lightweight PR profiles * fix(ci): harden lightweight profile classification * fix(ci): harden lightweight profile edge cases * fix(ci): report classifier invocation failures * fix(ci): tighten docs-only path matching * ci: restrict light profiles to same-repo PRs --------- Co-authored-by: Shaojin Wen <[email protected]> |