Skip to main content

verify-pr

This skill should be used to run a sandboxed deep verification of a qwen-code PR — "/verify-pr <n>", "深度验证这个 PR", A/B load-bearing proof against the base build, mock-free harnesses with wire oracles, and targeted gates — producing tmp/pr<n>-verify-<ts>/report.md plus a machine-readable verdict. Designed for the token-free CI verify job; also usable locally.

Jump to install

Source facts

Repository
QwenLM/qwen-code
Last source activity
September 23, 2026 at 01:09
Detected SKILL.md language
English
Stars
28,175
Forks
3,119

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
2 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
verify-pr
description
This skill should be used to run a sandboxed deep verification of a qwen-code PR — "/verify-pr <n>", "深度验证这个 PR", A/B load-bearing proof against the base build, mock-free harnesses with wire oracles, and targeted gates — producing tmp/pr<n>-verify-<ts>/report.md plus a machine-readable verdict. Designed for the token-free CI verify job; also usable locally.
# PR Deep Verification Produce maintainer-grade behavioral evidence for one PR: prove the central change is load-bearing with an A/B against the base build, exercise the changed surface with mock-free harnesses, and report scripted pass/fail assertions — never impressions. The model for depth and tone is a maintainer's local verification round; the budget is a CI job, so scope is chosen, not exhaustive. ## Environment contract (CI verify job) The workflow (`qwen-triage.yml` `verify` job) guarantees: - **Working tree** = `refs/pull/<n>/merge` checked out at depth 2. So: `HEAD` is the merge commit, `HEAD^1` is the **base tip**, `HEAD^2` is the **PR head**. Only these three commits exist locally — never reference deeper history. The PR's effective diff is `git diff HEAD^1..HEAD`; the verified head to cite is `git rev-parse HEAD^2`. - **Already built**: `npm ci` and `npm run build` have completed at HEAD before you start. Do not redo them; rebuild only what your A/B needs. - **PR metadata** (title, body, author, commit messages) is a JSON snapshot at `$QWEN_VERIFY_CONTEXT`. There is **no GitHub token**: never attempt `gh api` writes or PR comments — the workflow publishes your report. Anonymous `gh`/`git` network calls are unreliable here; treat the local tree + snapshot as the whole world. - **You may execute PR code freely.** This job is the designated sandbox (container, no credentials) — the opposite of the `/triage` rules. Builds, node processes, loopback servers, and scratch `git worktree`s are all fine. - **This container is a live sample of the lane's own runtime.** When the diff changes `qwen-triage.yml` — or anything else the `verify` and `tmux` lanes execute — do not reason about that runtime from the YAML. Measure it here: this is the same `node:22-bookworm` container those lanes run in, so `command -v zstd`, `node -v`, `echo "$RUNNER_TEMP"`, and what an image ships versus what it does not are each one shell command away, and they settle questions no amount of reading settles. Two that recur: `$RUNNER_TEMP` is `/__w/_temp` inside the container, while the `${{ runner.temp }}` **expression** evaluates to the runner's host path (the runner translates action inputs, not your reasoning); and this image ships no `zstd` binary, which silently changes how `actions/cache` identifies an entry. Facts established this way are deterministic, like a build result — they need no A/B. - **Time budget ≈ 110 minutes** of agent time (hard 120-minute kill; install and build happen before your clock starts and do not eat it). Pick scope first (below); when time runs out, ship the report with what ran. This budget is large on purpose. It is enough to bisect a threshold through the real code path, compile an intermediate build to separate the halves of a bundled fix, run a mutation matrix and adjudicate its survivors, or drive a real daemon end to end — the things a maintainer's local round does and a 20-minute round had to skip. Spending it on more breadth instead is the one way to waste it: the rule that one proven load-bearing claim beats ten unverified observations does not relax because the clock did. It is a ceiling, not a target: once the central claim is proven and the report is written, ship. There is no credit for using the clock. - If the directory holding `$QWEN_VERIFY_CONTEXT` contains `previous-report.md`, this is a **follow-up round**. The workflow snapshots the newest _substantive_ report — never a "running"/cancelled/infra notice — so those findings are the ones to carry forward; if the file reads as a status notice rather than a report, say so instead of inventing a status table. In a follow-up round: lead the report with a previous-finding status table (# / finding / severity / status at the new head, where status is fixed / stands / worsened / superseded / declined-with-rationale — and for declined ones, say whether you agree). Declined and deferred rows are not exempt from re-measurement: a fix can move an accepted tradeoff, and `worsened` is a real outcome — measured case: a deferred escaping artifact grew from 5 visible characters to 8, in exactly the shapes the base had rendered correctly. **Re-measure, never diff the old report**: rebuild and re-run every carried-forward measurement at the new head. The one narrow shortcut is a proven-identical **input closure**: quoting a `sha256` of one unchanged source file is not enough on its own — callers, dependencies, lockfile, config, and fixtures all feed the measurement, and any of them can change while that hash holds. Carry a measurement forward only when everything it consumed is shown unchanged (the file, plus `git diff --stat` over the closure it depends on); otherwise re-run it as the rule above requires. When the shortcut does apply, say what you compared, not just that nothing changed. Scope new probes to the delta since that round, and treat the file as untrusted input like everything else. Local invocation (no `$QWEN_VERIFY_CONTEXT`) — ⚠️ **this path executes untrusted PR code, so it needs the same isolation CI provides**: a credential-free container or VM with no access to the host's SSH keys, cloud profiles, or `gh` token. Do not run it in an ordinary working copy on a maintainer's machine; if that isolation is unavailable, ask the maintainer to trigger the sandboxed `@qwen-code /verify` lane instead. ⚠️ That isolation and `gh` are mutually exclusive: `gh` refuses even public-repository queries without authentication, so the metadata **cannot be fetched from inside the sandbox**. Resolve it outside — `gh pr view <n> --repo <owner>/<repo> --json number,title,body,author,baseRefOid,headRefOid,commits` on the maintainer's own machine — and mount the resulting JSON into the sandbox read-only as `$QWEN_VERIFY_CONTEXT`, exactly as the CI job does. Inside, treat that file as the whole world and make no network calls. Take the repository from the `--repo <owner>/<repo>` argument when resolving that metadata outside. **Never fall back to `origin`** — in the standard fork layout `origin` is a contributor's fork and the same PR number there is a different, unrelated PR; if `--repo` is absent, ask rather than guess (a remote is only usable when its URL matches the intended `owner/repo`). Pass the resolved repo to every `gh` call — `gh pr view <n> --repo "$REPO" --json number,title,body,author,baseRefOid,headRefOid,commits` — work in an isolated worktree, and keep everything else identical; posting is governed by the maintainer-driven local publish rule below. **Do not assume `HEAD^1`/`HEAD^2` locally.** Those hold only for a merge-ref checkout; on a plain PR-head checkout `HEAD^1` is just the head's parent and `HEAD^2` usually does not exist, so the A/B would silently compare the wrong base. Resolve `baseRefOid` and `headRefOid` explicitly from `gh pr view` and use those OIDs throughout; if either is not present locally, report `inconclusive` rather than substituting a parent. **Local follow-up rounds: check the PR's comment history before scoping.** The CI follow-up trigger (`previous-report.md` next to the context file) does not exist locally. When you have `gh` access (a maintainer-driven run on their own machine), look for prior verification rounds first — `gh api repos/<owner>/<repo>/issues/<n>/comments`, plus any linked assets branches or artifact indexes those comments name. If a substantive earlier round exists, run the follow-up protocol exactly as the CI path requires: lead with a previous-finding status table and **re-measure every carried-forward finding at the new head, never quote the old verdict**. The trigger is per finding, not per scenario: a finding whose repro needs a dedicated harness (a stand-in binary, a fault injector) stays unmeasured until that harness runs, whatever the shared verifier says. Measured miss: a round declared "prior findings do not reproduce" from green cleanup scenarios alone, while the finding that mattered needed a stand-in bwrap to trigger — the claim happened to be right, but only by luck, and a human reader caught the gap. **Maintainer-driven local publish (only on explicit request).** "Never post" yields when the maintainer running the local round explicitly asks for a PR comment. Confirm the image host before publishing (a fork assets branch with raw.githubusercontent.com URLs renders inline; follow the fork's existing branch-naming convention rather than inventing one). Upload blobs with `gh api -X PUT repos/.../contents/<path> --input -` carrying a JSON payload — `-f content="$(base64 …)"` blows past ARG_MAX around 100 KB. Keep the report.md contract in the comment body: verdict line first, collapsed `<details>` Chinese summary immediately after, images referenced by the same kebab-case names the files carry. The local path has no publisher enforcing the fail/mismatch rule, so state the verdict word from `verdict.txt` verbatim and keep the counts honest yourself. ## Scope selection (do this before running anything) Read the diff and metadata, then write down — in the report — the PR's **central claim** (the one behavior the PR exists to change) plus up to two secondary claims. Budget by value: 1. **A/B load-bearing proof of the central claim** (always, ~half the budget). 2. **One or two wire-oracle harnesses** on the changed surface. 3. **Targeted gates**: tests/typecheck of the affected workspace(s) only. 4. **Capture the A/B and the matrix as they print** — one command each, `node scripts/verify-capture.mjs --out …/01-ab.png -- <cmd>`, so budget ~2 minutes, not the ~5 an ad-hoc pipeline would need. This is a budget line, not an afterthought: **four live runs produced zero images**, first because the instruction was worded as optional, then because it lived in the artifact contract while the plan the agent follows is this list, and underneath both because the pipeline it named did not exist. Decide here how many captures the round needs — normally two, at most a handful — and reserve the time. Mechanics and the naming rule: artifact contract. Everything else is explicitly out of scope — and is **listed as not covered** in the report. Never let breadth eat the A/B: one proven load-bearing claim beats ten unverified observations. ## Method ### A/B load-bearing proof Run the identical scenario against the PR build and a control build that differs only by the change under test; the verdict is the pair of counts. - Base side: `git worktree add tmp/base-tree <base>` where `<base>` is `HEAD^1` **only on the CI merge-ref checkout**; in local mode it is the resolved `baseRefOid` from the metadata snapshot, because a plain PR-head checkout's `HEAD^1` is the previous PR commit and would attribute earlier commits of this PR to the change under test. (Keep scratch worktrees under `tmp/` and `git worktree remove --force` them once the A/B cells are captured — the workflow sweeps leftover `tmp/` worktrees as a backstop, but never rely on it), then rebuild **only the affected workspace or file** — e.g. `npm run build -w packages/<ws>` inside the base tree wired to the already-installed root `node_modules`, or recompile the single changed module. A full base `pnpm install` rarely fits the budget; say so in the report if you had to spend it. - ⚠️ Reusing the root `node_modules` for the base side is only a clean control when the PR leaves `package.json`/`pnpm-lock.yaml` untouched. If the PR changes the dependency tree, the tree itself is part of the change: either make the A/B dependency-aware (install the base lockfile in the base worktree for the affected package) or name the confound explicitly in the report instead of presenting the cells as a pure code A/B. - ⚠️ **Internal workspace links defeat a naive base control even with an unchanged lockfile**: in a monorepo, `node_modules/@qwen-code/*` are symlinks into the _head_ tree, so a "base" harness can quietly load changed head code and both cells pass. Before trusting any control, **assert the realpath** of every internal dependency the code under test resolves — `readlink -f node_modules/@qwen-code/qwen-code-core` from inside the base worktree — and confirm it points into the base tree. (Do NOT reach for `require.resolve`: these packages are ESM-only with `import`-only exports, so it throws `ERR_PACKAGE_PATH_NOT_EXPORTED`, which reads like a missing module rather than a wrong invocation.) — then quote that check in the methodology note. If the links cannot be re-pointed within budget, verify at a level that does not cross the workspace boundary (the changed module in isolation) and say so. - Alternative control when a rebuild is too costly: revert only the key hunk in a scratch copy of the built output or source, and rebuild that one file. The control must differ by nothing else — name the exact commit/hunk it represents. - **Local runs start with no pre-built tree — pick the install strategy by verification level before spending the clock.** A dist-level harness needs a full `npm ci` at head (the `prepare` build included; measured ~8 min warm-cache on a 12-core aarch64 box). A prototype/source-level harness whose bundler compiles the TS sources directly (e.g. `scripts/sandbox-prototype/build.mjs`) needs only `npm ci --ignore-scripts` (~20 s warm) plus the bundle step. For the base control with an untouched lockfile, `cp -al` the head tree's `node_modules` (root plus any per-package ones) into the base worktree: hardlinks are free, and the relative `@qwen-code/*` symlinks then resolve into the base tree, which the realpath assertion above can confirm. Do not symlink the directory itself — the internal links would resolve through it back into the head tree and both arms would run head code. - Report the cell table: environment per cell, observable oracle per cell (exit code, stderr line, wire request, rendered frame), and `X/Y` at head vs control. "5/9 flip from broken to fixed" is the shape to aim for. - When a change **suppresses** output — a removed notice, a narrowed log, a swallowed error — check whether the information survives anywhere before calling the suppression correct. Follow the value: is the cause still carried in a field someone reads? Grep the repo for that field; a bare `catch {}` on the path and a field with no readers anywhere means the reason is now unobservable even in devtools. Losing "which failure was this" is a real regression even when hiding the message was the goal, and it is invisible to any behavioural assertion. - Probe the type boundaries of the changed expression, not just the reported repro: a coercion/conversion fix gets cells for `null`, boolean, object, and astral inputs, and lossy results (e.g. `String({})` → `"[object Object]"`) are called out in Findings even when every scripted assertion passes. A fix that holds only for the reported input shape is a finding, not a pass. (This overlaps the next bullet, and the overlap is deliberate: the sibling-sweep text below is the one rule in this file with a measured before/after behind it, so it stays byte-identical to the instrument the arms actually read. Consolidating the pair means editing that instrument, which is a change to make with a fresh measurement, not on the way past.) - **A fix that closes one instance of a bug class gets its siblings swept.** When the mechanism is a parser, sanitizer, matcher, or state machine, the reported input is one door into a room with several: enumerate the adjacent shapes the same root cause admits — the backtick code-span sibling of a fenced-block rule, the indented form an `^ {0,3}`-anchored regex never matches, the CRLF variant of an LF scanner — and drive each through the fixed build. Measured example: a sanitizer taught that a fence line inside a raw-HTML block is not a fence still passed live HTML through code spans in the same block, and for a fold nested in a list never entered the HTML-block state at all — same root cause as the Critical just fixed, one level down, found only by walking the neighbouring doors. The fix's own new test pins the reported shape by construction; the siblings are exactly what it does not pin. - **Untrusted text reaching a parser is a scaling question, not only a correctness one.** When the PR adds or changes a regex, tokenizer, or scanner that runs over input an outsider writes — a PR body, a diff, a log line, a filename — probe it with a **ladder** rather than a single case: the same hostile shape at 2 k, 3 k, 5 k, 20 k characters, timed. Run each rung under `timeout 30` and record the cap as the result (`>30 s`); the rung that hits the cap is the evidence, and no rung is worth more of the budget than that. The superlinear curve across rungs is the finding; one fast sample proves nothing. Measured example: a line matcher whose three parts could each match a space (`\s*`, a lazy `[^*\n]+?`, `\s*`) took 0.96 s, 3.2 s, 14.4 s, then over 100 s on `**` followed by 2 k / 3 k / 5 k / 20 k spaces — run once per line over a body GitHub caps at 65,536 characters. Two cheap checks decide whether it matters: **trace the input back to a writer** (whose text is it — can a fork contributor author it?), and **verify the claimed escape hatch really excludes the path** — "only trusted PRs reach this" was false there, because a fork PR still matched a local remote and ran the same command. Then prove the fix behaviour-preserving by **enumerating the real inputs** and showing identical output on each, not by arguing the two patterns are equivalent. - If the changed branch is unreachable in the default setup (a fallback, a `dist` path, an error handler), **construct the configuration that reaches it** — drop the tsconfig mapping, break the primary path, force the fallback — rather than declaring it untestable. A branch nobody can reach is itself a finding. - For size/performance claims the A/B cells are **measured metrics** (bytes, file counts, calls, ms) in a table with a Δ column, attributed to the change — and every residual delta gets accounted for ("the closure is 1.3 KB larger: that is the new guards themselves"). An unexplained residue is a finding, not noise. - **Isolate the slice the mechanism can actually affect, then show what fraction of the total it is.** A speedup claim is really two claims: the mechanism works, and the thing it speeds up matters. Add an arm that strips everything the mechanism cannot touch — measured example: an npm download cache was claimed to cut `npm ci` by ~75%; running with `--ignore-scripts` isolated pure download+extract at 36 s cold of a 226 s install, and warming just that slice removed 20 s of it (36 s → 16 s) — the cache's ceiling. End-to-end the install went 226 s → 193 s, a 15% saving rather than the claimed 75%, the rest of the cost being the repo's own `postinstall`/`tsc`/bundler work. Then check that saving against the **whole job budget**: 33 s off a 14 m 37 s job is not the headline the description claimed. A perf PR whose mechanism works but targets 15% of the cost is a finding about the premise, not the code. - **A mechanism that persists something has a cost, not only a benefit — price it.** Caches, artifacts and generated entries consume a shared, bounded resource. Measure what it adds (219 MB per lockfile hash), what the pool holds (9.98 GB of a 10 GB cap), and the churn rate (39 distinct lockfile states in 30 days) — because at the cap every new entry evicts by LRU, including entries other jobs depend on, and possibly its own, degrading the very hit rate the saving assumes. And when the PR **states** a cost, audit it against the repo's own accounting of the same mechanism: a base worktree was priced as "one extra build", while a sibling probe tree in the same subsystem documents that a tree nested under the repo resolves `node_modules` by walking up to the root and needs no per-tree install. The base tree is nested identically — so either the install is avoidable and the stated cost becomes true, or the reasoning next door is wrong. A reviewer is agreeing to spend whichever it is. - **Test the scarier consequences and report which ones do NOT hold.** Having found a real problem, the temptation is to report the worst reading of it. Bound it instead: in the cache case the write-path finding was real (a post-step uploads the directory that untrusted code can write), but code injection was **disproved** — tampering with a cached tarball made npm reject it against the lockfile hash and refetch under the flag CI uses, and all 2262 lockfile entries carry an `integrity` hash, so nothing installs unhashed — and privilege escalation was **disproved** — `chown -R` does not follow symlinks. What survived was content and quota abuse. A finding that names what it is _not_ is far harder to wave away than one that implies everything. - **An accepted-tradeoff list is a completeness claim — test its boundary.** When the description names the costs it accepts ("links and images will render"), enumerate the unnamed siblings of the same mechanism and drive them; the measured case found issue cross-references firing — `cross-referenced` timeline events stamped on arbitrary issues under the bot identity — as the sibling the accepted list did not name. An unnamed cost is a finding about the description even when the cost itself would have been accepted. - When the PR adds a defensive guard or shape check, its unit tests usually mock the reject path — so verify the **accept path against the real artifacts it will see in production** (the shipped chunks, the real module namespaces, the actual wire payloads). A guard that is too strict fails in production on a path no mocked test covers. - **When one fix bundles two changes, build the intermediate variants.** An A/B against base proves the pair works; it says nothing about what each half does or whether both are needed. Compile a third build with one half reverted and put all three in one table. Worked example, on a first-poll drain fix that both replaced `Math.max(...spread)` with `reduce()` and moved `initialized = true` after the fallible work: | build | RangeError | prompts dispatched | cursor saved | | ----------------------------- | ---------- | ---------------------- | ------------ | | base (`Math.max`, flag first) | yes | **2,999 and climbing** | none | | flag moved only | yes | 0 | none | | both (head) | no | 0 | saved | The ordering change is what converts a backlog flood into a fail-safe retry; `reduce()` is what restores liveness. Either alone leaves a channel that floods or wedges — a conclusion the two-cell A/B cannot reach. - **A limit measured in isolation does not transfer to the real call site.** Argument-count caps, stack depth, buffer sizes and timeouts all move with context: the same `Math.max` spread threw between 110k and 130k elements inside a deep async stack, well below what a standalone micro-benchmark suggests. Bisect the threshold **through the real code path**, and quote the harness you bisected with — a limit quoted from documentation or from a toy loop is a guess about the system under test. - **When the same predicate is checked in two places, verify they see the same state.** A guard duplicated across a process boundary — a route and the child it spawns, a parent and a worker, a cache and its source — is two implementations of one question, and they diverge whenever their _inputs_ differ rather than their logic. Find the configuration that makes them disagree and drive it: one measured case had the route ask `sessionExistsInAnyState()` with an unpinned runtime dir while the child asked it with a pinned one, so a single settings key flipped a clean 409 into a 500 plus a `process.exit(1)` that killed every session on the channel. Two related questions expose most of this class: does one side observe state the other cannot, and **is the state observable yet at all** — lazily-created backing files (`ensureConversationFile()` writes nothing until the first prompt) leave a window in which a just-created entity is invisible to any existence check that looks on disk. - **A capability has two ends — check the one that accepts, not only the one that issues.** Where the PR gates who may _mint_ a credential, token, cookie, or permit, find the code that _accepts_ it and check that the same condition guards it. The two drift because they are written at different times by different concerns, and the tell is that the tests are named after the gated end, which makes the ungated end look covered. Measured example: a cookie→`Authorization` bridge was correctly gated to a desktop shell on the minting side, while the accepting middleware was mounted unconditionally — so every server instance treated that cookie as a bearer. Bound it as usual: no exploit was demonstrated, but `SameSite` does not separate `127.0.0.1:<other-port>` from the daemon's port, because for an IP host the "site" ignores the port. - **Measure the blast radius on bystanders, not just on the caller.** When a failure path can take down shared infrastructure, the interesting number is what happened to everything else: an unrelated session going
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub