fix(harness): any saved plugin root is checked for symlinks before an opt-out deletes through it

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-11-01 06:52:47 +00:00
1 parent 2b0b503c60
commit f276da838f
2 files changed
+9 -1

No files matched your search

+2 -1
View File
@@ -540,7 +540,8 @@ function hookPlan(opts: RegisterOptions, dest: string, prev: Ledger | null): Hoo
const profile = prevOn ? prev!.meta.hook_profile : hookProfile(t.name, opts.env, opts.home);
const root = hookRoot(t.name, profile);
const plan = { on: choice.on, prevOn, explicit: choice.source === 'flag', retry: prevOn && prev!.meta.hook_active === 'no', profile, root, files: new Map<string, Buffer>() };
if (prevOn && !choice.on) assertHookRoot(t.name, profile, root, root, (code, msg) => { throw new HarnessError(code, msg); });
const saved = prev?.meta.hook_root && prev.meta.hook_profile ? { profile: prev.meta.hook_profile, root: prev.meta.hook_root } : null;
if (saved && !choice.on) assertHookRoot(t.name, saved.profile, saved.root, saved.root, (code, msg) => { throw new HarnessError(code, msg); });
if (!choice.on) return plan;
const why = dest !== profileSkills(t.name, profile) ? 'this registration uses another skills folder (' + dest + ')'
: t.name === 'openclaw' && opts.env.OPENCLAW_CONFIG_PATH ? 'OPENCLAW_CONFIG_PATH points OpenClaw at a config outside that profile' : '';
+7
View File
@@ -420,6 +420,13 @@ describe('start hook: teardown keeps what gstack did not write (fake CLI)', () =
expect(() => register(r.o({ startHook: false }))).toThrow(/EACCES/);
expect(readLedger(on.ledger)!.meta.hook_root).toBe(r.root);
} finally { fs.chmodSync(r.root, 0o755); }
const ext = path.join(r.profile, 'extensions'), moved = path.join(r.d, 'moved');
fs.renameSync(ext, moved);
fs.symlinkSync(moved, ext);
expect(() => register(r.o({ startHook: false }))).toThrow(expect.objectContaining({ code: 'HARNESS_UNSAFE_DESTINATION' }));
expect(fs.existsSync(path.join(moved, HOOK_ID, 'index.js'))).toBe(true);
fs.unlinkSync(ext);
fs.renameSync(moved, ext);
const off = register(r.o({ startHook: false }));
expect(off.hook?.state).toBe('off');
expect(fs.existsSync(path.join(r.profile, 'extensions'))).toBe(false);