fix(server): reopening a closed agent keeps metadata saved just before

The loader reads the agent record, then queues a resume. A setMetadata
queued ahead of that resume saves a newer record, and the open hooks saw
it, but registration took metadata from the loader's older read and wrote
it back over the saved patch. Registration now uses the metadata read
inside the lifecycle lane.
This commit is contained in:
Mohamed Boudra
2026-11-04 11:30:32 +01:00
parent f3d223ad02
commit b97a2481a0
2 changed files with 34 additions and 0 deletions
@@ -5597,6 +5597,37 @@ test("setLabels merges and persists labels", async () => {
});
});
test("reopening a closed agent keeps metadata saved while the loader was reading its record", async () => {
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-metadata-reopen-"));
const storage = new AgentStorage(join(workdir, "agents"), logger);
const manager = new AgentManager({
clients: { codex: new TestAgentClient() },
registry: storage,
logger,
});
const created = await manager.createAgent(
{ provider: "codex", cwd: workdir, title: "Metadata reopen" },
undefined,
{ workspaceId: undefined, metadata: { first: 1 } },
);
const agentId = created.id;
await manager.closeAgent(agentId);
// The loader reads the record, then a setMetadata is queued before its resume runs.
const readByLoader = await storage.get(agentId);
const saved = manager.setMetadata(agentId, { second: 2 });
const reopened = manager.resumeAgentFromPersistence(
{ provider: "codex", sessionId: "session-metadata", metadata: { cwd: workdir } },
{ cwd: workdir },
agentId,
{ metadata: readByLoader?.metadata },
);
expect(await saved).toEqual({ first: 1, second: 2 });
expect((await reopened).metadata).toEqual({ first: 1, second: 2 });
expect((await storage.get(agentId))?.metadata).toEqual({ first: 1, second: 2 });
});
test("detachAgent removes relationship lifecycle labels from a live agent and emits state", async () => {
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-detach-live-"));
const storagePath = join(workdir, "agents");
@@ -1597,6 +1597,9 @@ export class AgentManager {
return this.registerSession(session, storedConfig, resolvedAgentId, {
launchedOptions,
...options,
// The caller read its metadata before this turn in the lane; a setMetadata queued ahead of
// it has saved a newer record, the one the open hooks saw.
...(record ? { metadata: record.metadata } : {}),
launchEnv,
persistence: handle,
restoring: true,