From 680a41f43c592bd2ff8cad60bc867e5c718c78d6 Mon Sep 17 00:00:00 2001 From: Hypatia May Date: Tue, 30 Jun 2026 13:14:06 +0800 Subject: [PATCH] fix: stabilize session fork duplicate-id errors --- packages/session-fork/session-fork/README.md | 2 +- packages/session-fork/session-fork/src/index.ts | 2 +- .../session-fork/tests/session-fork.spec.ts | 10 ++++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/session-fork/session-fork/README.md b/packages/session-fork/session-fork/README.md index 0f8e4f237c..f473f8c272 100644 --- a/packages/session-fork/session-fork/README.md +++ b/packages/session-fork/session-fork/README.md @@ -8,7 +8,7 @@ Session fork service (`ctx.sessionFork`) for creating seeded child sessions from | Method | Purpose | |---|---| -| `snapshot(source)` | Resolve a live `Session | SessionId`, reject non-boundary logs, and return a deep-cloned seed plus `parentSession` / `seedLength` metadata. | +| `snapshot(source)` | Resolve a live `Session \| SessionId`, reject non-boundary logs, and return a deep-cloned seed plus `parentSession` / `seedLength` metadata. | | `fork({ source, sessionId? })` | Create a live child session from `snapshot(source)`, using the caller-supplied child id or the session store's generated id. | ## Boundary Rule diff --git a/packages/session-fork/session-fork/src/index.ts b/packages/session-fork/session-fork/src/index.ts index b7781601ea..2606fb959c 100644 --- a/packages/session-fork/session-fork/src/index.ts +++ b/packages/session-fork/session-fork/src/index.ts @@ -95,10 +95,10 @@ export class SessionForkService extends Service { * `ctx.agents.create` instead. */ fork(options: ForkSessionOptions): Session { - const snapshot = this.snapshot(options.source) if (options.sessionId !== undefined && this.ctx.sessions.get(options.sessionId) !== undefined) { throw new SessionForkError(`session "${options.sessionId}" already exists`, 'SESSION_ALREADY_EXISTS') } + const snapshot = this.snapshot(options.source) return this.ctx.sessions.create(options.sessionId, { seed: snapshot.seed, meta: snapshot.meta, diff --git a/packages/session-fork/session-fork/tests/session-fork.spec.ts b/packages/session-fork/session-fork/tests/session-fork.spec.ts index 44dcd3c962..b39dc0c44b 100644 --- a/packages/session-fork/session-fork/tests/session-fork.spec.ts +++ b/packages/session-fork/session-fork/tests/session-fork.spec.ts @@ -203,6 +203,16 @@ describe('SessionForkService', () => { .toThrow(new SessionForkError('session "child" already exists', 'SESSION_ALREADY_EXISTS')) }) + it('rejects a duplicate child session id before validating the source boundary', async () => { + const { ctx, fork } = await setup() + const source = ctx.sessions.create(SessionId('open-parent')) + source.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) + ctx.sessions.create(SessionId('child')) + + expect(() => fork.fork({ source, sessionId: SessionId('child') })) + .toThrow(new SessionForkError('session "child" already exists', 'SESSION_ALREADY_EXISTS')) + }) + it('persists a forked child seed through the existing session write path', async () => { const root = await tempRoot() const ctx = new Context()