fix: stabilize session fork duplicate-id errors

This commit is contained in:
Hypatia May
2026-06-30 13:14:06 +08:00
parent 088860b80b
commit 680a41f43c
3 changed files with 12 additions and 2 deletions

View File

@@ -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

View File

@@ -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,

View File

@@ -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()