test: preserve wrapper behavior for targeted runs#45518
Conversation
🔒 Aisle Security AnalysisWe found 3 potential security issue(s) in this PR:
1. 🟠 Unbounded synchronous recursive test-file traversal at module load can DoS CI
Description
Impact (availability / CI DoS):
Vulnerable code (executed on every run): const walkTestFiles = (rootDir) => {
const entries = fs.readdirSync(rootDir, { withFileTypes: true });
const files = [];
for (const entry of entries) {
const fullPath = path.join(rootDir, entry.name);
if (entry.isDirectory()) {
files.push(...walkTestFiles(fullPath));
continue;
}
...
}
return files;
};
const allKnownTestFiles = [
...new Set([
...walkTestFiles("src"),
...walkTestFiles("extensions"),
...walkTestFiles("test"),
...walkTestFiles(path.join("ui", "src", "ui")),
]),
];RecommendationMitigate CI/availability risk by making enumeration lazy and bounded:
let allKnownTestFiles = null;
const getAllKnownTestFiles = () => {
if (allKnownTestFiles) return allKnownTestFiles;
allKnownTestFiles = [
...new Set([
...walkTestFiles("src"),
...walkTestFiles("extensions"),
...walkTestFiles("test"),
...walkTestFiles(path.join("ui", "src", "ui")),
]),
];
return allKnownTestFiles;
};
2. 🔵 Command injection risk on Windows due to
|
| Property | Value |
|---|---|
| Severity | Low |
| CWE | CWE-78 |
| Location | scripts/test-parallel.mjs:633-637 |
Description
The test wrapper forwards user-controlled CLI arguments into a spawn() call that enables a Windows shell.
- Input:
process.argvis parsed intopassthroughOptionArgs(and file filters that becomefiltersintargetedEntries). - Sink: those values are appended into the argument list passed to
spawn(pnpm, args, { shell: isWindows }). - Risk: when
shell: trueon Windows, Node executes viacmd.exeand must serialize arguments into a command line. If any forwarded arg contains CMD metacharacters (e.g.,&,|,<,>,^,%VAR%,!), it can be interpreted by the shell, enabling command injection in environments where an attacker can influence these args (e.g., automated tooling/CI wrappers that pass untrusted filters/options).
Vulnerable code:
const rawPassthroughArgs = process.argv.slice(2);
// ...
const { fileFilters: passthroughFileFilters, optionArgs: passthroughOptionArgs } =
parsePassthroughArgs(passthroughArgs);
// ... later
child = spawn(pnpm, args, {
stdio: "inherit",
env: { ...process.env, VITEST_GROUP: entry.name, NODE_OPTIONS: resolvedNodeOptions },
shell: isWindows,
});Even though spawn() is called with an args array, shell: true forces command-line serialization, which is the core injection primitive on Windows.
Recommendation
Avoid invoking a shell when forwarding user-controlled arguments.
Preferred: run pnpm via Node (no shell) using npm_execpath when available:
const pnpmJs = process.env.npm_execpath;
const useNodeForPnpm = Boolean(pnpmJs);
const child = useNodeForPnpm
? spawn(process.execPath, [pnpmJs, ...args], {
stdio: "inherit",
env: { ...process.env, VITEST_GROUP: entry.name, NODE_OPTIONS: resolvedNodeOptions },
shell: false,
})
: spawn(pnpm, args, {
stdio: "inherit",
env: { ...process.env, VITEST_GROUP: entry.name, NODE_OPTIONS: resolvedNodeOptions },
// consider failing fast on Windows here, or using a hardened escaping strategy
shell: false,
});If you must keep shell: true on Windows for .cmd resolution, escape/quote arguments for cmd.exe (caret-escape metacharacters, handle % expansion, etc.) or use a well-tested library that performs correct Windows shell escaping.
3. 🔵 Test wrapper allows absolute/parent-path filters to be passed to Vitest (potential execution of files outside repo)
| Property | Value |
|---|---|
| Severity | Low |
| CWE | CWE-22 |
| Location | scripts/test-parallel.mjs:367-383 |
Description
The new targeted test-filter resolution accepts arbitrary filesystem paths from CLI args and can forward them directly to vitest run without restricting them to repo-contained test files.
- Input: untrusted/unchecked
fileFilterfromprocess.argv(non-flag arguments) - Logic:
- If the supplied filter exists and is a file, it is returned verbatim (after separator normalization) with no validation that it is within the repository or that it matches
*.test.ts. - If no known test files match (e.g., when an absolute/
../directory is provided), the filter is still forwarded to Vitest via thematchedFiles.length === 0fallback.
- If the supplied filter exists and is a file, it is returned verbatim (after separator normalization) with no validation that it is within the repository or that it matches
- Sink:
spawn(pnpm, ["vitest","run",..., ...filters])
If an attacker can influence the arguments passed to pnpm test -- <filter> in an automated context (e.g., a CI job that forwards user-provided filters), they could cause Vitest to load/execute a JS/TS file outside the repo by passing an absolute path or ../ traversal path that exists on the runner.
Vulnerable code:
if (fs.existsSync(fileFilter)) {
const stats = fs.statSync(fileFilter);
if (stats.isFile()) {
return [normalizedFilter];
}
}and later:
if (matchedFiles.length === 0) {
...
files.push(normalizeRepoPath(fileFilter));
}Recommendation
Restrict targeted filters to repo-relative paths and/or the precomputed allowlist of known test files.
Suggested hardening:
- Reject absolute paths and any path containing
..segments. - Resolve to an absolute path and ensure it stays within the repo root.
- Only allow files that match expected test suffixes.
Example:
const repoRoot = process.cwd();
function isSafeRepoPath(input) {
if (path.isAbsolute(input)) return false;
const normalized = input.split(path.sep).join("/");
if (normalized.split("/").includes("..")) return false;
const resolved = path.resolve(repoRoot, input);
const rel = path.relative(repoRoot, resolved);
return !!rel && !rel.startsWith("..") && !path.isAbsolute(rel);
}
const resolveFilterMatches = (fileFilter) => {
if (!isSafeRepoPath(fileFilter)) {
throw new Error(`Refusing unsafe test filter: ${fileFilter}`);
}
const normalizedFilter = normalizeRepoPath(fileFilter);
// only return files from allKnownTestFiles
if (fs.existsSync(fileFilter) && fs.statSync(fileFilter).isFile()) {
if (!/\.(live\.)?(e2e\.)?test\.ts$/.test(normalizedFilter)) {
throw new Error(`Refusing non-test file filter: ${fileFilter}`);
}
}
...
};Additionally, remove the fallback that forwards unmatched filters directly to Vitest; instead, require that filters resolve to at least one known test file under allKnownTestFiles.
Analyzed PR: #45518 at commit e967026
Last updated on: 2026-03-14T00:02:17Z
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: db426dc25e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Greptile SummaryThis PR replaces the raw Changes
Issues found
Confidence Score: 3/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: scripts/test-parallel.mjs
Line: 325-334
Comment:
**`unitIsolatedFiles` check unreachable for channel-prefix and base-config-prefix files**
Several files listed in `unitIsolatedFiles` (e.g. `src/telegram/bot.test.ts`, `src/telegram/bot.create-telegram-bot.test.ts`, `src/web/media.test.ts`, `src/browser/server.auth-token-gates-http.test.ts`, `src/agents/bash-tools.test.ts`, `src/agents/pi-embedded-runner.test.ts`, etc.) will never reach the `unitIsolatedFiles.includes()` branch on line 332. The `channelPrefixes` guard on line 325 and the `baseConfigPrefixes` guard on line 328 short-circuit first.
In the full suite those files are run via the `unit-isolated` entry: `vitest.unit.config.ts --pool=forks`. In a targeted run, this routing sends them to `vitest.channels.config.ts` (no `--pool` specified) or `vitest.config.ts` instead, dropping the fork-isolation that those files were specifically moved into `unitIsolatedFiles` to preserve (e.g. "Uses process-level unhandledRejection listeners; keep it off vmForks", "Mutates process.cwd() and mocks core module loaders; isolate from the shared fast lane").
If the intent is to preserve full-suite isolation for these files in targeted runs too, the `unitIsolatedFiles.includes()` check needs to come before the `channelPrefixes` and `baseConfigPrefixes` guards:
```js
const inferTargetKind = (fileFilter) => {
if (fileFilter.endsWith(".live.test.ts")) return "live";
if (fileFilter.endsWith(".e2e.test.ts")) return "e2e";
if (fileFilter.startsWith("extensions/")) return "extensions";
if (fileFilter.startsWith("src/gateway/")) return "gateway";
// Check isolation list before prefix routing so files like
// src/telegram/bot.test.ts keep --pool=forks in targeted runs.
if (unitIsolatedFiles.includes(fileFilter)) return "unit-isolated";
if (channelPrefixes.some((prefix) => fileFilter.startsWith(prefix))) return "channels";
if (baseConfigPrefixes.some((prefix) => fileFilter.startsWith(prefix))) return "base";
if (fileFilter.startsWith("src/")) return "unit";
return "base";
};
```
If routing those files to their "native" config (channels/base) in targeted runs is intentional, the comment in `unitIsolatedFiles` for those entries should note the discrepancy, and the dead `unitIsolatedFiles.includes()` branch should be removed or documented.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: scripts/test-parallel.mjs
Line: 246-251
Comment:
**`--reporter` in `SINGLE_RUN_ONLY_FLAGS` is overly conservative**
`--coverage`, `--outputFile`, and `--mergeReports` all write shared file artifacts that can conflict when multiple wrapper lanes run in parallel — blocking those makes sense. `--reporter` by itself only changes the display format (e.g. `--reporter=verbose`, `--reporter=dot`) and produces no output file unless paired with `--outputFile`. Blocking it unconditionally means `pnpm test -- --reporter=verbose` fails on a full-suite run, even though each lane printing verbose output doesn't conflict with any other lane.
Consider removing `--reporter` from `SINGLE_RUN_ONLY_FLAGS`:
```suggestion
const SINGLE_RUN_ONLY_FLAGS = new Set([
"--coverage",
"--outputFile",
"--mergeReports",
]);
```
If blocking `--reporter` is intentional (e.g. to avoid interleaved multi-lane output confusion), a code comment explaining why would help future maintainers.
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: db426dc |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e96702623a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| resolve(1); | ||
| return; | ||
| if (targetedEntries.length > 0) { | ||
| if (passthroughRequiresSingleRun && targetedEntries.length > 1) { |
There was a problem hiding this comment.
Guard single-run flags when targeted runs are sharded
The single-run check only rejects multi-config targeting (targetedEntries.length > 1), but a single targeted entry can still execute multiple Vitest invocations when sharding is enabled (for example Windows CI defaults to 2 shards, or OPENCLAW_TEST_SHARDS>1). In that case flags like --coverage/--outputFile run once per shard and can clobber or fragment artifacts, which defeats the new single-run protection in exactly the environments where sharding is active.
Useful? React with 👍 / 👎.
| fullPath.endsWith(".live.test.ts") || | ||
| fullPath.endsWith(".e2e.test.ts") |
There was a problem hiding this comment.
Exclude e2e/live files from broad targeted path expansion
Broad filters are expanded from allKnownTestFiles, which currently includes .live.test.ts and .e2e.test.ts; this makes commands like pnpm test -- src/plugins or pnpm test -- src/browser fan out into vitest.live.config.ts/vitest.e2e.config.ts runs even though the default pnpm test lane excludes those suites. That regression can unexpectedly pull in slow or environment-dependent tests during normal targeted unit debugging.
Useful? React with 👍 / 👎.
…claw#45518) * test: preserve wrapper behavior for targeted runs * test: tighten targeted wrapper routing
…claw#45518) * test: preserve wrapper behavior for targeted runs * test: tighten targeted wrapper routing
* refactor: share discord binding update loop * test: dedupe discord route fixture setup * refactor: share discord trailing media delivery * test: dedupe model info reply setup * test: dedupe inline action skip assertions * test: dedupe discord forwarded media assertions * test: dedupe discord retry delivery setup * test: dedupe discord gateway proxy register flow * test: dedupe discord provider account config harness * refactor: share discord channel override config type * refactor: share session entry persistence update * refactor: share discord preflight shared fields * test: dedupe discord listener deferred setup * test: dedupe session idle timeout assertions * test: dedupe discord bound slash dispatch setup * test: dedupe discord queue preflight setup * test: dedupe discord preflight helpers * refactor: share discord exec approval helpers * refactor: share auto reply helper fixtures * refactor: share embedded run and discord test helpers * refactor: share self hosted provider auth flow * test: share zalouser test helpers * refactor: share bluebubbles multipart helpers * test: share synology channel harness * test: share feishu monitor startup mocks * test: share matrix sdk test mocks * test: reuse feishu streaming merge helper * test: simplify mattermost token summary fixtures * test: share pairing setup resolution assertions * test: preserve wrapper behavior for targeted runs FIX OOM issues(openclaw#45518) * test: preserve wrapper behavior for targeted runs * test: tighten targeted wrapper routing * fix: tighten path guard coverage * fix(imessage): sanitize SCP remote path to prevent shell metacharacter injection References GHSA-g2f6-pwvx-r275. * fix: tighten runtime status coverage * fix: tighten package json coverage * fix: tighten bonjour error coverage * fix: tighten package tag coverage * fix: tighten machine name coverage * test: tighten gateway process argv coverage * test: tighten install safe path coverage * test: tighten tmp dir fallback coverage * test: tighten brew helper coverage * test: add archive staging helper coverage * fix: tighten device identity helper coverage * UI: fix chat context notice icon sizing (openclaw#45533) * UI: fix chat context notice icon sizing * Update ui/src/ui/views/chat.browser.test.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * UI: tighten chat context notice regression test --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * test: tighten transport ready coverage * test: tighten channel summary coverage * test: tighten safe bin policy coverage * fix: tighten safe bin runtime policy coverage * fix: tighten duration formatter coverage * fix: harden browser existing-session flows * test: tighten fetch helper coverage * test: extract provider usage load coverage * test: extract fingerprint helper coverage * test: add gateway tls helper coverage * test: extract archive helper coverage * test: extract apns relay coverage * test: extract apns auth helper coverage * test: extract apns store coverage * test: add device bootstrap coverage * test: add state migration coverage * test: tighten apns send coverage * fix(ui): stop dashboard chat history reload storm (openclaw#45541) * UI: stop dashboard chat history reload storm * Changelog: add PR number for chat reload fix * fix: resolve branch typecheck regressions * test: tighten fetch and channel summary coverage * fix: retry Telegram inbound media downloads over IPv4 fallback (openclaw#45327) * fix: retry telegram inbound media downloads over ipv4 * fix: preserve telegram media retry errors * fix: redact telegram media fetch errors * fix: harden bootstrap and transport ready coverage * test: expand browser existing-session coverage * fix: tighten package tag and channel summary coverage * fix: tighten runtime status detail coverage * fix: support bun lockfile detection * test: add home relative path coverage * test: tighten json file lock coverage * test: tighten path prepend casing coverage * refactor: share models command helpers * test: share cli help version assertions * test: share venice model response fixtures * test: share browser loopback auth error assertions * test: share config pruning defaults setup * test: share cron telegram delivery failure assertions * test: share agent acp turn helpers * test: share systemd service test helpers * test: share scheduled task stop helpers * test: share lane delivery final helpers * test: share outbound media fallback helpers * test: share telegram sticky fetch helpers * test: share embedded compaction hook helpers * test: share sanitize session usage helpers * test: share telegram draft stream helpers * test: share telegram account helpers * test: share line webhook gating helpers * test: share heartbeat scheduler helpers * test: share config-only channel status helpers * test: share restart health helpers * test: share lifecycle config guard helpers * test: share daemon cli service helpers * test: share qr cli setup code helpers * test: share gateway chat run helpers * refactor: share daemon lifecycle restart helpers * refactor: share daemon launchd and path helpers * test: share schtasks gateway script fixture * test: share startup auth token fixtures * test: share gateway reload helpers * test: share plugin http auth helpers * test: share gateway hook and cron helpers * test: share gateway chat history setup * refactor: share gateway chat text normalization * refactor: share gateway connection auth options * test: share channel health helpers * refactor: share plugin directory helpers * refactor: share browser route helpers * refactor: share cli install helpers * test: tighten system run command coverage * test: add parallels windows smoke harness * fix: force-stop lingering gateway client sockets * test: share gateway route auth helpers * test: share browser route test helpers * test: share gateway status auth fixtures * test: share models list forward compat fixtures * fix: tighten bonjour whitespace error coverage * docs: reorder changelog highlights by user impact * test: tighten proxy fetch helper coverage * test: tighten path guard helper coverage * test: tighten warning filter coverage * test: tighten wsl detection coverage * test: tighten system run command normalization coverage * fix(feishu): preserve non-ASCII filenames in file uploads (openclaw#33912) (openclaw#34262) * fix(feishu): preserve non-ASCII filenames in file uploads (openclaw#33912) * style(feishu): format media test file * fix(feishu): preserve UTF-8 filenames in file uploads (openclaw#34262) thanks @fabiaodemianyang --------- Co-authored-by: Robin Waslander <[email protected]> * test: tighten is-main helper coverage * test: tighten json file helper coverage * fix: resolve current ci regressions * test: tighten backoff abort coverage * docs(changelog): note upcoming security fixes * test: tighten bonjour ciao coverage * test: tighten channel activity account isolation * test: tighten update channel display precedence * test: tighten node list parse fallback coverage * test: tighten package tag prefix matching * test: tighten outbound identity normalization * test: tighten outbound session context coverage * macOS: respect exec-approvals.json settings in gateway prompter (openclaw#13707) Fix macOS gateway exec approvals to respect exec-approvals.json. This updates the macOS gateway prompter to resolve per-agent exec approval policy before deciding whether to show UI, use agentId for policy lookup, honor askFallback when prompts cannot be presented, and resolve no-prompt decisions from the configured security policy instead of hardcoded allow-once behavior. It also adds regression coverage for ask-policy and allowlist-fallback behavior, plus a changelog entry for the fix. Co-authored-by: ImLukeF <[email protected]> * fix: tighten target error hint coverage * test: tighten prototype key matching * test: tighten hostname normalization coverage * fix(ui): keep oversized chat replies readable (openclaw#45559) * fix(ui): keep oversized chat replies readable * Update ui/src/ui/markdown.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * fix(ui): preserve oversized markdown whitespace --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * test: tighten openclaw exec env coverage * fix: tighten pairing token blank handling * test: tighten target error hint trimming * test: tighten node shell platform normalization * fix(gateway/ui): restore control-ui auth bypass and classify connect failures (openclaw#45512) Merged via squash. Prepared head SHA: 42b5595 Co-authored-by: sallyom <[email protected]> Co-authored-by: BunsDev <[email protected]> Reviewed-by: @BunsDev * fix(macos): prevent PortGuard from killing Docker Desktop in remote mode (openclaw#13798) fix(macos): prevent PortGuardian from killing Docker Desktop in remote mode (openclaw#6755) PortGuardian.sweep() was killing non-SSH processes holding the gateway port in remote mode. When the gateway runs in a Docker container, `com.docker.backend` owns the port-forward, so this could shut down Docker Desktop entirely. Changes: - accept any process on the gateway port in remote mode - add a defense-in-depth guard to skip kills in remote mode - update remote-mode port diagnostics/reporting to match - add regression coverage for Docker and local-mode behavior - add a changelog entry for the fix Co-Authored-By: ImLukeF <[email protected]> * test: fix current ci regressions * test: share outbound action runner helpers * test: share telegram monitor startup helpers * refactor: share self hosted provider plugin helpers * test: share outbound delivery helpers * refactor: share onboarding diagnostics type * refactor: share delimited channel entry parsing * refactor: share zalo send context validation * refactor: share terminal note wrapping * refactor: share tts request setup * refactor: share gateway timeout parsing * refactor: share session send context lines * refactor: share memory tool builders * refactor: share browser console result formatting * refactor: share pinned sandbox entry finalization * refactor: share tool result char estimation * refactor: share agent tool fixture helpers * test: share compaction retry timer helpers * test: share embedded workspace attempt helpers * refactor: share whatsapp outbound adapter base * refactor: share zalo status issue helpers * test: share whatsapp outbound poll fixtures * refactor: share telegram reply chunk threading * refactor: share daemon install cli setup * fix: widen telegram reply progress typing * refactor: share slack text truncation * refactor: share allowlist wildcard matching * refactor: declone model picker model ref parsing * refactor: share dual text command gating * test: share startup account lifecycle helpers * test: share status issue assertion helpers * fix: restore imessage control command flag * test: share web fetch header helpers * refactor: share session tool context setup * test: share memory tool helpers * refactor: share request url resolution * Changelog: credit embedded runner queue deadlock fix * fix(voicewake): avoid crash on foreign transcript ranges * refactor(voicewake): mark transcript parameter unused * docs(changelog): note voice wake crash fix * fix: harden gateway status rpc smoke * test: add parallels linux smoke harness * fix(sessions): create transcript file on chat.inject when missing (openclaw#36645) `chat.inject` called `appendAssistantTranscriptMessage` with `createIfMissing: false`, causing a hard error when the transcript file did not exist on disk despite having a valid `transcriptPath` in session metadata. This commonly happens with ACP oneshot/run sessions where the session entry is created but the transcript file is not yet materialized. The fix is a one-character change: `createIfMissing: true`. The `ensureTranscriptFile` helper already handles directory creation and file initialization safely. Fixes openclaw#36170 Co-authored-by: Claude Opus 4.6 <[email protected]> * fix: harden discord guild allowlist resolution * chore: update dependencies * Plugins: fail fast on channel and binding collisions (openclaw#45628) * Plugins: reject duplicate channel ids * Bindings: reject duplicate adapter registration * Plugins: fail on export id mismatch * feat: add node-connect skill * test: share directory runtime helpers * refactor: share open allowFrom config checks * test: share send cfg threading helpers * refactor: reduce extension channel setup duplication * refactor: share extension channel status summaries * test: share feishu startup mock modules * test: share plugin api test harness * refactor: share extension monitor runtime setup * refactor: share extension deferred and runtime helpers * test: share sandbox fs bridge seeded workspace * test: share subagent gateway mock setup * test: share models config merge helpers * test: share workspace skills snapshot helpers * test: share context lookup helpers * test: share timeout failover assertions * test: share model selection config helpers * test: share provider discovery auth fixtures * test: share subagent announce timeout helpers * test: share workspace skill test helpers * refactor: share exec host approval helpers * test: share oauth profile fixtures * test: share memory search config helpers * fix(macos): align minimum Node.js version with runtime guard (22.16.0) (openclaw#45640) * macOS: align minimum Node.js version with runtime guard * macOS: add boundary and failure-message coverage for RuntimeLocator * docs: add changelog note for the macOS runtime locator fix * credit: original fix direction from @sumleo, cleaned up and rebased in openclaw#45640 by @ImLukeF * fix(agents): preserve blank local custom-provider API keys after onboarding Co-authored-by: Xinhua Gu <[email protected]> * fix(browser): harden existing-session driver validation and session lifecycle (openclaw#45682) * fix(browser): harden existing-session driver validation, session lifecycle, and code quality Fix config validation rejecting existing-session profiles that lack cdpPort/cdpUrl (they use Chrome MCP auto-connect instead). Fix callTool tearing down the MCP session on tool-level errors (element not found, script error), which caused expensive npx re-spawns. Skip unnecessary CDP port allocation for existing-session profiles. Remove redundant ensureChromeMcpAvailable call in isReachable. Extract shared ARIA role sets (INTERACTIVE_ROLES, CONTENT_ROLES, STRUCTURAL_ROLES) into snapshot-roles.ts so both the Playwright and Chrome MCP snapshot paths stay in sync. Add usesChromeMcp capability flag and replace ~20 scattered driver === "existing-session" string checks with the centralized flag. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(browser): harden existing-session driver validation and session lifecycle (openclaw#45682) (thanks @odysseus0) --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> * fix(ci): repair helper typing regressions * fix: default Android TLS setup codes to port 443 * fix: unblock discord startup on deploy rate limits * fix(feishu): add early event-level dedup to prevent duplicate replies (openclaw#43762) * fix(feishu): add early event-level dedup to prevent duplicate replies Add synchronous in-memory dedup at EventDispatcher handler level using message_id as key with 5-minute TTL and 2000-entry cap. This catches duplicate events immediately when they arrive from the Lark SDK — before the inbound debouncer or processing queue — preventing the race condition where two concurrent dispatches enter the pipeline before either records the messageId in the downstream dedup layer. Fixes the root cause reported in openclaw#42687. * fix(feishu): correct inverted dedup condition check() returns false on first call (new key) and true on subsequent calls (duplicate). The previous `!check()` guard was inverted — dropping every first delivery and passing all duplicates. Remove the negation so the guard correctly drops duplicates. * fix(feishu): simplify eventDedup key — drop redundant accountId prefix eventDedup is already scoped per account (one instance per registerEventHandlers call), so the accountId prefix in the cache key is redundant. Use `evt:${messageId}` instead. * fix(feishu): share inbound processing claim dedupe --------- Co-authored-by: Tak Hoffman <[email protected]> * fix(models): apply Gemini model-id normalization to google-vertex provider (openclaw#42435) * fix(models): apply Gemini model-id normalization to google-vertex provider The existing normalizeGoogleModelId() (which maps e.g. gemini-3.1-flash-lite to gemini-3.1-flash-lite-preview) was only applied when the provider was "google". Users configuring google-vertex/gemini-3.1-flash-lite would get a "missing" model because the -preview suffix was never appended. Extend the normalization to google-vertex in both model-selection (parseModelRef path) and normalizeProviders (config normalization path). Ref: openclaw#36838 Ref: openclaw#36918 (comment) * fix(models): normalize google-vertex flash-lite * fix(models): place unreleased changelog entry last * fix(models): place unreleased changelog entry before releases * fix(browser): add browser session selection * build(android): add auto-bump signed aab release script * build(android): strip unused dnsjava resolver service before R8 * test(discord): align rate limit error mock with carbon * docs: fix changelog formatting * fix: keep exec summaries inline * build: shrink Android app release bundle * Gateway: treat scope-limited probe RPC as degraded reachability (openclaw#45622) * Gateway: treat scope-limited probe RPC as degraded * Docs: clarify gateway probe degraded scope output * test: fix CI type regressions in gateway and outbound suites * Tests: fix Node24 diffs theme loading and Windows assertions * Tests: fix extension typing after main rebase * Tests: fix Windows CI regressions after rebase * Tests: normalize executable path assertions on Windows * Tests: remove duplicate gateway daemon result alias * Tests: stabilize Windows approval path assertions * Tests: fix Discord rate-limit startup fixture typing * Tests: use Windows-friendly relative exec fixtures --------- Co-authored-by: Mainframe <[email protected]> * build: upload Android native debug symbols * fix(browser): prefer user profile over chrome relay * chore: bump pi to 0.58.0 * test: harden parallels all-os smoke harness * fix: keep windows onboarding logs ascii-safe * docs: reorder unreleased changelog by impact * build: prepare 2026.3.13-beta.1 * ci: add npm token fallback for npm releases * fix(gateway): bound unanswered client requests (openclaw#45689) * fix(gateway): bound unanswered client requests * fix(gateway): skip default timeout for expectFinal requests * fix(gateway): preserve gateway call timeouts * fix(gateway): localize request timeout policy * fix(gateway): clamp explicit request timeouts * fix(gateway): clamp default request timeout * Revert "Browser: scope nested batch failures in switch" This reverts commit aaeb348. * build: prepare 2026.3.13 release * fix: keep android canvas home visible after restart * chore: update appcast for 2026.3.13 release * fix(browser): restore batch playwright dispatch * fix(harbor): preserve shared-auth scopes and harbor runtime behavior --------- Co-authored-by: Peter Steinberger <[email protected]> Co-authored-by: Tak Hoffman <[email protected]> Co-authored-by: Robin Waslander <[email protected]> Co-authored-by: Val Alexander <[email protected]> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: Frank Yang <[email protected]> Co-authored-by: fabiaodemianyang <[email protected]> Co-authored-by: Steven <[email protected]> Co-authored-by: ImLukeF <[email protected]> Co-authored-by: Sally O'Malley <[email protected]> Co-authored-by: sallyom <[email protected]> Co-authored-by: Jaehoon You <[email protected]> Co-authored-by: Vincent Koc <[email protected]> Co-authored-by: 2233admin <[email protected]> Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: Ayaan Zaidi <[email protected]> Co-authored-by: Xinhua Gu <[email protected]> Co-authored-by: George Zhang <[email protected]> Co-authored-by: yunweibang <[email protected]> Co-authored-by: scoootscooob <[email protected]> Co-authored-by: Muhammed Mukhthar CM <[email protected]> Co-authored-by: Josh Avant <[email protected]> Co-authored-by: Mainframe <[email protected]>
…claw#45518) * test: preserve wrapper behavior for targeted runs * test: tighten targeted wrapper routing (cherry picked from commit bff340c)
…claw#45518) * test: preserve wrapper behavior for targeted runs * test: tighten targeted wrapper routing
…claw#45518) * test: preserve wrapper behavior for targeted runs * test: tighten targeted wrapper routing (cherry picked from commit bff340c)
…claw#45518) * test: preserve wrapper behavior for targeted runs * test: tighten targeted wrapper routing (cherry picked from commit bff340c)
Summary
Describe the problem and fix in 2–5 bullets:
pnpm test -- ...forwarded passthrough args into a rawvitest run ...fallback, bypassing the repo test wrapper’s config/profile/pool routing.vitest.unit.config.ts.scripts/test-parallel.mjsnow parses passthrough args, routes explicit file targets through the correct wrapper-owned config/lane, preserves worker/profile behavior, and rejects single-run-only artifact flags when they would fan out across multiple wrapper lanes.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
User-visible / Behavior Changes
pnpm test -- <path-or-filter> [vitest args...]now keeps targeted runs on the repo wrapper instead of falling back to raw Vitest.vitest.unit.config.ts(for examplesrc/commands/**) are routed through the appropriate config.--coveragenow fail fast when they would span multiple wrapper configs, instead of producing incorrect shared artifacts.Security Impact (required)
No)No)No)No)No)Yes, explain risk + mitigation:Repro + Verification
Environment
Steps
pnpm test -- src/commands/onboard-search.test.ts -t "shows registered plugin providers" --passWithNoTests.pnpm test -- --coverage.node scripts/test-parallel.mjs -- src/commands/onboard-search.test.ts src/plugins/loader.test.ts --coverage.Expected
Actual
--coveragenow fails fast with a clear single-run error instead of producing multi-lane artifact conflicts.--coveragealso fails fast with a clear error.Evidence
Attach at least one:
Human Verification (required)
What you personally verified (not just CI), and how:
PATH=/Users/thoffman/openclaw/node_modules/.bin:$PATH node scripts/test-parallel.mjs -- src/commands/onboard-search.test.ts -t "shows registered plugin providers" --passWithNoTestspnpm test -- --coveragenow errors before spawning conflicting multi-lane runsnode scripts/test-parallel.mjs -- src/commands/onboard-search.test.ts src/plugins/loader.test.ts --coveragenow errors for multi-config single-run flagssrc/commands/**routes through the wrapper instead of raw Vitest fallbackReview Conversations
If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.
Compatibility / Migration
Yes)No)No)Failure Recovery (if this breaks)
scripts/test-parallel.mjsto restore the prior passthrough behaviorscripts/test-parallel.mjs, optionallyAGENTS.mdRisks and Mitigations
List only real risks for this PR. Add/remove entries as needed. If none, write
None.