Skip to content

Commit 45bb1ef

Browse files
heavygeecursoragent
andcommitted
fix(web): scope sessionResume guard to current flavor only
Hub `resolveAgentResumeId` only honors the metadata.flavor's id; the web guard was falling back across all flavors so a cursor session with a stale codexSessionId still tried to resume and 409'd. Mirror the hub switch and default to claude when flavor is unknown. Addresses HAPI Bot review on tiann#761. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 786fe47 commit 45bb1ef

2 files changed

Lines changed: 52 additions & 9 deletions

File tree

web/src/lib/sessionResume.test.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,39 @@ function makeSession(overrides: Partial<Session> = {}): Session {
1515
}
1616

1717
describe('sessionResume', () => {
18-
it('resolveAgentSessionIdFromMetadata matches hub flavor precedence (codex before cursor)', () => {
18+
it('resolveAgentSessionIdFromMetadata picks the id matching the session flavor', () => {
1919
expect(resolveAgentSessionIdFromMetadata({
2020
path: '/p',
2121
host: 'h',
22+
flavor: 'codex',
2223
codexSessionId: 'codex-1',
2324
cursorSessionId: 'cursor-1',
2425
})).toBe('codex-1')
2526
expect(resolveAgentSessionIdFromMetadata({
2627
path: '/p',
2728
host: 'h',
29+
flavor: 'cursor',
2830
cursorSessionId: 'cursor-1',
2931
})).toBe('cursor-1')
3032
})
3133

34+
it('resolveAgentSessionIdFromMetadata ignores stale cross-flavor ids', () => {
35+
expect(resolveAgentSessionIdFromMetadata({
36+
path: '/p',
37+
host: 'h',
38+
flavor: 'cursor',
39+
codexSessionId: 'codex-1',
40+
})).toBeUndefined()
41+
})
42+
43+
it('resolveAgentSessionIdFromMetadata defaults to claude when flavor is missing', () => {
44+
expect(resolveAgentSessionIdFromMetadata({
45+
path: '/p',
46+
host: 'h',
47+
claudeSessionId: 'claude-1',
48+
})).toBe('claude-1')
49+
})
50+
3251
it('inactiveSessionCanResume is true for active sessions', () => {
3352
expect(inactiveSessionCanResume(makeSession({ active: true }), 0)).toBe(true)
3453
})
@@ -52,6 +71,25 @@ describe('sessionResume', () => {
5271
expect(inactiveSessionCanResume(makeSession(), 3)).toBe(false)
5372
})
5473

74+
it('inactiveSessionCanResume rejects when stale cross-flavor agent id is present but no messages', () => {
75+
expect(inactiveSessionCanResume(makeSession({
76+
metadata: {
77+
path: '/tmp/project',
78+
host: 'localhost',
79+
flavor: 'cursor',
80+
codexSessionId: 'stale-codex-1',
81+
},
82+
}), 0)).toBe(true)
83+
expect(inactiveSessionCanResume(makeSession({
84+
metadata: {
85+
path: '/tmp/project',
86+
host: 'localhost',
87+
flavor: 'cursor',
88+
codexSessionId: 'stale-codex-1',
89+
},
90+
}), 3)).toBe(false)
91+
})
92+
5593
it('inactiveSessionCanResume rejects when metadata path is missing', () => {
5694
expect(inactiveSessionCanResume(makeSession({ metadata: { path: '', host: 'localhost' } }), 0)).toBe(false)
5795
})

web/src/lib/sessionResume.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
1+
import { isKnownFlavor } from '@hapi/protocol'
12
import type { Session } from '@/types/api'
23

3-
/** Agent thread id used by hub `resolveAgentResumeId` (any flavor). */
4+
/** Agent thread id used by hub `resolveAgentResumeId`, flavor-specific.
5+
* Mirrors hub: cross-flavor ids are ignored to avoid the web layer claiming a
6+
* session is resumable when the hub will only honor the current flavor's id. */
47
export function resolveAgentSessionIdFromMetadata(
58
metadata: Session['metadata'] | null | undefined,
69
): string | undefined {
710
if (!metadata) {
811
return undefined
912
}
10-
return metadata.codexSessionId
11-
?? metadata.claudeSessionId
12-
?? metadata.geminiSessionId
13-
?? metadata.opencodeSessionId
14-
?? metadata.cursorSessionId
15-
?? metadata.kimiSessionId
16-
?? undefined
13+
const flavor = isKnownFlavor(metadata.flavor) ? metadata.flavor : 'claude'
14+
switch (flavor) {
15+
case 'codex': return metadata.codexSessionId ?? undefined
16+
case 'gemini': return metadata.geminiSessionId ?? undefined
17+
case 'opencode': return metadata.opencodeSessionId ?? undefined
18+
case 'cursor': return metadata.cursorSessionId ?? undefined
19+
case 'kimi': return metadata.kimiSessionId ?? undefined
20+
default: return metadata.claudeSessionId ?? undefined
21+
}
1722
}
1823

1924
/**

0 commit comments

Comments
 (0)