Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 49 additions & 16 deletions src/claude/facade.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,26 +131,31 @@ async def stream_handler(update: StreamUpdate):

# Execute command
try:
# Only continue session if it's not a new session
should_continue = bool(session_id) and not getattr(
session, "is_new_session", False
# Only continue session if we have a real (non-temporary) session
# Temporary sessions start with "temp_" prefix
should_continue = bool(session_id) and not session.session_id.startswith(
"temp_"
)

# For new sessions, don't pass the temporary session_id to Claude Code
# For new sessions (temp_ prefix), don't pass the temporary session_id to Claude Code
claude_session_id = (
None
if getattr(session, "is_new_session", False)
else session.session_id
None if session.session_id.startswith("temp_") else session.session_id
)

response = await self._execute_with_fallback(
response, backend_used = await self._execute_with_fallback(
prompt=prompt,
working_directory=working_directory,
session_id=claude_session_id,
continue_session=should_continue,
stream_callback=stream_handler,
session_backend=session.backend,
)

# Track which backend was used for this session
if not session.backend:
session.backend = backend_used
logger.info(f"Session backend set to: {backend_used}")

# Check if tool validation failed
if not tools_validated:
logger.error(
Expand Down Expand Up @@ -193,8 +198,8 @@ async def stream_handler(update: StreamUpdate):
old_session_id = session.session_id
await self.session_manager.update_session(session.session_id, response)

# For new sessions, get the updated session_id from the session manager
if hasattr(session, "is_new_session") and response.session_id:
# For new sessions (temp_ prefix), get the updated session_id from the session manager
if old_session_id.startswith("temp_") and response.session_id:
# The session_id has been updated to Claude's session_id
final_session_id = response.session_id
else:
Expand Down Expand Up @@ -231,9 +236,35 @@ async def _execute_with_fallback(
session_id: Optional[str] = None,
continue_session: bool = False,
stream_callback: Optional[Callable] = None,
) -> ClaudeResponse:
"""Execute command with SDK->subprocess fallback on JSON decode errors."""
# Try SDK first if configured
session_backend: Optional[str] = None,
) -> tuple[ClaudeResponse, str]:
"""Execute command with SDK->subprocess fallback on JSON decode errors.

Returns: (response, backend_used) where backend_used is 'sdk' or 'subprocess'
"""
# If session has a backend preference, use it exclusively
if session_backend:
logger.debug(f"Using session's preferred backend: {session_backend}")
if session_backend == "subprocess":
response = await self.process_manager.execute_command(
prompt=prompt,
working_directory=working_directory,
session_id=session_id,
continue_session=continue_session,
stream_callback=stream_callback,
)
return response, "subprocess"
elif session_backend == "sdk" and self.sdk_manager:
response = await self.sdk_manager.execute_command(
prompt=prompt,
working_directory=working_directory,
session_id=session_id,
continue_session=continue_session,
stream_callback=stream_callback,
)
return response, "sdk"

# Try SDK first if configured (for new sessions)
if self.config.use_sdk and self.sdk_manager:
try:
logger.debug("Attempting Claude SDK execution")
Expand All @@ -246,7 +277,7 @@ async def _execute_with_fallback(
)
# Reset failure count on success
self._sdk_failed_count = 0
return response
return response, "sdk"

except Exception as e:
error_str = str(e)
Expand All @@ -268,6 +299,7 @@ async def _execute_with_fallback(
# Use subprocess fallback
try:
logger.info("Executing with subprocess fallback")
# Pass session info to subprocess to maintain continuity
response = await self.process_manager.execute_command(
prompt=prompt,
working_directory=working_directory,
Expand All @@ -276,7 +308,7 @@ async def _execute_with_fallback(
stream_callback=stream_callback,
)
logger.info("Subprocess fallback succeeded")
return response
return response, "subprocess"

except Exception as fallback_error:
logger.error(
Expand All @@ -295,13 +327,14 @@ async def _execute_with_fallback(
else:
# Use subprocess directly if SDK not configured
logger.debug("Using subprocess execution (SDK disabled)")
return await self.process_manager.execute_command(
response = await self.process_manager.execute_command(
prompt=prompt,
working_directory=working_directory,
session_id=session_id,
continue_session=continue_session,
stream_callback=stream_callback,
)
return response, "subprocess"

async def continue_session(
self,
Expand Down
1 change: 1 addition & 0 deletions src/claude/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class ClaudeSession:
message_count: int = 0
tools_used: List[str] = field(default_factory=list)
is_new_session: bool = False # True if session hasn't been sent to Claude Code yet
backend: Optional[str] = None # Track which backend created this session ('sdk' or 'subprocess')

def is_expired(self, timeout_hours: int) -> bool:
"""Check if session has expired."""
Expand Down