Skip to content

Replace query() with ClaudeSDKClient for native session management#56

Merged
RichardAtCT merged 2 commits intomainfrom
finding1/sdk-client-migration
Feb 20, 2026
Merged

Replace query() with ClaudeSDKClient for native session management#56
RichardAtCT merged 2 commits intomainfrom
finding1/sdk-client-migration

Conversation

@RichardAtCT
Copy link
Owner

@RichardAtCT RichardAtCT commented Feb 19, 2026

Summary

  • Swap stateless query() for per-request ClaudeSDKClient instances, eliminating temp session IDs (temp_*) and the delete-old/save-new session swap pattern
  • New sessions now created with session_id="" and deferred storage save until Claude responds with a real ID
  • update_session() takes a ClaudeSession object directly instead of a string ID, simplifying the facade's post-execution flow
  • Uses ResultMessage.result for content extraction with fallback to message parsing

Test plan

  • All 379 tests pass (poetry run pytest tests/ -v)
  • No new flake8 or mypy errors introduced
  • New sessions get real session_id from ResultMessage (not temp)
  • Resumed sessions pass options.resume correctly to ClaudeSDKClient
  • ResultMessage.result used when available, falls back to message extraction
  • Manual smoke test with live Telegram bot

🤖 Generated with Claude Code

Swap the stateless query() function for per-request ClaudeSDKClient instances,
eliminating temp session IDs and the delete-old/save-new session swap pattern.

- sdk_integration.py: Use async with ClaudeSDKClient(options) + client.query()
  + client.receive_response() instead of query() async generator. Remove
  active_sessions dict, _update_session(), _execute_query_with_streaming().
  Use ResultMessage.result for content extraction with message fallback.
- session.py: New sessions created with session_id="" instead of temp_ prefix.
  Storage save deferred until Claude responds. update_session() now takes
  session object directly instead of session_id string.
- facade.py: Remove startswith("temp_") checks. Simplified post-execution
  session update to single update_session(session, response) call.
- Tests updated for new ClaudeSDKClient mock pattern and session lifecycle.
@RichardAtCT
Copy link
Owner Author

PR Review
Reviewed head: fe6928a07575bdcc2ff580889de586e5cf5e0871

Summary

  • Migrates from query() to per-request ClaudeSDKClient, which is a cleaner lifecycle model.
  • Removes temp session IDs and defers persistence until a real Claude session ID is returned.
  • Adds strong unit coverage around resume behavior, result extraction, timeout/error paths, and MCP option passing.

What looks good

  • src/claude/sdk_integration.py: using ResultMessage.result first with a fallback is the right extraction order.
  • tests/unit/test_claude/test_sdk_integration.py: coverage improved substantially and tracks intent of the refactor.
  • src/claude/facade.py: simplification from temp-ID checks to truthy real-ID checks is much easier to reason about.

Issues / questions

  1. [Blocker] src/claude/session.py (update_session) — if Claude returns no response.session_id for a new session, we still set is_new_session=False and then skip save_session because session.session_id is empty. That leaves an untracked in-memory session that cannot be resumed later and can silently lose continuity after process restart. Can we either (a) fail hard for new sessions without an ID, or (b) persist a recoverable placeholder state and retry ID binding on next turn?

  2. [Important] src/claude/facade.py + src/claude/session.py — after update_session, we force response.session_id = session.session_id. If session.session_id is still empty, downstream callers/logging get an empty ID that looks “successful” but is unusable. Suggest explicit guard + warning/error path when final session ID is empty.

  3. [Important] tests/unit/test_claude/test_session.py (test_session_limit_enforcement) — assertion expects len(persisted) == 1 after creating a third session, but comment says user should still have 2 persisted sessions. The test currently codifies behavior where new sessions are unsaved pre-response, but the mismatch between comment/expectation makes intent unclear. Worth tightening to avoid future regressions in retention logic.

Suggested tests (if needed)

  • Add a unit test for update_session(new_session, response_without_id) that verifies explicit behavior (raise, warning + persisted placeholder, or other chosen policy).
  • Add facade-level test ensuring empty final session_id is surfaced as an error/diagnostic, not silently propagated.
  • Add an integration-style test: create new session -> execute -> restart simulation -> resume, to validate persistence semantics end-to-end.

Verdict

  • ⚠️ Merge after fixes

When Claude returns no session_id for a new session, log warnings in
both the session manager and facade so the gap is diagnosable. Add
tests covering the empty session_id path and fix a misleading comment
in the eviction test.
@RichardAtCT RichardAtCT merged commit 3310581 into main Feb 20, 2026
1 check failed
@RichardAtCT RichardAtCT deleted the finding1/sdk-client-migration branch February 20, 2026 05:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments