fix(hooks): pass workspaceDir in gateway session reset internal hook context (#64735)

* fix(hooks): pass workspaceDir in gateway session reset internal hook context

The gateway path (performGatewaySessionReset) omitted workspaceDir when
creating the internal hook event, while the plugin hook path
(emitGatewayBeforeResetPluginHook) in the same file correctly resolved and
passed it.  This caused the session-memory handler to fall back to
resolveAgentWorkspaceDir from the session key, which for default-agent
keys resolves to the shared default workspace instead of the per-agent
workspace.  Daily notes and memory files were written to the wrong
workspace in multi-agent setups.

Closes #64528

* docs(changelog): add session-memory workspace reset note

* fix(changelog): remove conflict markers

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
Subash Natarajan
2026-04-14 06:49:07 +05:30
committed by GitHub
parent b5fa2ed5cb
commit 575202b06e
3 changed files with 47 additions and 0 deletions

View File

@@ -514,6 +514,8 @@ export async function performGatewaySessionReset(params: {
})();
const { entry, legacyKey, canonicalKey } = loadSessionEntry(params.key);
const hadExistingEntry = Boolean(entry);
const agentId = normalizeAgentId(target.agentId ?? resolveDefaultAgentId(cfg));
const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId);
const hookEvent = createInternalHookEvent(
"command",
params.reason,
@@ -523,6 +525,7 @@ export async function performGatewaySessionReset(params: {
previousSessionEntry: entry,
commandSource: params.commandSource,
cfg,
workspaceDir,
},
);
await triggerInternalHook(hookEvent);

View File

@@ -533,6 +533,49 @@ describe("session-memory hook", () => {
expect(files.length).toBe(1);
});
it("uses agent-specific workspace when workspaceDir is provided for non-default agent (gateway path regression)", async () => {
const defaultWorkspace = await createCaseWorkspace("workspace-default");
const customAgentWorkspace = await createCaseWorkspace("workspace-custom-agent");
const sessionsDir = path.join(customAgentWorkspace, "sessions");
await fs.mkdir(sessionsDir, { recursive: true });
const sessionFile = await writeWorkspaceFile({
dir: sessionsDir,
name: "custom-agent-session.jsonl",
content: createMockSessionContent([
{ role: "user", content: "Custom agent conversation" },
{ role: "assistant", content: "Stored in agent workspace" },
]),
});
// Simulate the gateway internal hook path: workspaceDir is resolved and
// passed explicitly in context (fix for #64528). Without the fix, the
// gateway path omitted workspaceDir, causing the handler to fall back to
// the default workspace via resolveAgentWorkspaceDir — which for a
// default-agent sessionKey would resolve to the shared default workspace.
const { files, memoryContent } = await runNewWithPreviousSessionEntry({
tempDir: customAgentWorkspace,
cfg: {
agents: {
defaults: { workspace: defaultWorkspace },
list: [{ id: "custom-agent", workspace: customAgentWorkspace }],
},
} satisfies OpenClawConfig,
sessionKey: "agent:main:main",
workspaceDirOverride: customAgentWorkspace,
previousSessionEntry: {
sessionId: "custom-agent-session",
sessionFile,
},
});
expect(files.length).toBe(1);
expect(memoryContent).toContain("user: Custom agent conversation");
expect(memoryContent).toContain("assistant: Stored in agent workspace");
// Verify memory did NOT leak to the default workspace
await expect(fs.access(path.join(defaultWorkspace, "memory"))).rejects.toThrow();
});
it("handles session files with fewer messages than requested", async () => {
const sessionContent = createMockSessionContent([
{ role: "user", content: "Only message 1" },