Skip to content

QA: fix private runtime source loading#67428

Merged
gumadeiras merged 9 commits into
mainfrom
codex/private-qa-runtime-pr
Apr 16, 2026
Merged

QA: fix private runtime source loading#67428
gumadeiras merged 9 commits into
mainfrom
codex/private-qa-runtime-pr

Conversation

@gumadeiras

Copy link
Copy Markdown
Member

Summary

  • fix private QA wrapper/runtime path resolution for source checkouts
  • share local-only plugin-sdk subpath metadata between alias paths
  • make isolated QA gateway staging load the right bundled plugin tree under Node

Testing

  • pnpm test src/infra/run-node.test.ts src/plugin-sdk/qa-runner-runtime.test.ts src/plugins/sdk-alias.test.ts src/cli/program/register.subclis.test.ts extensions/qa-lab/src/gateway-child.test.ts extensions/qa-lab/src/scenario-flow-runner.test.ts
  • pnpm build
  • pnpm openclaw qa matrix --provider-mode mock-openai --scenario matrix-thread-follow-up --output-dir .artifacts/qa-e2e/pr1-matrix-smoke
  • pnpm openclaw qa suite --transport qa-channel --provider-mode mock-openai --scenario bundled-plugin-skill-runtime --output-dir .artifacts/qa-e2e/pr1-qa-channel-smoke

Copilot AI review requested due to automatic review settings April 15, 2026 23:18
@aisle-research-bot

aisle-research-bot Bot commented Apr 15, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

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

# Severity Title
1 🟠 High Arbitrary code execution via AsyncFunction expression evaluation with exposed qaImport dynamic import
2 🟠 High Untrusted search path for Node resolution allows PATH hijacking (executing attacker-controlled node binary)
3 🟠 High Symlink-following arbitrary deletion risk in QA runtime staging (fs.rm on repo-controlled .artifacts path)
4 🟡 Medium Dynamic import of private QA CLI from filesystem path derived from cwd/argv enables local code execution when env flag is set
1. 🟠 Arbitrary code execution via AsyncFunction expression evaluation with exposed qaImport dynamic import
Property Value
Severity High
CWE CWE-94
Location extensions/qa-lab/src/scenario-flow-runner.ts:68-83

Description

scenario-flow-runner evaluates scenario expr strings from qa-flow YAML using AsyncFunction/Function, and now explicitly exposes qaImport(specifier) => import(specifier) in the evaluation context.

If QA scenario markdown/YAML can be modified by an untrusted party (e.g., a PR author, a downloaded scenario pack, or any user-supplied scenario content), this becomes straightforward arbitrary code execution:

  • Input: expr / detailsExpr strings are read from qa/scenarios/*.md (YAML fences) and parsed into QaScenarioFlow.
  • Execution sink: new AsyncFunction(...names, \return (${expr});`)` executes the attacker-controlled string.
  • Amplification in this change: qaImport allows importing powerful Node.js modules (e.g. node:child_process, node:fs) or other specifiers from inside expressions, enabling command execution / file reads without relying on ambient globals.

Vulnerable code (new behavior enabling arbitrary imports):

function createEvalContext(api: QaFlowApi, vars: QaFlowVars) {
  return {
    ...api,
    qaImport: (specifier: string) => import(specifier),
    vars,
    ...vars,
  };
}

Because evalExpr and buildLambda compile and run these expressions with AsyncFunction/Function, a malicious scenario can do e.g.:

(await qaImport('node:child_process')).execSync('...')

This is effectively unsandboxed code execution within the process running QA flows.

Recommendation

Treat scenario expr/detailsExpr as untrusted code and avoid evaluating them with Function/AsyncFunction.

Options (pick based on needed functionality):

  1. Remove qaImport from the expression context, and disallow imports entirely in expressions.

  2. If imports are required, strictly allowlist safe specifiers and block node: and path-like specifiers:

const ALLOWED_IMPORTS = new Set(["node:path", "node:url"]);
function qaImport(specifier: string) {
  if (!ALLOWED_IMPORTS.has(specifier)) {
    throw new Error(`Import not allowed: ${specifier}`);
  }
  return import(specifier);
}
  1. Replace AsyncFunction evaluation with a real sandbox / expression language:
  • parse expressions (e.g., jsep) and interpret against a safe AST
  • or use vm/vm2-style isolation (with careful hardening), ensuring no access to Node builtins (process, require, dynamic import, etc.)

At minimum, document and enforce that scenario flow files are trusted-only, and prevent execution of scenarios from untrusted sources in CI/CD contexts.

2. 🟠 Untrusted search path for Node resolution allows PATH hijacking (executing attacker-controlled node binary)
Property Value
Severity High
CWE CWE-426
Location extensions/qa-lab/src/node-exec.ts:27-56

Description

resolveQaNodeExecPath() resolves the Node executable by running which node/where node and then uses the returned path to spawn child processes.

  • When the parent runtime is Bun (or process.execPath is not already Node), the code falls back to the first node found in PATH.
  • The lookup uses the current environment by default (env is omitted by call sites), so a manipulated PATH can cause an attacker-controlled node binary/script to be selected.
  • The resolved path is then executed via spawn() in gateway-child.ts and model-catalog.runtime.ts, inheriting a large environment that may contain secrets.

Vulnerable code:

const locator = platform === "win32" ? "where" : "which";
({ stdout } = await execFileImpl(locator, ["node"], {
  encoding: "utf8",
  env: params?.env,
}));
...
return resolved;

If an attacker can influence PATH in the environment where QA is run (e.g., CI jobs, developer shells, wrapper scripts), this becomes an execution of an attacker-controlled binary (RCE).

Recommendation

Avoid resolving the executable via untrusted PATH.

Options (choose one appropriate to the product’s distribution model):

  1. Prefer a trusted absolute Node path
  • If running under Node already, always use process.execPath.
  • If running under Bun, require an explicit configuration/env var that points to a Node binary (absolute path), and validate it.
  1. Harden PATH lookup
  • Call the locator with a sanitized PATH (e.g., system directories only) and reject results outside trusted prefixes.
  • Resolve symlinks and validate the target.

Example (explicit config + validation):

import { access } from "node:fs/promises";
import { constants } from "node:fs";

export async function resolveQaNodeExecPath(): Promise<string> {
  if (process.versions.bun !== undefined) {
    const configured = process.env.OPENCLAW_QA_NODE_PATH;
    if (!configured || !path.isAbsolute(configured)) {
      throw new Error("Set OPENCLAW_QA_NODE_PATH to an absolute Node executable path");
    }
    await access(configured, constants.X_OK);
    return configured;
  }
  return process.execPath;
}

This prevents executing arbitrary node from an attacker-controlled PATH.

3. 🟠 Symlink-following arbitrary deletion risk in QA runtime staging (fs.rm on repo-controlled .artifacts path)
Property Value
Severity High
CWE CWE-59
Location extensions/qa-lab/src/bundled-plugin-staging.ts:354-361

Description

createQaBundledPluginsDir constructs a staging directory under ${repoRoot}/.artifacts/qa-runtime/${basename(tempRoot)} and then unconditionally deletes it with fs.rm(..., { recursive: true, force: true }).

Because the path is under a repo-controlled directory (.artifacts), a malicious checkout (or untrusted workspace) can replace .artifacts (or .artifacts/qa-runtime) with a symlink to an arbitrary location. When the QA suite runs, the code will then recursively delete outside the repository.

Key points:

  • Attacker control: If an attacker can influence the repo working tree contents, they can create .artifacts as a symlink to another directory (e.g. ~, /var/tmp, etc.).
  • Dangerous sink: fs.rm(stagedRoot, { recursive: true, force: true }) performs a recursive deletion on that attacker-influenced path.
  • path.basename(tempRoot) is safe when tempRoot comes from mkdtemp, but it does not mitigate the symlink issue in the parent directories under repoRoot.

Vulnerable code:

const stagedRoot = path.join(
  params.repoRoot,
  ".artifacts",
  "qa-runtime",
  path.basename(params.tempRoot),
);
await fs.rm(stagedRoot, { recursive: true, force: true });

Recommendation

Harden staging directory creation/deletion against symlink attacks:

  1. Resolve and validate parent directories (.artifacts and .artifacts/qa-runtime) using lstat/realpath and ensure they are real directories (not symlinks).
  2. Ensure the final stagedRoot resolves under an expected base directory before deleting.
  3. Consider using a dedicated temp dir outside the repo (OS temp) for staging, avoiding repo-controlled paths entirely.

Example mitigation (one approach):

const artifactsDir = path.join(params.repoRoot, ".artifacts");
const qaRuntimeDir = path.join(artifactsDir, "qa-runtime");

for (const p of [artifactsDir, qaRuntimeDir]) {
  if (existsSync(p)) {
    const st = await fs.lstat(p);
    if (st.isSymbolicLink()) throw new Error(`Refusing to use symlinked path: ${p}`);
    if (!st.isDirectory()) throw new Error(`Expected directory: ${p}`);
  }
}

await fs.mkdir(qaRuntimeDir, { recursive: true });
const stagedRoot = path.join(qaRuntimeDir, path.basename(params.tempRoot));

const realBase = await fs.realpath(qaRuntimeDir);
const realTargetParent = await fs.realpath(path.dirname(stagedRoot));
if (realTargetParent !== realBase) throw new Error("Unexpected stagedRoot parent");

await fs.rm(stagedRoot, { recursive: true, force: true });

This prevents .artifacts-symlink tricks from redirecting recursive deletion outside the repo.

4. 🟡 Dynamic import of private QA CLI from filesystem path derived from cwd/argv enables local code execution when env flag is set
Property Value
Severity Medium
CWE CWE-427
Location src/cli/program/private-qa-cli.ts:24-42

Description

loadPrivateQaCliModule() dynamically imports a JS module from a packageRoot discovered via resolveOpenClawPackageRootSync({ moduleUrl, argv1, cwd }), and buildCandidates() includes cwd as a fallback.

When OPENCLAW_ENABLE_PRIVATE_QA_CLI=1, an attacker who can influence the process working directory (or argv[1] resolution) can cause packageRoot to resolve to an attacker-controlled directory that merely contains:

  • a package.json with {"name":"openclaw"} (satisfies root resolution)
  • marker paths .git and src
  • the target module dist/plugin-sdk/qa-lab.js

The code then imports that file URL and executes it. This turns the private QA flag into an untrusted search path mechanism that can execute arbitrary code from the filesystem.

Vulnerable code:

const packageRoot = resolvePackageRootSync({ argv1, cwd, moduleUrl });
...
const sourceModulePath = path.join(packageRoot, "dist/plugin-sdk/qa-lab.js");
...
return (await import(pathToFileURL(sourceModulePath).href)) as Record<string, unknown>;

Notes:

  • The .git/src checks are not an authenticity check; an attacker can create them.
  • This is gated by OPENCLAW_ENABLE_PRIVATE_QA_CLI=1, but if that flag is enabled in a compromised environment (CI job, dev shell, or wrapper script), it provides a straightforward escalation to code execution.

Recommendation

Avoid resolving the import target from attacker-influenceable locations (notably cwd). Prefer a trusted anchor:

  • Only resolve from import.meta.url of the currently executing, installed openclaw package, and do not fall back to cwd.
  • Additionally, validate the resolved root more strongly (e.g., require a specific monorepo layout plus package.json name and repository-specific markers).
  • Consider requiring an explicit absolute path env var (e.g. OPENCLAW_PRIVATE_QA_ROOT=/abs/path) and refuse to run otherwise.

Example hardening (remove cwd fallback):

const packageRoot = resolveOpenClawPackageRootSync({
  moduleUrl: import.meta.url,
  argv1: process.argv[1],// no cwd fallback
});
if (!packageRoot) throw new Error("...");

If you must allow cwd, require explicit opt-in to cwd-based resolution and display the resolved path before import, requiring confirmation in interactive use.


Analyzed PR: #67428 at commit b8bf2b6

Last updated on: 2026-04-16T02:02:03Z

@openclaw-barnacle openclaw-barnacle Bot added cli CLI command changes scripts Repository scripts channel: qa-channel Channel integration: qa-channel extensions: qa-lab size: XL maintainer Maintainer-authored PR labels Apr 15, 2026
@gumadeiras gumadeiras self-assigned this Apr 15, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes private QA runtime/module resolution so local source checkouts and isolated QA gateway staging consistently load the intended plugin tree and runtime helpers.

Changes:

  • Add a private, local-only plugin-sdk subpath list (gated by OPENCLAW_ENABLE_PRIVATE_QA_CLI) and include it in plugin-sdk alias/subpath resolution and caching.
  • Improve QA discovery/staging for source checkouts and isolated gateway child runs (including Node exec-path resolution when parent runtime is bun).
  • Update QA scenarios/flow runner to support qaImport() in flow expressions and adjust scenarios accordingly.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/plugins/sdk-alias.ts Adds gated private subpath listing + cache-keying for alias/subpath enumeration.
src/plugins/sdk-alias.test.ts Adds tests for private QA subpaths and cache invalidation behavior.
src/plugin-sdk/root-alias.cjs Extends root alias mapping to include private QA subpaths and adjusts alias precedence.
src/plugin-sdk/qa-runner-runtime.ts Overrides manifest discovery env to prefer source extensions/ when in a repo checkout and private QA is enabled.
src/plugin-sdk/qa-runner-runtime.test.ts Tests private QA discovery preferring the source bundled tree.
src/infra/run-node.test.ts Adds coverage for skipping/requiring rebuilds for QA commands based on presence of the QA CLI facade artifact.
src/cli/program/register.subclis.test.ts Updates mocking to the new QA CLI module specifier.
src/cli/program/private-qa-cli.ts Switches private QA CLI module load to a package subpath specifier.
scripts/run-node.mjs Points private QA dist entry check to dist/plugin-sdk/qa-lab.js.
scripts/lib/plugin-sdk-private-local-only-subpaths.json Defines the private local-only plugin-sdk subpaths (qa-lab, qa-runtime).
qa/scenarios/session-memory-ranking.md Writes stale memory into a directory instead of a single file path.
qa/scenarios/bundled-plugin-skill-runtime.md Uses qaImport() for runtime imports inside flow expressions.
extensions/qa-lab/src/scenario-flow-runner.ts Exposes qaImport() to flow expression evaluation context.
extensions/qa-lab/src/scenario-flow-runner.test.ts Adds test validating qaImport() works in flow expressions.
extensions/qa-lab/src/qa-transport-registry.ts Refactors transport registry and adds default concurrency plumbing.
extensions/qa-lab/src/qa-channel-transport.ts Adds a default suite concurrency constant.
extensions/qa-lab/src/node-exec.ts Adds Node executable resolution (notably for bun parent runtimes).
extensions/qa-lab/src/node-exec.test.ts Tests Node exec-path resolution behavior and error messaging.
extensions/qa-lab/src/model-catalog.runtime.ts Spawns model-catalog CLI using resolved Node exec path instead of process.execPath.
extensions/qa-lab/src/gateway-child.ts Uses shared staging helpers and spawns gateway child with resolved Node exec path.
extensions/qa-lab/src/gateway-child.test.ts Updates tests for new staging helpers and verifies import behavior from staged runtime roots.
extensions/qa-lab/src/bundled-plugin-staging.ts New shared implementation for bundled plugin staging, owner resolution, and min host version detection.
extensions/qa-channel/src/runtime.ts Updates runtime store creation to pass pluginId metadata.

Comment thread src/cli/program/private-qa-cli.ts Outdated
Comment thread extensions/qa-lab/src/qa-transport-registry.ts Outdated
Comment thread src/plugin-sdk/root-alias.cjs Outdated
@greptile-apps

greptile-apps Bot commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes private QA runtime source loading for repo checkouts. The main changes are: (1) extracting bundled plugin staging logic from gateway-child.ts into a new bundled-plugin-staging.ts module that now supports source-only checkouts (no built dist) by falling back through dist/extensionsdist-runtime/extensionsextensions/; (2) adding resolveQaNodeExecPath to correctly spawn Node.js when the parent runtime is Bun; (3) making private local-only plugin SDK subpaths (qa-lab, qa-runtime) discoverable via the alias system when OPENCLAW_ENABLE_PRIVATE_QA_CLI=1 is set; and (4) updating the loadPrivateQaCliModule specifier from a split-string relative import to "openclaw/plugin-sdk/qa-lab" to leverage the alias infrastructure.

Confidence Score: 5/5

Safe to merge; changes are well-scoped to QA infrastructure, all key paths are covered by new tests, and no production API surfaces are broken.

All findings are P2 or non-existent. The core logic (per-plugin dist/source fallback, Node exec resolution, alias cache invalidation) is correct and tests exercise the new source-checkout path including ESM import resolution through the staged openclaw package. No correctness or data-integrity issues found.

No files require special attention.

Reviews (1): Last reviewed commit: "QA: fix private runtime source loading" | Re-trigger Greptile

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

Copy link
Copy Markdown
Contributor

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: bc88642665

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/cli/program/private-qa-cli.ts Outdated
Comment thread extensions/qa-lab/src/qa-transport-registry.ts Outdated

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

Copy link
Copy Markdown
Contributor

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: cd4235557e

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/run-node.mjs

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

Copy link
Copy Markdown
Contributor

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: 72ddcc98d9

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread extensions/qa-lab/src/gateway-child.ts Outdated

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

Copy link
Copy Markdown
Contributor

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: cf296d62bd

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread extensions/qa-lab/src/bundled-plugin-staging.ts Outdated

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

Copy link
Copy Markdown
Contributor

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: 4cca44b64e

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/plugin-sdk/qa-runner-runtime.ts Outdated
Fix the private QA wrapper and source-checkout runtime paths so
qa-lab, qa-channel, and qa-matrix resolve their local-only SDK
surfaces and staged bundled plugins reliably.

This keeps private QA behavior local-only, restores source-runner
discovery, and makes the isolated gateway runtime load the right
plugin tree under Node.
@gumadeiras
gumadeiras force-pushed the codex/private-qa-runtime-pr branch from 6957704 to b8bf2b6 Compare April 16, 2026 01:58
@gumadeiras
gumadeiras merged commit d5933af into main Apr 16, 2026
11 of 12 checks passed
@gumadeiras

Copy link
Copy Markdown
Member Author

Merged via squash.

Thanks @gumadeiras!

@gumadeiras
gumadeiras deleted the codex/private-qa-runtime-pr branch April 16, 2026 01:59

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

Copy link
Copy Markdown
Contributor

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: b8bf2b6be6

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread extensions/qa-lab/src/node-exec.ts
xudaiyanzi pushed a commit to xudaiyanzi/openclaw that referenced this pull request Apr 17, 2026
Merged via squash.

Prepared head SHA: b8bf2b6
Co-authored-by: gumadeiras <[email protected]>
Co-authored-by: gumadeiras <[email protected]>
Reviewed-by: @gumadeiras
kvnkho pushed a commit to kvnkho/openclaw that referenced this pull request Apr 17, 2026
Merged via squash.

Prepared head SHA: b8bf2b6
Co-authored-by: gumadeiras <[email protected]>
Co-authored-by: gumadeiras <[email protected]>
Reviewed-by: @gumadeiras
Mquarmoc pushed a commit to Mquarmoc/openclaw that referenced this pull request Apr 20, 2026
Merged via squash.

Prepared head SHA: b8bf2b6
Co-authored-by: gumadeiras <[email protected]>
Co-authored-by: gumadeiras <[email protected]>
Reviewed-by: @gumadeiras
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
Merged via squash.

Prepared head SHA: b8bf2b6
Co-authored-by: gumadeiras <[email protected]>
Co-authored-by: gumadeiras <[email protected]>
Reviewed-by: @gumadeiras
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
Merged via squash.

Prepared head SHA: b8bf2b6
Co-authored-by: gumadeiras <[email protected]>
Co-authored-by: gumadeiras <[email protected]>
Reviewed-by: @gumadeiras
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
Merged via squash.

Prepared head SHA: b8bf2b6
Co-authored-by: gumadeiras <[email protected]>
Co-authored-by: gumadeiras <[email protected]>
Reviewed-by: @gumadeiras
globalcaos pushed a commit to globalcaos/tinkerclaw that referenced this pull request May 13, 2026
Merged via squash.

Prepared head SHA: b8bf2b6
Co-authored-by: gumadeiras <[email protected]>
Co-authored-by: gumadeiras <[email protected]>
Reviewed-by: @gumadeiras
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
Merged via squash.

Prepared head SHA: b8bf2b6
Co-authored-by: gumadeiras <[email protected]>
Co-authored-by: gumadeiras <[email protected]>
Reviewed-by: @gumadeiras
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
Merged via squash.

Prepared head SHA: b8bf2b6
Co-authored-by: gumadeiras <[email protected]>
Co-authored-by: gumadeiras <[email protected]>
Reviewed-by: @gumadeiras
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: qa-channel Channel integration: qa-channel cli CLI command changes extensions: qa-lab maintainer Maintainer-authored PR scripts Repository scripts size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants