Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Claude Agent SDK v2 session API breaks session persistence #10

@paralin

Description

@paralin

Problem

The Claude Agent SDK's v2 session API (unstable_v2_createSession / unstable_v2_resumeSession) does not reliably persist sessions. When session.close() is called after streaming completes, it sends SIGTERM to the subprocess immediately, killing it before it can write session data to disk. This means unstable_v2_resumeSession() either fails outright or starts a new session with no context from the previous conversation.

Upstream issue: anthropics/claude-agent-sdk-typescript#177

Evidence

Two prototype scripts (prototypes/test-v1-session.ts and prototypes/test-v2-session.ts) that test session persistence with each API using the same flow:

  1. Send a prompt containing a secret code ("PINEAPPLE-42")
  2. Capture the session ID
  3. Wait 3 seconds, then resume the session
  4. Ask the model to recall the secret code

v1 query() API — works

--- v1 query() [new] ---
Response: PINEAPPLE-42 acknowledged.
Session ID: 4ff709d2-5111-4bd1-a8f7-0be073839e51

--- v1 query() [resume:4ff709d2-...] ---
Response: The secret code you shared earlier was **PINEAPPLE-42**.
Session ID: 4ff709d2-5111-4bd1-a8f7-0be073839e51  (SAME)

Session persisted: YES

The v1 query() returns an AsyncGenerator. When iteration completes, the subprocess exits naturally, giving it time to flush session data to disk before the process ends.

v2 session API — broken

--- v2 createSession [new] ---
Response: PINEAPPLE-42 acknowledged.
Session ID: 24d643eb-425a-4167-b388-3f537e0602e4

--- v2 resumeSession [24d643eb-...] ---
Response: (empty, 0 chars)
Session ID: 30bf9ac1-a436-425a-9fbe-b80969afa7ba  (DIFFERENT!)
Result subtype: error_during_execution

Session persisted: NO

session.close() sends SIGTERM immediately. The subprocess is killed before it can persist the session. On resume, a different session ID is returned and the model has no context from the first interaction.

Root cause

The v2 API requires calling session.close() to clean up resources after streaming completes. However, close() sends SIGTERM to the Claude Code subprocess, which kills it before the internal session persistence logic (writing to ~/.claude/projects/) can finish.

The v1 query() API doesn't have this problem because the subprocess lifecycle is tied to the AsyncGenerator — it exits naturally when the query is done, after persisting session data.

Additional notes

  • Setting persistSession: true does not help with the v2 API (the SDKSessionOptions type doesn't even include this field; it must be spread in as an escape hatch)
  • The v2 API is marked as @alpha / UNSTABLE, so this may be a known limitation
  • SDK version tested: @anthropic-ai/[email protected]

Resolution

Switched src/claude-code.ts from the v2 API to the v1 query() API:

  • Replace unstable_v2_createSession / unstable_v2_resumeSession with query()
  • Use Options.resume to resume existing sessions (instead of unstable_v2_resumeSession)
  • Use Options.abortController for cancellation (instead of session.close())
  • Only call q.close() on timeout to force-terminate; otherwise the subprocess exits naturally

This gives us reliable session persistence — the subprocess has time to write session data before exiting.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingupstream

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions