From 8e237ca8dd7f2abf42e617b31ad7fb635714ce08 Mon Sep 17 00:00:00 2001 From: Aaron Erickson Date: Wed, 20 May 2026 12:49:15 -0700 Subject: [PATCH 1/6] test(e2e): use sandbox status command in scenario helpers Signed-off-by: Aaron Erickson --- .../e2e-lib-helpers.test.ts | 17 +++++++++++++++-- .../lib/baseline_onboarding.sh | 2 +- .../validation_suites/lib/sandbox_lifecycle.sh | 2 +- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/test/e2e/scenario-framework-tests/e2e-lib-helpers.test.ts b/test/e2e/scenario-framework-tests/e2e-lib-helpers.test.ts index 19a5906280..8514fadab5 100644 --- a/test/e2e/scenario-framework-tests/e2e-lib-helpers.test.ts +++ b/test/e2e/scenario-framework-tests/e2e-lib-helpers.test.ts @@ -562,7 +562,13 @@ describe("baseline onboarding validation helper", () => { const ctx = path.join(tmp, "ctx"); fs.mkdirSync(bin); fs.mkdirSync(ctx); fs.writeFileSync(path.join(ctx, "context.env"), "E2E_SANDBOX_NAME=sb1\nE2E_PROVIDER=nvidia\nE2E_INFERENCE_ROUTE=inference-local\n"); - fs.writeFileSync(path.join(bin, "nemoclaw"), "#!/usr/bin/env bash\n[[ $1 == --help ]] && echo help && exit 0\n", { mode: 0o755 }); + fs.writeFileSync(path.join(bin, "nemoclaw"), `#!/usr/bin/env bash +case "$*" in + --help) echo help;; + "sb1 status") echo 'status running gateway healthy sandbox running';; + *) echo "unexpected nemoclaw args: $*" >&2; exit 64;; +esac +`, { mode: 0o755 }); fs.writeFileSync(path.join(bin, "openshell"), "#!/usr/bin/env bash\nexit 0\n", { mode: 0o755 }); const r = runBash(` set -euo pipefail @@ -571,11 +577,13 @@ describe("baseline onboarding validation helper", () => { baseline_assert_nemoclaw_on_path baseline_assert_openshell_on_path baseline_assert_nemoclaw_help_exits_zero + baseline_assert_sandbox_status_exits_zero `, { E2E_CONTEXT_DIR: ctx, PATH: `${bin}:${process.env.PATH}` }); expect(r.status, r.stderr).toBe(0); expect(r.stdout).toContain("PASS: validation.baseline_onboarding.nemoclaw_on_path"); expect(r.stdout).toContain("PASS: validation.baseline_onboarding.openshell_on_path"); expect(r.stdout).toContain("PASS: validation.baseline_onboarding.nemoclaw_help_exits_zero"); + expect(r.stdout).toContain("PASS: validation.baseline_onboarding.sandbox_status"); } finally { fs.rmSync(tmp, { recursive: true, force: true }); } }); }); @@ -614,7 +622,12 @@ describe("sandbox lifecycle validation helper", () => { try { const bin = path.join(tmp, "bin"); fs.mkdirSync(bin); fs.writeFileSync(path.join(bin, "nemoclaw"), `#!/usr/bin/env bash -case "$1" in list) echo sb1;; status) echo 'status running gateway healthy sandbox running';; logs) echo logline;; esac +case "$*" in + list) echo sb1;; + "sb1 status") echo 'status running gateway healthy sandbox running';; + "logs sb1") echo logline;; + *) echo "unexpected nemoclaw args: $*" >&2; exit 64;; +esac `, { mode: 0o755 }); fs.writeFileSync(path.join(bin, "openshell"), `#!/usr/bin/env bash echo lifecycle-ok diff --git a/test/e2e/validation_suites/lib/baseline_onboarding.sh b/test/e2e/validation_suites/lib/baseline_onboarding.sh index 231962a310..f3661a4b73 100755 --- a/test/e2e/validation_suites/lib/baseline_onboarding.sh +++ b/test/e2e/validation_suites/lib/baseline_onboarding.sh @@ -53,7 +53,7 @@ baseline_assert_sandbox_list_contains_context_sandbox() { baseline_assert_sandbox_status_exits_zero() { local out - if out=$(nemoclaw status "$E2E_SANDBOX_NAME" 2>&1); then + if out=$(nemoclaw "$E2E_SANDBOX_NAME" status 2>&1); then baseline_onboarding_pass validation.baseline_onboarding.sandbox_status "$E2E_SANDBOX_NAME status ok" else baseline_onboarding_fail validation.baseline_onboarding.sandbox_status "status failed: ${out:0:200}" diff --git a/test/e2e/validation_suites/lib/sandbox_lifecycle.sh b/test/e2e/validation_suites/lib/sandbox_lifecycle.sh index 00b8ce244c..083e797dd4 100755 --- a/test/e2e/validation_suites/lib/sandbox_lifecycle.sh +++ b/test/e2e/validation_suites/lib/sandbox_lifecycle.sh @@ -73,7 +73,7 @@ sandbox_lifecycle_assert_nemoclaw_list_contains_sandbox() { sandbox_lifecycle_assert_status_fields_present() { local id="validation.sandbox_operations.status_fields_present" - sandbox_lifecycle_run_with_timeout 20 nemoclaw status "${E2E_SANDBOX_NAME}" >/dev/null || { + sandbox_lifecycle_run_with_timeout 20 nemoclaw "${E2E_SANDBOX_NAME}" status >/dev/null || { sandbox_lifecycle_fail "${id}" "nemoclaw status failed" return 1 } From 44faed1b2442339dba327fc222c5508dc898c808 Mon Sep 17 00:00:00 2001 From: Aaron Erickson Date: Wed, 20 May 2026 13:01:20 -0700 Subject: [PATCH 2/6] test(e2e): restore WeChat plugin load path --- scripts/seed-wechat-accounts.py | 51 ++++++++++++++++++++------- test/generate-openclaw-config.test.ts | 10 +++++- test/seed-wechat-accounts.test.ts | 40 +++++++++++++++++++++ 3 files changed, 88 insertions(+), 13 deletions(-) diff --git a/scripts/seed-wechat-accounts.py b/scripts/seed-wechat-accounts.py index 5c4935c2e4..d28524dc83 100755 --- a/scripts/seed-wechat-accounts.py +++ b/scripts/seed-wechat-accounts.py @@ -13,15 +13,16 @@ # Files written (matching auth/accounts.ts in @tencent-weixin/openclaw-weixin@2.4.2): # /openclaw-weixin/accounts.json — JSON array of accountIds # /openclaw-weixin/accounts/.json — { token, savedAt, baseUrl, userId } -# /openclaw.json (channels.openclaw-weixin) — registered channel + accounts..enabled +# /openclaw.json (plugins.load.paths + channels.openclaw-weixin) +# — registered plugin/channel + accounts..enabled # # The third file is the one OpenClaw consults at startup to know the channel # is registered. Without channels.openclaw-weixin.accounts..enabled=true # in openclaw.json, the plugin's auth/accounts.ts considers the account # disabled and the bridge won't start, even if the per-account state files -# above exist. The patch also restores the openclaw-weixin plugin registry -# entry because later OpenClaw config rewrites can drop it while leaving the -# pre-installed extension files in place. +# above exist. The patch also restores the openclaw-weixin plugin registry and +# load path because later OpenClaw config rewrites can drop them while leaving +# the pre-installed extension files in place. # # State dir resolution mirrors the upstream's resolveStateDir(): # $OPENCLAW_STATE_DIR || $CLAWDBOT_STATE_DIR || ~/.openclaw @@ -56,10 +57,6 @@ WECHAT_PLUGIN_ID = "openclaw-weixin" WECHAT_PLUGIN_SPEC = "@tencent-weixin/openclaw-weixin@2.4.2" -WECHAT_PLUGIN_INSTALL = { - "source": "npm", - "spec": WECHAT_PLUGIN_SPEC, -} WECHAT_TOKEN_PLACEHOLDER = "openshell:resolve:env:WECHAT_BOT_TOKEN" @@ -88,6 +85,14 @@ def _state_dir() -> pathlib.Path: return pathlib.Path(raw.strip()).resolve() +def _wechat_plugin_install_path(install_record: object | None = None) -> str: + if isinstance(install_record, dict): + install_path = install_record.get("installPath") + if isinstance(install_path, str) and install_path.strip(): + return install_path.strip() + return str(_state_dir() / "extensions" / WECHAT_PLUGIN_ID) + + def _decode_config() -> dict: raw = os.environ.get("NEMOCLAW_WECHAT_CONFIG_B64", "e30=") or "e30=" try: @@ -157,12 +162,34 @@ def _patch_openclaw_config(account_id: str) -> None: installs = {} plugins["installs"] = installs wechat_install = installs.get(WECHAT_PLUGIN_ID) + if not isinstance(wechat_install, dict): + wechat_install = {} + wechat_install_path = _wechat_plugin_install_path(wechat_install) + if wechat_install.get("source") != "npm": + wechat_install["source"] = "npm" + if not isinstance(wechat_install.get("spec"), str) or not wechat_install["spec"].strip(): + wechat_install["spec"] = WECHAT_PLUGIN_SPEC if ( - not isinstance(wechat_install, dict) - or wechat_install.get("source") != WECHAT_PLUGIN_INSTALL["source"] - or not wechat_install.get("spec") + not isinstance(wechat_install.get("installPath"), str) + or not wechat_install["installPath"].strip() ): - installs[WECHAT_PLUGIN_ID] = dict(WECHAT_PLUGIN_INSTALL) + wechat_install["installPath"] = wechat_install_path + installs[WECHAT_PLUGIN_ID] = wechat_install + + load = plugins.setdefault("load", {}) + if not isinstance(load, dict): + load = {} + plugins["load"] = load + load_paths = load.get("paths") + normalized_paths = ( + [item.strip() for item in load_paths if isinstance(item, str) and item.strip()] + if isinstance(load_paths, list) + else [] + ) + if wechat_install_path not in normalized_paths: + normalized_paths.append(wechat_install_path) + load["paths"] = normalized_paths + entries = plugins.setdefault("entries", {}) if not isinstance(entries, dict): entries = {} diff --git a/test/generate-openclaw-config.test.ts b/test/generate-openclaw-config.test.ts index d14c0b2b58..5ca812a481 100644 --- a/test/generate-openclaw-config.test.ts +++ b/test/generate-openclaw-config.test.ts @@ -61,6 +61,10 @@ function runConfigScript(envOverrides: Record = {}): any { return JSON.parse(fs.readFileSync(configPath, "utf-8")); } +function wechatExtensionPath(stateDir = path.join(tmpDir, ".openclaw")) { + return path.join(fs.realpathSync(stateDir), "extensions", "openclaw-weixin"); +} + function writeRegistryManifest( blueprintDir: string, relativeManifestPath: string, @@ -284,7 +288,11 @@ describe("generate-openclaw-config.py: config generation", () => { NEMOCLAW_WECHAT_CONFIG_B64: wechatConfig, }); - expect(config.plugins?.installs?.["openclaw-weixin"]).toEqual(installEntry); + expect(config.plugins?.installs?.["openclaw-weixin"]).toEqual({ + ...installEntry, + installPath: wechatExtensionPath(), + }); + expect(config.plugins?.load?.paths).toEqual([wechatExtensionPath()]); expect(config.channels?.["openclaw-weixin"]?.accounts?.primary).toEqual({ enabled: true, }); diff --git a/test/seed-wechat-accounts.test.ts b/test/seed-wechat-accounts.test.ts index e093785144..d3c5e52ae4 100644 --- a/test/seed-wechat-accounts.test.ts +++ b/test/seed-wechat-accounts.test.ts @@ -54,6 +54,10 @@ function writeOpenclawConfig(extra: Record = {}) { return cfgPath; } +function wechatExtensionPath(stateDir = path.join(tmpDir, ".openclaw")) { + return path.join(fs.realpathSync(stateDir), "extensions", "openclaw-weixin"); +} + function readJson(p: string): any { return JSON.parse(fs.readFileSync(p, "utf-8")); } @@ -257,12 +261,48 @@ describe("seed-wechat-accounts.py: openclaw.json patching (channels.openclaw-wei expect(cfg.plugins.installs["openclaw-weixin"]).toEqual({ source: "npm", spec: "@tencent-weixin/openclaw-weixin@2.4.2", + installPath: wechatExtensionPath(), }); + expect(cfg.plugins.load.paths).toEqual([wechatExtensionPath()]); expect(cfg.plugins.entries["openclaw-weixin"].enabled).toBe(true); expect(Object.keys(cfg.channels)).toEqual(["telegram", "slack", "openclaw-weixin"]); expect(cfg.channels["openclaw-weixin"].accounts.primary.enabled).toBe(true); }); + it("preserves existing plugin load paths and appends the WeChat extension path", () => { + writeOpenclawConfig({ + plugins: { + load: { paths: ["/opt/custom-openclaw-plugin"] }, + installs: { + "openclaw-weixin": { + source: "npm", + spec: "@tencent-weixin/openclaw-weixin@2.4.2", + installPath: "/already/installed/openclaw-weixin", + pinned: true, + }, + }, + }, + }); + + const result = runSeed({ + NEMOCLAW_WECHAT_CONFIG_B64: configB64({ accountId: "primary" }), + }); + expect(result.status).toBe(0); + + const cfg = readJson(path.join(tmpDir, ".openclaw", "openclaw.json")); + expect(cfg.plugins.installs["openclaw-weixin"]).toEqual({ + source: "npm", + spec: "@tencent-weixin/openclaw-weixin@2.4.2", + installPath: "/already/installed/openclaw-weixin", + pinned: true, + }); + expect(cfg.plugins.load.paths).toEqual([ + "/opt/custom-openclaw-plugin", + "/already/installed/openclaw-weixin", + ]); + expect(cfg.channels["openclaw-weixin"].accounts.primary.enabled).toBe(true); + }); + it("bails (and warns) when openclaw.json is missing — does not invent a config", () => { // generate-openclaw-config.py runs first and is responsible for producing // openclaw.json. If it failed silently, we'd rather print a warning than From 3aa7d86ed92e3b55e617856da17c1d3b326c6696 Mon Sep 17 00:00:00 2001 From: Aaron Erickson Date: Wed, 20 May 2026 13:20:40 -0700 Subject: [PATCH 3/6] test(e2e): use sandbox logs command in scenario helpers --- test/e2e/scenario-framework-tests/e2e-lib-helpers.test.ts | 5 ++++- test/e2e/validation_suites/lib/baseline_onboarding.sh | 4 ++-- test/e2e/validation_suites/lib/sandbox_lifecycle.sh | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/test/e2e/scenario-framework-tests/e2e-lib-helpers.test.ts b/test/e2e/scenario-framework-tests/e2e-lib-helpers.test.ts index 8514fadab5..0d087535db 100644 --- a/test/e2e/scenario-framework-tests/e2e-lib-helpers.test.ts +++ b/test/e2e/scenario-framework-tests/e2e-lib-helpers.test.ts @@ -566,6 +566,7 @@ describe("baseline onboarding validation helper", () => { case "$*" in --help) echo help;; "sb1 status") echo 'status running gateway healthy sandbox running';; + "sb1 logs") echo baseline-log;; *) echo "unexpected nemoclaw args: $*" >&2; exit 64;; esac `, { mode: 0o755 }); @@ -578,12 +579,14 @@ esac baseline_assert_openshell_on_path baseline_assert_nemoclaw_help_exits_zero baseline_assert_sandbox_status_exits_zero + baseline_assert_logs_produce_output `, { E2E_CONTEXT_DIR: ctx, PATH: `${bin}:${process.env.PATH}` }); expect(r.status, r.stderr).toBe(0); expect(r.stdout).toContain("PASS: validation.baseline_onboarding.nemoclaw_on_path"); expect(r.stdout).toContain("PASS: validation.baseline_onboarding.openshell_on_path"); expect(r.stdout).toContain("PASS: validation.baseline_onboarding.nemoclaw_help_exits_zero"); expect(r.stdout).toContain("PASS: validation.baseline_onboarding.sandbox_status"); + expect(r.stdout).toContain("PASS: validation.baseline_onboarding.logs_available"); } finally { fs.rmSync(tmp, { recursive: true, force: true }); } }); }); @@ -625,7 +628,7 @@ describe("sandbox lifecycle validation helper", () => { case "$*" in list) echo sb1;; "sb1 status") echo 'status running gateway healthy sandbox running';; - "logs sb1") echo logline;; + "sb1 logs") echo logline;; *) echo "unexpected nemoclaw args: $*" >&2; exit 64;; esac `, { mode: 0o755 }); diff --git a/test/e2e/validation_suites/lib/baseline_onboarding.sh b/test/e2e/validation_suites/lib/baseline_onboarding.sh index f3661a4b73..7c91750084 100755 --- a/test/e2e/validation_suites/lib/baseline_onboarding.sh +++ b/test/e2e/validation_suites/lib/baseline_onboarding.sh @@ -62,10 +62,10 @@ baseline_assert_sandbox_status_exits_zero() { baseline_assert_logs_produce_output() { local out - if out=$(nemoclaw logs "$E2E_SANDBOX_NAME" 2>&1) && [[ -n "$out" ]]; then + if out=$(nemoclaw "$E2E_SANDBOX_NAME" logs 2>&1) && [[ -n "$out" ]]; then baseline_onboarding_pass validation.baseline_onboarding.logs_available "logs available" else - baseline_onboarding_fail validation.baseline_onboarding.logs_available "logs unavailable" + baseline_onboarding_fail validation.baseline_onboarding.logs_available "logs unavailable: ${out:0:200}" fi } diff --git a/test/e2e/validation_suites/lib/sandbox_lifecycle.sh b/test/e2e/validation_suites/lib/sandbox_lifecycle.sh index 083e797dd4..367397feb9 100755 --- a/test/e2e/validation_suites/lib/sandbox_lifecycle.sh +++ b/test/e2e/validation_suites/lib/sandbox_lifecycle.sh @@ -90,7 +90,7 @@ sandbox_lifecycle_assert_status_fields_present() { sandbox_lifecycle_assert_logs_available() { local id="validation.sandbox_operations.logs_available" - sandbox_lifecycle_run_with_timeout 20 nemoclaw logs "${E2E_SANDBOX_NAME}" >/dev/null || { + sandbox_lifecycle_run_with_timeout 20 nemoclaw "${E2E_SANDBOX_NAME}" logs >/dev/null || { sandbox_lifecycle_fail "${id}" "nemoclaw logs failed" return 1 } From 1787f5470957c2c831e0a3087a2aa7d854a52d00 Mon Sep 17 00:00:00 2001 From: Aaron Erickson Date: Wed, 20 May 2026 13:58:37 -0700 Subject: [PATCH 4/6] fix(docker): make OpenClaw patch layer idempotent --- Dockerfile | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 176bc2b023..1331d0835b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -195,15 +195,18 @@ RUN set -eu; \ # symlinks resolve; the real security gates (realpath + isPathInside \ # containment) remain intact — a symlink escaping the base tree is still caught. \ # Scoped to install-safe-path + install-package-dir only. \ - isp_file="$(grep -RIlE --include='*.js' 'const baseLstat = await fs\.lstat\(baseDir\)' "$OC_DIST/install-safe-path-"*.js)"; \ + isp_file="$(grep -RIlE --include='*.js' 'const baseLstat = await fs\.(lstat|stat)\(baseDir\)' "$OC_DIST/install-safe-path-"*.js || true)"; \ test -n "$isp_file" || { echo "ERROR: install-safe-path baseLstat pattern not found" >&2; exit 1; }; \ sed -i 's/const baseLstat = await fs\.lstat(baseDir)/const baseLstat = await fs.stat(baseDir)/' "$isp_file"; \ if grep -q 'const baseLstat = await fs\.lstat(baseDir)' "$isp_file"; then echo "ERROR: Patch 3a (install-safe-path) left baseLstat lstat call" >&2; exit 1; fi; \ - ipd_file="$(grep -RIlE --include='*.js' 'assertInstallBaseStable' "$OC_DIST/install-package-dir-"*.js)"; \ + if ! grep -q 'const baseLstat = await fs\.stat(baseDir)' "$isp_file"; then echo "ERROR: Patch 3a (install-safe-path) did not find patched baseLstat stat call" >&2; exit 1; fi; \ + ipd_file="$(grep -RIlE --include='*.js' 'assertInstallBaseStable' "$OC_DIST/install-package-dir-"*.js || true)"; \ test -n "$ipd_file" || { echo "ERROR: install-package-dir assertInstallBaseStable not found" >&2; exit 1; }; \ sed -i 's/const baseLstat = await fs\.lstat(params\.installBaseDir)/const baseLstat = await fs.stat(params.installBaseDir)/' "$ipd_file"; \ sed -i 's/baseLstat\.isSymbolicLink()/false \/* nemoclaw: symlink check disabled, realpath guards containment *\//' "$ipd_file"; \ if grep -q 'fs\.lstat(params\.installBaseDir)' "$ipd_file"; then echo "ERROR: Patch 3b (install-package-dir) left lstat in assertInstallBaseStable" >&2; exit 1; fi; \ + if ! grep -q 'const baseLstat = await fs\.stat(params\.installBaseDir)' "$ipd_file"; then echo "ERROR: Patch 3b (install-package-dir) did not find patched baseLstat stat call" >&2; exit 1; fi; \ + if grep -q 'baseLstat\.isSymbolicLink()' "$ipd_file"; then echo "ERROR: Patch 3b (install-package-dir) left baseLstat symlink check" >&2; exit 1; fi; \ # --- Patch 5: bump default WS handshake timeout 10s -> 60s (#2484) --- \ # OpenClaw's WS connect handshake has a hard-coded 10s timeout on both \ # client and server. Server-side connect-handler processing can exceed \ @@ -219,10 +222,11 @@ RUN set -eu; \ # \ # Removal criteria: drop when openclaw fixes the underlying connect \ # latency, or exposes the timeout as an unbounded env override. \ - hto_files="$(grep -RIlE --include='*.js' 'DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = 1e4' "$OC_DIST")"; \ + hto_files="$(grep -RIlE --include='*.js' 'DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = (1e4|6e4)' "$OC_DIST" || true)"; \ test -n "$hto_files" || { echo "ERROR: handshake-timeout constant not found" >&2; exit 1; }; \ printf '%s\n' "$hto_files" | xargs sed -i -E 's|DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = 1e4|DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = 6e4|g'; \ - if grep -REq --include='*.js' 'DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = 1e4' "$OC_DIST"; then echo "ERROR: Patch 5 left a 1e4 constant" >&2; exit 1; fi + if grep -REq --include='*.js' 'DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = 1e4' "$OC_DIST"; then echo "ERROR: Patch 5 left a 1e4 constant" >&2; exit 1; fi; \ + if ! grep -REq --include='*.js' 'DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = 6e4' "$OC_DIST"; then echo "ERROR: Patch 5 did not find patched 6e4 constant" >&2; exit 1; fi # Patch OpenClaw's pinned 2026.4.24 compiled selection runtime to expose a # compact searchable tool catalog to the model while preserving the full From 439c7bc2c224877efcacdf2634c70085df2f56a6 Mon Sep 17 00:00:00 2001 From: Aaron Erickson Date: Wed, 20 May 2026 14:03:53 -0700 Subject: [PATCH 5/6] fix(docker): accept newer OpenClaw patch shapes --- Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1331d0835b..871ac5d22f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -205,7 +205,7 @@ RUN set -eu; \ sed -i 's/const baseLstat = await fs\.lstat(params\.installBaseDir)/const baseLstat = await fs.stat(params.installBaseDir)/' "$ipd_file"; \ sed -i 's/baseLstat\.isSymbolicLink()/false \/* nemoclaw: symlink check disabled, realpath guards containment *\//' "$ipd_file"; \ if grep -q 'fs\.lstat(params\.installBaseDir)' "$ipd_file"; then echo "ERROR: Patch 3b (install-package-dir) left lstat in assertInstallBaseStable" >&2; exit 1; fi; \ - if ! grep -q 'const baseLstat = await fs\.stat(params\.installBaseDir)' "$ipd_file"; then echo "ERROR: Patch 3b (install-package-dir) did not find patched baseLstat stat call" >&2; exit 1; fi; \ + if ! grep -q 'const baseLstat = await fs\.stat(params\.installBaseDir)' "$ipd_file" && ! grep -q 'await fs\.stat(params\.installBaseDir)).isDirectory()' "$ipd_file"; then echo "ERROR: Patch 3b (install-package-dir) did not find patched/safe installBaseDir stat call" >&2; exit 1; fi; \ if grep -q 'baseLstat\.isSymbolicLink()' "$ipd_file"; then echo "ERROR: Patch 3b (install-package-dir) left baseLstat symlink check" >&2; exit 1; fi; \ # --- Patch 5: bump default WS handshake timeout 10s -> 60s (#2484) --- \ # OpenClaw's WS connect handshake has a hard-coded 10s timeout on both \ @@ -222,10 +222,10 @@ RUN set -eu; \ # \ # Removal criteria: drop when openclaw fixes the underlying connect \ # latency, or exposes the timeout as an unbounded env override. \ - hto_files="$(grep -RIlE --include='*.js' 'DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = (1e4|6e4)' "$OC_DIST" || true)"; \ + hto_files="$(grep -RIlE --include='*.js' 'DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = (1e4|15e3|6e4)' "$OC_DIST" || true)"; \ test -n "$hto_files" || { echo "ERROR: handshake-timeout constant not found" >&2; exit 1; }; \ - printf '%s\n' "$hto_files" | xargs sed -i -E 's|DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = 1e4|DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = 6e4|g'; \ - if grep -REq --include='*.js' 'DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = 1e4' "$OC_DIST"; then echo "ERROR: Patch 5 left a 1e4 constant" >&2; exit 1; fi; \ + printf '%s\n' "$hto_files" | xargs sed -i -E 's|DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = (1e4|15e3)|DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = 6e4|g'; \ + if grep -REq --include='*.js' 'DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = (1e4|15e3)' "$OC_DIST"; then echo "ERROR: Patch 5 left a short handshake-timeout constant" >&2; exit 1; fi; \ if ! grep -REq --include='*.js' 'DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = 6e4' "$OC_DIST"; then echo "ERROR: Patch 5 did not find patched 6e4 constant" >&2; exit 1; fi # Patch OpenClaw's pinned 2026.4.24 compiled selection runtime to expose a From 9060ac8a480aca2e269fac20de7b654c4248151b Mon Sep 17 00:00:00 2001 From: Aaron Erickson Date: Wed, 20 May 2026 14:08:45 -0700 Subject: [PATCH 6/6] fix(docker): avoid sed delimiter collision --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 871ac5d22f..b562354701 100644 --- a/Dockerfile +++ b/Dockerfile @@ -224,7 +224,7 @@ RUN set -eu; \ # latency, or exposes the timeout as an unbounded env override. \ hto_files="$(grep -RIlE --include='*.js' 'DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = (1e4|15e3|6e4)' "$OC_DIST" || true)"; \ test -n "$hto_files" || { echo "ERROR: handshake-timeout constant not found" >&2; exit 1; }; \ - printf '%s\n' "$hto_files" | xargs sed -i -E 's|DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = (1e4|15e3)|DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = 6e4|g'; \ + printf '%s\n' "$hto_files" | xargs sed -i -E 's#DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = (1e4|15e3)#DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = 6e4#g'; \ if grep -REq --include='*.js' 'DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = (1e4|15e3)' "$OC_DIST"; then echo "ERROR: Patch 5 left a short handshake-timeout constant" >&2; exit 1; fi; \ if ! grep -REq --include='*.js' 'DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = 6e4' "$OC_DIST"; then echo "ERROR: Patch 5 did not find patched 6e4 constant" >&2; exit 1; fi