Skip to content

Commit 56dce1e

Browse files
authored
fix(cloud-agent-next): use lastIndexOf for DO name sessionId extraction (#1112)
## Summary The `CloudAgentSession` constructor parsed the Durable Object name using `split(':')[1]` to extract the session ID. The DO name format is `userId:sessionId` (e.g. `oauth/google:12345:agent_abc`). When the userId itself contains a colon (as with all OAuth provider IDs like `oauth/google:XXXXX`), splitting on the first colon incorrectly extracted the OAuth numeric ID (`12345`) instead of the actual session ID (`agent_abc`). This caused a `SessionId mismatch` error that blocked **all** session creation for OAuth-authenticated users. The fix switches from `split(':')[1]` to `lastIndexOf(':')` + `slice()`, so the session ID is always extracted from after the final colon regardless of how many colons appear in the userId. ## Verification - [x] `pnpm run typecheck` — passed (tsgo + wrapper) - [x] `pnpm run test` — 650 unit tests passed - [x] `pnpm run test:integration` — 30 integration tests passed (including 3 new tests for this fix) ## Visual Changes N/A ## Reviewer Notes - The bug only affects users whose `userId` contains a colon — all `oauth/*` provider IDs have this format. Users with plain UUID-style IDs were unaffected. - The new integration test in `test/integration/session/session-id-parsing.test.ts` covers: OAuth userId with colon, plain userId without colon, and metadata correctness verification. - `CloudAgentSession.ts:174-177` is the only location where the DO name is parsed into a sessionId.
2 parents f969f86 + 75891f6 commit 56dce1e

2 files changed

Lines changed: 79 additions & 2 deletions

File tree

cloud-agent-next/src/persistence/CloudAgentSession.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,12 @@ export class CloudAgentSession extends DurableObject {
170170
super(ctx, env);
171171

172172
// Extract sessionId from DO name pattern: "userId:sessionId"
173-
// The DO name is set by the worker when creating the stub
173+
// The DO name is set by the worker when creating the stub.
174+
// Split on the *last* colon because userId may contain colons
175+
// (e.g. "oauth/google:12345:agent_abc" → sessionId = "agent_abc").
174176
const doName = ctx.id.name;
175-
const sessionIdPart = doName?.split(':')[1];
177+
const lastColon = doName?.lastIndexOf(':') ?? -1;
178+
const sessionIdPart = doName && lastColon > 0 ? doName.slice(lastColon + 1) : undefined;
176179
this.sessionId = sessionIdPart ? (sessionIdPart as SessionId) : undefined;
177180

178181
const db = drizzle(ctx.storage, { logger: false });
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* Integration test for DO name → sessionId parsing.
3+
*
4+
* Ensures that userIds containing colons (e.g. "oauth/google:12345") don't
5+
* break the session-id extraction in the CloudAgentSession constructor.
6+
*/
7+
8+
import { env, runInDurableObject } from 'cloudflare:test';
9+
import { describe, it, expect } from 'vitest';
10+
11+
describe('CloudAgentSession sessionId parsing from DO name', () => {
12+
it('extracts sessionId correctly when userId contains a colon (OAuth provider)', async () => {
13+
const userId = 'oauth/google:103883072551006019454';
14+
const sessionId = 'agent_aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee';
15+
const doId = env.CLOUD_AGENT_SESSION.idFromName(`${userId}:${sessionId}`);
16+
const stub = env.CLOUD_AGENT_SESSION.get(doId);
17+
18+
const result = await runInDurableObject(stub, async instance => {
19+
return instance.prepare({
20+
sessionId,
21+
userId,
22+
kiloSessionId: 'kilo_test_session',
23+
prompt: 'test prompt',
24+
mode: 'code',
25+
model: 'test-model',
26+
});
27+
});
28+
29+
expect(result.success).toBe(true);
30+
});
31+
32+
it('extracts sessionId correctly when userId has no colon', async () => {
33+
const userId = 'user_simple';
34+
const sessionId = 'agent_11111111-2222-3333-4444-555555555555';
35+
const doId = env.CLOUD_AGENT_SESSION.idFromName(`${userId}:${sessionId}`);
36+
const stub = env.CLOUD_AGENT_SESSION.get(doId);
37+
38+
const result = await runInDurableObject(stub, async instance => {
39+
return instance.prepare({
40+
sessionId,
41+
userId,
42+
kiloSessionId: 'kilo_test_session',
43+
prompt: 'test prompt',
44+
mode: 'code',
45+
model: 'test-model',
46+
});
47+
});
48+
49+
expect(result.success).toBe(true);
50+
});
51+
52+
it('stores the correct sessionId in metadata (not the userId fragment)', async () => {
53+
const userId = 'oauth/github:99999';
54+
const sessionId = 'agent_metadata-check';
55+
const doId = env.CLOUD_AGENT_SESSION.idFromName(`${userId}:${sessionId}`);
56+
const stub = env.CLOUD_AGENT_SESSION.get(doId);
57+
58+
const metadata = await runInDurableObject(stub, async instance => {
59+
await instance.prepare({
60+
sessionId,
61+
userId,
62+
kiloSessionId: 'kilo_test_session',
63+
prompt: 'test prompt',
64+
mode: 'code',
65+
model: 'test-model',
66+
});
67+
return instance.getMetadata();
68+
});
69+
70+
// The stored sessionId must be the agent session ID, not the OAuth numeric ID
71+
expect(metadata?.sessionId).toBe(sessionId);
72+
expect(metadata?.sessionId).not.toBe('99999');
73+
});
74+
});

0 commit comments

Comments
 (0)