Skip to content

test: preserve wrapper behavior for targeted runs#45518

Merged
Takhoffman merged 2 commits intomainfrom
codex/test-wrapper-targeted
Mar 13, 2026
Merged

test: preserve wrapper behavior for targeted runs#45518
Takhoffman merged 2 commits intomainfrom
codex/test-wrapper-targeted

Conversation

@Takhoffman
Copy link
Copy Markdown
Contributor

Summary

Describe the problem and fix in 2–5 bullets:

  • Problem: pnpm test -- ... forwarded passthrough args into a raw vitest run ... fallback, bypassing the repo test wrapper’s config/profile/pool routing.
  • Why it matters: targeted debugging could behave differently from the repo’s intended test path, including different worker and memory behavior and incorrect config selection for files excluded from vitest.unit.config.ts.
  • What changed: scripts/test-parallel.mjs now 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.
  • What did NOT change (scope boundary): default full-suite wrapper behavior is unchanged, and this does not modify Vitest config contents or test files.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #
  • Related #

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.
  • Targeted files excluded from vitest.unit.config.ts (for example src/commands/**) are routed through the appropriate config.
  • Single-run-only flags like --coverage now fail fast when they would span multiple wrapper configs, instead of producing incorrect shared artifacts.

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: Node + pnpm
  • Model/provider: N/A
  • Integration/channel (if any): N/A
  • Relevant config (redacted): default local repo config

Steps

  1. Run pnpm test -- src/commands/onboard-search.test.ts -t "shows registered plugin providers" --passWithNoTests.
  2. Run pnpm test -- --coverage.
  3. Run node scripts/test-parallel.mjs -- src/commands/onboard-search.test.ts src/plugins/loader.test.ts --coverage.

Expected

  • Targeted file runs stay on the wrapper path and honor wrapper config/profile/pool routing.
  • Single-run-only artifact flags do not fan out across multiple wrapper lanes/configs.

Actual

  • Targeted wrapper run succeeded and executed the requested file/name filter.
  • --coverage now fails fast with a clear single-run error instead of producing multi-lane artifact conflicts.
  • Multi-config targeted --coverage also fails fast with a clear error.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios:
    • PATH=/Users/thoffman/openclaw/node_modules/.bin:$PATH node scripts/test-parallel.mjs -- src/commands/onboard-search.test.ts -t "shows registered plugin providers" --passWithNoTests
    • pnpm test -- --coverage now errors before spawning conflicting multi-lane runs
    • node scripts/test-parallel.mjs -- src/commands/onboard-search.test.ts src/plugins/loader.test.ts --coverage now errors for multi-config single-run flags
  • Edge cases checked:
    • targeted file in src/commands/** routes through the wrapper instead of raw Vitest fallback
    • single-run-only flags are blocked for both broad and multi-config targeted runs
  • What you did not verify:
    • full suite execution
    • CI behavior
    • extension/gateway/live/e2e targeted runs beyond routing logic

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

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

  • Backward compatible? (Yes)
  • Config/env changes? (No)
  • Migration needed? (No)
  • If yes, exact upgrade steps:

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: revert scripts/test-parallel.mjs to restore the prior passthrough behavior
  • Files/config to restore: scripts/test-parallel.mjs, optionally AGENTS.md
  • Known bad symptoms reviewers should watch for: targeted wrapper runs selecting the wrong config, or artifact flags unexpectedly running across multiple lanes

Risks and Mitigations

List only real risks for this PR. Add/remove entries as needed. If none, write None.

  • Risk:
    • Some uncommon Vitest flags may still need explicit single-run handling if they write shared artifacts.
    • Mitigation:
      • the known artifact-emitting flags are explicitly blocked from multi-lane fanout, and normal targeted file/name filtering was exercised manually.

@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot bot commented Mar 13, 2026

🔒 Aisle Security Analysis

We found 3 potential security issue(s) in this PR:

# Severity Title
1 🟠 High Unbounded synchronous recursive test-file traversal at module load can DoS CI
2 🔵 Low Command injection risk on Windows due to spawn(..., { shell: true }) with user-controlled Vitest args
3 🔵 Low Test wrapper allows absolute/parent-path filters to be passed to Vitest (potential execution of files outside repo)

1. 🟠 Unbounded synchronous recursive test-file traversal at module load can DoS CI

Property Value
Severity High
CWE CWE-400
Location scripts/test-parallel.mjs:309-341

Description

scripts/test-parallel.mjs now builds allKnownTestFiles by recursively walking multiple large directories (src, extensions, test, ui/src/ui) synchronously and unconditionally at module execution time.

Impact (availability / CI DoS):

  • The traversal runs even when no targeted file filters are provided (common CI case), adding potentially significant overhead to every test invocation.
  • walkTestFiles() has no depth limit, file-count limit, or cycle protection and uses recursion (files.push(...walkTestFiles(fullPath))). A malicious PR can add a very deep or very large directory tree under these roots to cause:
    • long synchronous filesystem traversal time (CI timeout)
    • high memory usage (accumulating files and Set of all tests)
    • potential maximum call stack size exceeded crash from deep recursion

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")),
  ]),
];

Recommendation

Mitigate CI/availability risk by making enumeration lazy and bounded:

  1. Only compute allKnownTestFiles when actually needed (i.e., when passthroughFileFilters.length > 0 and the filter is not already a concrete file path):
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;
};
  1. Replace recursive traversal with an iterative approach and enforce limits:
  • max depth (e.g., 50)
  • max files (e.g., 50k)
  • skip symlinks explicitly (entry.isSymbolicLink())
  • consider using git ls-files for tracked test files instead of walking the filesystem.
  1. Prefer async IO (or at least avoid doing sync IO at import time) to reduce blocking and improve resilience.

2. 🔵 Command injection risk on Windows due to spawn(..., { shell: true }) with user-controlled Vitest args

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.argv is parsed into passthroughOptionArgs (and file filters that become filters in targetedEntries).
  • Sink: those values are appended into the argument list passed to spawn(pnpm, args, { shell: isWindows }).
  • Risk: when shell: true on Windows, Node executes via cmd.exe and 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 fileFilter from process.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 the matchedFiles.length === 0 fallback.
  • 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:

  1. Reject absolute paths and any path containing .. segments.
  2. Resolve to an absolute path and ensure it stays within the repo root.
  3. 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

@openclaw-barnacle openclaw-barnacle bot added scripts Repository scripts size: M maintainer Maintainer-authored PR labels Mar 13, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 13, 2026

Greptile Summary

This PR replaces the raw vitest run passthrough fallback in scripts/test-parallel.mjs with a proper routing layer that infers the correct Vitest config and pool lane per targeted file, blocks single-artifact flags (--coverage, --outputFile, etc.) from fanning out across multiple wrapper configs, and documents the updated workflow in AGENTS.md.

Changes

  • Adds OPTION_TAKES_VALUE and SINGLE_RUN_ONLY_FLAGS sets for structured arg parsing
  • parsePassthroughArgs splits passthrough args into file filters vs. option flags
  • inferTargetKind maps a file path to the appropriate config lane (unit, unit-isolated, channels, gateway, extensions, live, e2e, base)
  • createTargetedEntry constructs the correct vitest invocation (config + pool) for each lane
  • run / runEntries gain an extraArgs parameter so option flags are forwarded to every spawned process
  • Full-suite path (no file filters) is unchanged except that single-run-only flags now fail fast with a clear error

Issues found

  • The unitIsolatedFiles.includes() guard in inferTargetKind is unreachable for any file that also matches channelPrefixes (e.g. src/telegram/bot.test.ts, src/web/media.test.ts, multiple src/browser/server.*.test.ts) or baseConfigPrefixes (e.g. src/agents/bash-tools.test.ts, src/agents/pi-embedded-runner.test.ts, src/auto-reply/reply.block-streaming.test.ts). In the full suite those files run via vitest.unit.config.ts --pool=forks; in a targeted run they are routed to vitest.channels.config.ts (no --pool) or vitest.config.ts, silently dropping the fork-isolation those files were moved to unitIsolatedFiles to preserve.
  • --reporter in SINGLE_RUN_ONLY_FLAGS unconditionally blocks display-only reporter overrides for full-suite runs (e.g. pnpm test -- --reporter=verbose), even though a display reporter produces no conflicting file artifact across lanes.

Confidence Score: 3/5

  • Merging is low risk for CI/default runs, but targeted runs of certain isolated files will silently use the wrong config and lose fork isolation.
  • The full-suite and CI paths are unchanged. The new targeted routing is a clear improvement over the raw vitest fallback. However, the unitIsolatedFiles.includes() check is dead code for files under channelPrefixes and baseConfigPrefixes, meaning targeted runs of those files (e.g. src/telegram/bot.test.ts, src/agents/bash-tools.test.ts) silently drop the --pool=forks isolation that the full suite relies on for stability. This could produce flaky or misleading results during local debugging, which is the primary use-case this PR targets.
  • The inferTargetKind function in scripts/test-parallel.mjs (lines 312–335) needs attention: the unitIsolatedFiles priority ordering and the --reporter flag in SINGLE_RUN_ONLY_FLAGS.
Prompt To Fix All With AI
This 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

@Takhoffman Takhoffman merged commit bff340c into main Mar 13, 2026
24 of 31 checks passed
@Takhoffman Takhoffman deleted the codex/test-wrapper-targeted branch March 13, 2026 23:36
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment on lines +326 to +327
fullPath.endsWith(".live.test.ts") ||
fullPath.endsWith(".e2e.test.ts")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

frankekn pushed a commit to xinhuagu/openclaw that referenced this pull request Mar 14, 2026
…claw#45518)

* test: preserve wrapper behavior for targeted runs

* test: tighten targeted wrapper routing
ecochran76 pushed a commit to ecochran76/openclaw that referenced this pull request Mar 14, 2026
…claw#45518)

* test: preserve wrapper behavior for targeted runs

* test: tighten targeted wrapper routing
jbencook added a commit to harborworks/openclaw that referenced this pull request Mar 16, 2026
* 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]>
Interstellar-code pushed a commit to Interstellar-code/operator1 that referenced this pull request Mar 16, 2026
…claw#45518)

* test: preserve wrapper behavior for targeted runs

* test: tighten targeted wrapper routing

(cherry picked from commit bff340c)
sbezludny pushed a commit to sbezludny/openclaw that referenced this pull request Mar 27, 2026
…claw#45518)

* test: preserve wrapper behavior for targeted runs

* test: tighten targeted wrapper routing
alexey-pelykh pushed a commit to remoteclaw/remoteclaw that referenced this pull request Mar 27, 2026
…claw#45518)

* test: preserve wrapper behavior for targeted runs

* test: tighten targeted wrapper routing

(cherry picked from commit bff340c)
alexey-pelykh pushed a commit to remoteclaw/remoteclaw that referenced this pull request Mar 27, 2026
…claw#45518)

* test: preserve wrapper behavior for targeted runs

* test: tighten targeted wrapper routing

(cherry picked from commit bff340c)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintainer Maintainer-authored PR scripts Repository scripts size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant