Skip to content

Commit fb5ae96

Browse files
authored
fix(hooks): openclaw --with-hooks was a silent no-op
install-hooks.sh's openclaw branch only echoed manual copy instructions and never touched disk, while every other platform (claude/codex/gemini/pi/hermes) actually performs the install. bin/install.js's maybeSpawnInstallHooks() only checks the script's exit code, so this printed "hook 已注入 openclaw" even though nothing was installed — and per its own comment, code-abyss is the *only* entry point for openclaw hook injection (abyss CLI deliberately doesn't handle it), so this meant openclaw users following --with-hooks got nothing, silently. The printed manual instruction was also itself broken: `cp .../plugin.js ~/.openclaw/plugins/abyss-hooks.js` flattens the file, but plugin.js resolves its hook scripts via path.join(__dirname, '..', 'common') — it needs an `openclaw/` dir with a sibling `common/`, not a loose file. Fixed by mirroring hermes's own pattern (an actual `cp` into a self-contained plugin directory) while preserving that directory relationship, so the copied plugin.js's relative lookup still resolves. Added test/install-hooks-sh.test.js — this script had zero test coverage before, which is how a no-op-reported-as-success bug went unnoticed. Co-authored-by: telagod <telagod@users.noreply.github.com>
1 parent 28c0259 commit fb5ae96

2 files changed

Lines changed: 63 additions & 3 deletions

File tree

skills/indexing-code/hooks/common/install-hooks.sh

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,17 @@ YAML
138138
;;
139139

140140
openclaw)
141-
echo "OpenClaw: copy the plugin file to your OpenClaw plugins directory:"
142-
echo " cp ${HOOK_ROOT}/openclaw/plugin.js ~/.openclaw/plugins/abyss-hooks.js"
143-
echo " Then register in your plugin config."
141+
# plugin.js resolves its hook scripts via path.join(__dirname, '..', 'common') —
142+
# it needs an `openclaw/` dir with a sibling `common/` wherever it lands, so a
143+
# flat single-file copy (the old behavior here — and the old printed instruction,
144+
# which told users to do the same broken copy manually) silently breaks that
145+
# lookup. Mirror the source hooks/ layout instead.
146+
PLUGIN_DIR="${HOME}/.openclaw/plugins/abyss-hooks"
147+
mkdir -p "$PLUGIN_DIR/openclaw"
148+
cp -r "${HOOK_ROOT}/common" "$PLUGIN_DIR/common"
149+
cp "${HOOK_ROOT}/openclaw/plugin.js" "$PLUGIN_DIR/openclaw/plugin.js"
150+
echo "✓ OpenClaw plugin installed at $PLUGIN_DIR/openclaw/plugin.js"
151+
echo " Register it in your OpenClaw plugin config to enable it."
144152
;;
145153

146154
*)

test/install-hooks-sh.test.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
'use strict';
2+
3+
const path = require('path');
4+
const fs = require('fs');
5+
const os = require('os');
6+
const { spawnSync } = require('child_process');
7+
8+
const SCRIPT = path.join(__dirname, '..', 'skills', 'indexing-code', 'hooks', 'common', 'install-hooks.sh');
9+
10+
describe('install-hooks.sh openclaw branch', () => {
11+
let tmpHome;
12+
13+
beforeEach(() => {
14+
tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'code-abyss-hooks-test-'));
15+
});
16+
17+
afterEach(() => {
18+
fs.rmSync(tmpHome, { recursive: true, force: true });
19+
});
20+
21+
test('actually installs the plugin (not just prints instructions)', () => {
22+
const r = spawnSync('bash', [SCRIPT, 'openclaw'], {
23+
env: { ...process.env, HOME: tmpHome },
24+
encoding: 'utf8',
25+
});
26+
expect(r.status).toBe(0);
27+
28+
const pluginDir = path.join(tmpHome, '.openclaw', 'plugins', 'abyss-hooks');
29+
expect(fs.existsSync(path.join(pluginDir, 'openclaw', 'plugin.js'))).toBe(true);
30+
expect(fs.existsSync(path.join(pluginDir, 'common', 'session-init.sh'))).toBe(true);
31+
expect(fs.existsSync(path.join(pluginDir, 'common', 'pre-edit-check.sh'))).toBe(true);
32+
});
33+
34+
test('installed plugin.js resolves its hook scripts via the correct relative path (does not silently break on copy)', () => {
35+
spawnSync('bash', [SCRIPT, 'openclaw'], { env: { ...process.env, HOME: tmpHome } });
36+
37+
const pluginPath = path.join(tmpHome, '.openclaw', 'plugins', 'abyss-hooks', 'openclaw', 'plugin.js');
38+
const hookDir = path.join(path.dirname(pluginPath), '..', 'common');
39+
expect(fs.existsSync(path.join(hookDir, 'session-init.sh'))).toBe(true);
40+
expect(fs.existsSync(path.join(hookDir, 'pre-edit-check.sh'))).toBe(true);
41+
42+
const register = require(pluginPath);
43+
expect(typeof register).toBe('function');
44+
});
45+
46+
test('idempotent: running twice does not fail', () => {
47+
const first = spawnSync('bash', [SCRIPT, 'openclaw'], { env: { ...process.env, HOME: tmpHome } });
48+
const second = spawnSync('bash', [SCRIPT, 'openclaw'], { env: { ...process.env, HOME: tmpHome } });
49+
expect(first.status).toBe(0);
50+
expect(second.status).toBe(0);
51+
});
52+
});

0 commit comments

Comments
 (0)