Skip to content

fix(openclaw-patch): clean no-op backups, skip 5.22 inline-spread, harden filters#131

Closed
rocke2020 wants to merge 3 commits into
TencentCloud:mainfrom
rocke2020:fix/openclaw-patch-noop-backups-and-5.22-detect
Closed

fix(openclaw-patch): clean no-op backups, skip 5.22 inline-spread, harden filters#131
rocke2020 wants to merge 3 commits into
TencentCloud:mainfrom
rocke2020:fix/openclaw-patch-noop-backups-and-5.22-detect

Conversation

@rocke2020

@rocke2020 rocke2020 commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Three improvements to scripts/openclaw-after-tool-call-messages.patch.sh, plus two prior local-only fixes that ride along (all three commits touch only that one file).

The motivating incident: after running the patch script against OpenClaw 2026.5.22, two .pre-offload-patch.bak files remained next to the live dist/ files, byte-identical to their sources. This made it look like the script had modified those files and broken the gateway. Investigation showed the gateway breakage was unrelated (a wrapper-plist + secret-broker config issue), but the misleading backups were a real UX bug worth fixing.

Platform support

The script is verified on macOS (and remains compatible with Linux). The portability commit 6067d6d was made specifically to make the script run on the default macOS shell + utility set:

macOS default Behavior the script no longer relies on
bash 3.2.57(1)-release (arm64-apple-darwin25), /bin/bash mapfile -t (bash 4+) — replaced with a portable while IFS= read -r loop
BSD grep 2.6.0-FreeBSD grep -oP (GNU PCRE) for version parse — replaced with a node -e JSON read
BSD grep grep -qP (PCRE) inside strategies — replaced with grep -Eq (POSIX ERE) and perl
BSD sed -i sed -i -E inline edit (BSD sed -i requires a backup-suffix arg) — replaced with perl -i -pe

My new changes (commit 547874f) intentionally use only macOS-portable primitives: find ... -name, cmp -s, grep -qE, grep -q, and perl -0777 -ne — all available on a vanilla macOS install with the system shell.

Test environment for this PR:

ProductName:    macOS
ProductVersion: 26.5
BuildVersion:   25F71
GNU bash, version 3.2.57(1)-release (arm64-apple-darwin25)
grep (BSD grep, GNU compatible) 2.6.0-FreeBSD
OpenClaw 2026.5.22

Fixed points, one by one

1. Tighten candidate filter (commit 547874f)

Require runAfterToolCall (the actual hook call site) in addition to the literal string after_tool_call. Excludes schema/test-contract files such as plugin-sdk/agent-runtime-test-contracts.js and command-registration-*.js, which only mention "after_tool_call" as event-name data.

Before: 5 candidates → 3 with backups created speculatively.
After: 3 candidates → no speculative backups on non-runtime files.

2. Detect OpenClaw 5.22+ inline-spread shape and skip cleanly (commit 547874f)

native-hook-relay-CKQJJkrR.js in 5.22 was refactored to pass the hook event as an inline object to runAfterToolCall(...), with durationMs wrapped in a conditional spread:

await hookRunner.runAfterToolCall({
    toolName: params.toolName,
    ...
    ...params.startedAt != null ? { durationMs: Date.now() - params.startedAt } : {}
}, { /* second arg */ });

Two structural reasons this site cannot be patched by the current script:

Structural issue Consequence
No hookEvent literal identifier Strategies 1, 3, 4 cannot anchor
No ctx in function scope (only params) The injection text ctx.params.session?.messages would ReferenceError at runtime
params has no session field either A params.session?.messages rewrite would always be undefined

Patching this site needs an upstream OpenClaw change to thread session.messages into the function signature. Until then, the new detection emits an explicit INFO line:

[INFO] native-hook-relay-CKQJJkrR.js — OpenClaw 5.22+ 内联展开形态(调用点无 ctx 作用域),跳过;该位置需 OpenClaw 上游修改才能注入 messages

This replaces the silent debug-level skip + leftover backup, so future readers see why and what's needed.

The main runtime path selection-hR-AeOeU.js:2490 still has ctx in scope and still gets patched successfully — context-offload's L1/L1.5 pipeline keeps receiving messages from that site.

3. Global no-op backup cleanup pass at end of run (commit 547874f)

Walk $DIST_DIR and drop any .pre-offload-patch.bak byte-identical to its source. Catches:

  • regex matched nothing → file unchanged but backup left behind
  • strategy 4 restored from backup → backup now identical to file
  • legacy backups from prior runs that the new candidate filter no longer considers

The script's effect on disk now matches its summary: .bak existsscript actually modified that file. The summary line surfaces a 清理空备份 counter so the cleanup is visible without DEBUG=1.

4. macOS / portability fixes (commit 6067d6d, pre-existing on fork)

Detailed in the Platform support table above. In short: removes every GNU-only / bash-4+ dependency the script used to have, so it runs on a vanilla macOS install. Also moves backup_file inside each strategy so backups are only created when a strategy actually attempts an edit, and adds edit_attempted tracking so "no strategy matched" becomes SKIPPED++ instead of FAILED++.

5. Empty candidate list guard (commit de98071, pre-existing on fork)

Guards the per-file loop with if [[ ${#CANDIDATE_FILES[@]} -gt 0 ]] so an empty dist/ produces a clean summary instead of iterating empty.

Test plan

  • bash -n scripts/openclaw-after-tool-call-messages.patch.sh — syntax OK on macOS bash 3.2.57.
  • First run against live OpenClaw 2026.5.22 install with DEBUG=1:
    • 3 candidates (down from 5; test-contract + command-registration excluded by filter)
    • selection-hR-AeOeU.js → already-patched, skip
    • native-hook-relay-CKQJJkrR.js → 5.22 inline-spread skip with explicit INFO
    • hook-runner-global-BkXXy1ub.js → no durationMs, skip
    • 2 no-op backups cleaned up
    • Final summary: 成功: 0 跳过: 3 失败: 0 清理空备份: 2
  • Second run for idempotence: same skip counts, 清理空备份: 0, no .pre-offload-patch.bak left in dist/.
  • selection-hR-AeOeU.js:2490 confirmed to still carry messages: ctx.params.session?.messages after the cleanup pass.
  • Live end-to-end with a gateway tool call: [context-offload] [local-llm] L1 <<< 4506ms, output=317 chars and [context-offload] [local-llm] L1.5 <<< 2765ms, output=132 chars — context-offload still fully operational.

🤖 Generated with Claude Code

rocke2020 and others added 3 commits June 3, 2026 07:33
…shape

Three improvements to openclaw-after-tool-call-messages.patch.sh, prompted
by a debugging session where leftover *.pre-offload-patch.bak files were
mistaken for the script having broken the local OpenClaw gateway.

1. Tighten candidate filter: require runAfterToolCall (the actual hook
   call site), not just the literal string "after_tool_call". Excludes
   schema/test-contract files such as
   plugin-sdk/agent-runtime-test-contracts.js and
   command-registration-*.js, which only mention the event name as data.

2. Detect OpenClaw 5.22+ inline-spread shape and skip cleanly with an
   explicit reason. In 5.22, native-hook-relay-CKQJJkrR.js passes the hook
   event as an inline object to runAfterToolCall(...), with durationMs
   wrapped in a conditional spread:
       ...params.startedAt != null ? { durationMs: ... } : {}
   The call site has no hookEvent literal and no ctx in scope (only
   params). The current injection text `ctx.params.session?.messages`
   would ReferenceError here, and params has no session field either.
   Patching this site needs an upstream change to thread session.messages
   into the function signature. Skip with an INFO line that documents
   this so future readers do not have to re-derive it.

3. Global no-op backup cleanup pass at end of run. Walk DIST_DIR and drop
   any .pre-offload-patch.bak byte-identical to its source. Catches:
     - regex matched nothing -> file unchanged but backup left behind
     - strategy 4 restored from backup -> backup now identical to file
     - legacy backups from prior runs that the new candidate filter no
       longer considers
   The script's effect on disk now matches its summary: ".bak exists"
   <=> "script actually modified that file". Summary line surfaces a
   "清理空备份" counter so the cleanup is visible without DEBUG=1.

Verified on OpenClaw 2026.5.22:
  Run 1: 3 candidates, 0 patched, 3 skipped (1 already-patched +
         1 5.22-inline-spread + 1 no-durationMs), 0 failed, 2 cleaned
  Run 2 (idempotence): same skip count, 0 cleaned
  selection-hR-AeOeU.js:2490 still carries
    `messages: ctx.params.session?.messages`
  Live context-offload L1 + L1.5 still fire end-to-end against the
  patched main path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rocke2020

Copy link
Copy Markdown
Contributor Author

macOS support — details

Calling this out separately because it was the original motivation for commit 6067d6d and applies to anyone running OpenClaw on a Mac with the system shell. Tested on macOS 26.5 (25F71) arm64 with /bin/bash (3.2.57(1)-release) and BSD grep (2.6.0-FreeBSD).

Why the original script didn't run on macOS

The pre-6067d6d script used four idioms that fail on a vanilla macOS install:

# Idiom in original script Why it fails on macOS default Concrete failure
1 mapfile -t CANDIDATE_FILES < <(...) mapfile is bash 4+; macOS ships /bin/bash 3.2.57 mapfile: command not found → empty CANDIDATE_FILES, script reports "0 candidates"
2 VERSION=$(grep -oP '"version"\s*:\s*"\K[^"]+' package.json) BSD grep has no -P (PCRE), and no \K look-behind reset grep: invalid option -- PVERSION=unknown
3 grep -qP '^\s+durationMs\s*$' (strategy 2 pre-check) Same BSD grep no-PCRE issue Strategy 2 never fires on dispatch-*.js
4 sed -i -E 's/.../.../' file (strategy 2 edit) BSD sed -i requires an explicit empty backup-suffix arg (sed -i ''); GNU accepts bare -i sed: 1: "...": invalid command code → script aborts mid-strategy

What 6067d6d swapped in (still in this PR)

# Replaced with Why portable
1 while IFS= read -r line; do arr+=("$line"); done < <(...) POSIX-shell idiom, works on bash 3.2+
2 `node -e "const fs=require('fs'); const pkg=JSON.parse(fs.readFileSync(process.argv[1], 'utf8')); console.log(pkg.version
3 grep -Eq '^[[:space:]]+durationMs[[:space:]]*$' POSIX ERE + POSIX character classes ([[:space:]] works on both BSD and GNU)
4 perl -i -pe 's/.../.../' file Perl ships with macOS (/usr/bin/perl); -i works the same on BSD and GNU

What my new commit 547874f adds (also macOS-safe)

Only these primitives are introduced, all available on a vanilla macOS install:

grep -q 'runAfterToolCall' "$f"         # POSIX, BSD-safe
grep -qE '(^|\W)(hookEvent|hook_event)(\W|$)' "$f"   # POSIX ERE
perl -0777 -ne '...'                    # /usr/bin/perl, BSD-safe
find "$DIST_DIR" -name '*.pre-offload-patch.bak'   # POSIX find
cmp -s "$src" "$bak"                    # POSIX
${bak%.pre-offload-patch.bak}           # POSIX parameter expansion

No mapfile, no grep -P, no sed -i without backup-suffix, no readarray, no GNU-only flags. The script as a whole now runs end-to-end on macOS bash 3.2.57 without any Homebrew packages.

Verified runtime evidence on macOS 26.5

$ bash --version | head -1
GNU bash, version 3.2.57(1)-release (arm64-apple-darwin25)

$ grep --version | head -1
grep (BSD grep, GNU compatible) 2.6.0-FreeBSD

$ bash scripts/openclaw-after-tool-call-messages.patch.sh
[INFO]  OpenClaw 目录: /opt/homebrew/lib/node_modules/openclaw
[INFO]  检测到 OpenClaw 版本: 2026.5.22
[INFO]  找到 3 个候选文件
[INFO]  native-hook-relay-CKQJJkrR.js — OpenClaw 5.22+ 内联展开形态(调用点无 ctx 作用域),跳过;该位置需 OpenClaw 上游修改才能注入 messages
[WARN]  selection-hR-AeOeU.js — 已经 patch 过,跳过

═══════════════════════════════════════════════════════
  Patch 完成  (OpenClaw 2026.5.22)
═══════════════════════════════════════════════════════

  成功: 0  跳过: 3  失败: 0  清理空备份: 2

No command not found, no invalid option, no invalid command code. macOS users get the same behavior as Linux users.

@Maxwell-Code07

Copy link
Copy Markdown
Collaborator

Thanks for the contribution! We've received your PR improving the OpenClaw patch script and will review it internally. We'll get back to you once we have results. Appreciate the detailed troubleshooting!

@rocke2020

Copy link
Copy Markdown
Contributor Author

Cross-linking #118 as related context.

Same symptom (event.messages undefined in after_tool_call) but on OpenClaw 5.27, which is a stricter superset of the 5.22+ inline-spread shape this PR detects. To be clear, this PR does not close #118 — the new skip just makes the failure mode honest (explicit INFO + no misleading .pre-offload-patch.bak) instead of silently dropping messages and leaving a backup that looks like the script broke something.

A real fix for #118 needs an OpenClaw upstream change. The inline-spread call site in native-hook-relay-*.js has no ctx in scope and params has no session field, so no in-place regex injection from this script can work there — runAfterToolCall would have to take session.messages in its signature.

The main runtime path on selection-hR-AeOeU.js:2490 still has ctx and gets patched fine, which is why context-offload L1/L1.5 keep firing end-to-end in my run.

@rocke2020 rocke2020 closed this Jun 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants