fix(openclaw-patch): clean no-op backups, skip 5.22 inline-spread, harden filters#131
Conversation
…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>
macOS support — detailsCalling this out separately because it was the original motivation for commit Why the original script didn't run on macOSThe pre-
What
|
| # | 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 expansionNo 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.
|
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! |
|
Cross-linking #118 as related context. Same symptom ( A real fix for #118 needs an OpenClaw upstream change. The inline-spread call site in The main runtime path on |
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.bakfiles remained next to the livedist/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
6067d6dwas made specifically to make the script run on the default macOS shell + utility set:bash 3.2.57(1)-release(arm64-apple-darwin25),/bin/bashmapfile -t(bash 4+) — replaced with a portablewhile IFS= read -rloopgrep 2.6.0-FreeBSDgrep -oP(GNU PCRE) for version parse — replaced with anode -eJSON readgrepgrep -qP(PCRE) inside strategies — replaced withgrep -Eq(POSIX ERE) andperlsed -ised -i -Einline edit (BSDsed -irequires a backup-suffix arg) — replaced withperl -i -peMy new changes (commit
547874f) intentionally use only macOS-portable primitives:find ... -name,cmp -s,grep -qE,grep -q, andperl -0777 -ne— all available on a vanilla macOS install with the system shell.Test environment for this PR:
Fixed points, one by one
1. Tighten candidate filter (commit 547874f)
Require
runAfterToolCall(the actual hook call site) in addition to the literal stringafter_tool_call. Excludes schema/test-contract files such asplugin-sdk/agent-runtime-test-contracts.jsandcommand-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.jsin 5.22 was refactored to pass the hook event as an inline object torunAfterToolCall(...), withdurationMswrapped in a conditional spread:Two structural reasons this site cannot be patched by the current script:
hookEventliteral identifierctxin function scope (onlyparams)ctx.params.session?.messageswouldReferenceErrorat runtimeparamshas nosessionfield eitherparams.session?.messagesrewrite would always beundefinedPatching this site needs an upstream OpenClaw change to thread
session.messagesinto the function signature. Until then, the new detection emits an explicit INFO line: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:2490still hasctxin scope and still gets patched successfully — context-offload's L1/L1.5 pipeline keeps receivingmessagesfrom that site.3. Global no-op backup cleanup pass at end of run (commit 547874f)
Walk
$DIST_DIRand drop any.pre-offload-patch.bakbyte-identical to its source. Catches:The script's effect on disk now matches its summary:
.bak exists⇔script actually modified that file. The summary line surfaces a清理空备份counter so the cleanup is visible withoutDEBUG=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_fileinside each strategy so backups are only created when a strategy actually attempts an edit, and addsedit_attemptedtracking so "no strategy matched" becomesSKIPPED++instead ofFAILED++.5. Empty candidate list guard (commit de98071, pre-existing on fork)
Guards the per-file loop with
if [[ ${#CANDIDATE_FILES[@]} -gt 0 ]]so an emptydist/produces a clean summary instead of iterating empty.Test plan
bash -n scripts/openclaw-after-tool-call-messages.patch.sh— syntax OK on macOSbash 3.2.57.DEBUG=1:selection-hR-AeOeU.js→ already-patched, skipnative-hook-relay-CKQJJkrR.js→ 5.22 inline-spread skip with explicit INFOhook-runner-global-BkXXy1ub.js→ nodurationMs, skip成功: 0 跳过: 3 失败: 0 清理空备份: 2清理空备份: 0, no.pre-offload-patch.bakleft indist/.selection-hR-AeOeU.js:2490confirmed to still carrymessages: ctx.params.session?.messagesafter the cleanup pass.[context-offload] [local-llm] L1 <<< 4506ms, output=317 charsand[context-offload] [local-llm] L1.5 <<< 2765ms, output=132 chars— context-offload still fully operational.🤖 Generated with Claude Code