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
39 changes: 30 additions & 9 deletions src/claude/facade.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""

import asyncio
from datetime import UTC, datetime
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional

Expand Down Expand Up @@ -89,20 +90,28 @@ async def run_command(
stream_callback=on_stream,
interrupt_event=interrupt_event,
)
except Exception as resume_error:
# If resume failed (e.g., session expired/missing on Claude's side),
# retry as a fresh session. The CLI returns a generic exit-code-1
# when the session is gone, so we catch *any* error during resume.
if should_continue:
except Exception as exec_error:
# Decide whether to destroy the session or preserve it.
# Timeouts and most process errors are transient — the
# Claude session likely still exists. Only destroy when
# the error clearly indicates the session is gone.
_SESSION_GONE_HINTS = (
"session not found",
"invalid session",
"session expired",
"no such session",
)
error_str = str(exec_error).lower()
session_is_gone = any(h in error_str for h in _SESSION_GONE_HINTS)

if should_continue and session_is_gone:
logger.warning(
"Session resume failed, starting fresh session",
"Session gone on Claude side, starting fresh",
failed_session_id=claude_session_id,
error=str(resume_error),
error=str(exec_error),
)
# Clean up the stale session
await self.session_manager.remove_session(session.session_id)

# Create a fresh session and retry
session = await self.session_manager.get_or_create_session(
user_id, working_directory
)
Expand All @@ -115,6 +124,18 @@ async def run_command(
interrupt_event=interrupt_event,
)
else:
# Transient error — preserve the session so the next
# message can resume it.
if session.session_id:
session.last_used = datetime.now(UTC)
await self.session_manager.storage.save_session(session)
logger.warning(
"Claude command failed, preserving session",
session_id=claude_session_id,
user_id=user_id,
error=str(exec_error),
error_type=type(exec_error).__name__,
)
raise

# Update session (assigns real session_id for new sessions)
Expand Down
2 changes: 1 addition & 1 deletion src/utils/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
APP_DESCRIPTION = "Telegram bot for remote Claude Code access"

# Default limits
DEFAULT_CLAUDE_TIMEOUT_SECONDS = 300
DEFAULT_CLAUDE_TIMEOUT_SECONDS = 1200
DEFAULT_CLAUDE_MAX_TURNS = 10
DEFAULT_CLAUDE_MAX_COST_PER_USER = 10.0
DEFAULT_CLAUDE_MAX_COST_PER_REQUEST = 5.0
Expand Down