From 4095d3784dc0580f70804c5d0783a784ec114a73 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Mon, 2 Nov 2026 03:50:36 +0100 Subject: [PATCH] fix(plugins): omitted metadata in a workspace.create.before result means unchanged Same handoff as agent.create.before: a legacy hook that returned only { source, ... } dropped the caller's metadata and anything an earlier hook added, and workspace provisioning then defaulted it to {}. The omitted field is carried forward at each handoff; an explicit {} still replaces. --- .../server/plugins/lifecycle/handlers.test.ts | 35 +++++++++++++++++++ .../src/server/plugins/lifecycle/index.ts | 6 ++++ 2 files changed, 41 insertions(+) diff --git a/packages/server/src/server/plugins/lifecycle/handlers.test.ts b/packages/server/src/server/plugins/lifecycle/handlers.test.ts index e9b3ab4d74..49cf75fceb 100644 --- a/packages/server/src/server/plugins/lifecycle/handlers.test.ts +++ b/packages/server/src/server/plugins/lifecycle/handlers.test.ts @@ -125,6 +125,41 @@ test("a create hook that omits metadata passes on what an earlier hook added", a expect(seen).toEqual([{ original: true, "new.plugin": { ticket: 42 } }]); }); +test("a workspace create hook that omits metadata passes on what an earlier hook added", async () => { + const hooks = new PluginHookHandlers(() => {}); + hooks.addHook("workspace.create.before", async ({ request }) => ({ + ...request, + metadata: { ...request.metadata, "new.plugin": { ticket: 42 } }, + })); + hooks.addHook("workspace.create.before", async ({ request }) => ({ source: request.source })); + const seen: unknown[] = []; + hooks.addHook("workspace.create.before", async ({ request }) => { + seen.push(request.metadata); + }); + const result = await hooks.invoke( + "operation", + "workspace.create.before", + { source: { kind: "directory", path: "/project" }, metadata: { original: true } }, + paseo, + ); + expect(result).toMatchObject({ metadata: { original: true, "new.plugin": { ticket: 42 } } }); + expect(seen).toEqual([{ original: true, "new.plugin": { ticket: 42 } }]); + + const replacing = new PluginHookHandlers(() => {}); + replacing.addHook("workspace.create.before", async ({ request }) => ({ + ...request, + metadata: {}, + })); + await expect( + replacing.invoke( + "operation", + "workspace.create.before", + { source: { kind: "directory", path: "/project" }, metadata: { original: true } }, + paseo, + ), + ).resolves.toMatchObject({ metadata: {} }); +}); + test("a session-open hook that returns its own object without metadata leaves metadata unchanged", async () => { const hooks = new PluginHookHandlers(() => {}); hooks.addHook("agent.open.before", async ({ request }) => ({ diff --git a/packages/server/src/server/plugins/lifecycle/index.ts b/packages/server/src/server/plugins/lifecycle/index.ts index eb6684a2fa..f7ddd84b74 100644 --- a/packages/server/src/server/plugins/lifecycle/index.ts +++ b/packages/server/src/server/plugins/lifecycle/index.ts @@ -252,6 +252,12 @@ export async function validateBeforeResult