Repository: All-Hands-AI/OpenHands
File: openhands/server/routes/manage_conversations.py (Python)

Code before:
    )

    model_config = ConfigDict(extra='forbid')

    def model_post_init(self, __context) -> None:
        """Validate that selected_branch and selected_repository are provided together."""
        if (self.selected_branch is None) != (self.selected_repository is None):
            raise ValueError(
                'selected_branch and selected_repository must be provided together or both omitted'
            )


def _build_update_log_message(data: UpdateConversationRequest) -> str:
    """Build a log message describing which fields are being updated."""
    update_fields = []
    if data.title is not None:
        update_fields.append(f'title: {data.title}')
    if data.selected_branch is not None:
        update_fields.append(f'selected_branch: {data.selected_branch}')
    if data.selected_repository is not None:
        update_fields.append(f'selected_repository: {data.selected_repository}')

    return ', '.join(update_fields) if update_fields else 'no fields'


async def _validate_branch_update(
    data: UpdateConversationRequest,
    metadata: ConversationMetadata,
    provider_tokens: PROVIDER_TOKEN_TYPE,
) -> JSONResponse | None:
    """Validate branch update requirements.

    Args:
        data: Update request data
        metadata: Current conversation metadata
        provider_tokens: Provider tokens for API access

    Returns:
        JSONResponse with error if validation fails, None if validation passes
    """
    if data.selected_branch is None:
        return None

    # Since both selected_branch and selected_repository are required together,
    # we can safely use data.selected_repository
    repository = data.selected_repository
    if (
        not repository
    ):  # This should never happen due to model validation, but for type safety
        return JSONResponse(
            content={

Review comment (bug):
If we're getting the branch information directly from the runtime by running a git command, do we have to validate that the branch exists again? 

Sometimes the agent may make a local branch that hasn't been pushed yet, so would this fail in those scenarios?

Also NIT: lets move the `json response` objects to whether the route is called, instead of having them in the helper function 

Code after:
        conversation_id: The ID of the conversation to update
        data: The conversation update data (title, etc.)
        user_id: The authenticated user ID
        conversation_store: The conversation store dependency

    Returns:
        bool: True if the conversation was updated successfully

    Raises:
        HTTPException: If conversation is not found or user lacks permission
    """
    logger.info(
        f'Updating conversation {conversation_id} with title: {data.title}',
        extra={'session_id': conversation_id, 'user_id': user_id},
    )

    try:
        # Get the existing conversation metadata
        metadata = await conversation_store.get_metadata(conversation_id)

        # Validate that the user owns this conversation
        if user_id and metadata.user_id != user_id:
            logger.warning(
                f'User {user_id} attempted to update conversation {conversation_id} owned by {metadata.user_id}',
                extra={'session_id': conversation_id, 'user_id': user_id},
            )
            return JSONResponse(
                content={
                    'status': 'error',
                    'message': 'Permission denied: You can only update your own conversations',
                    'msg_id': 'AUTHORIZATION$PERMISSION_DENIED',
                },
                status_code=status.HTTP_403_FORBIDDEN,
            )

        # Update the conversation metadata
        original_title = metadata.title
        metadata.title = data.title.strip()
        metadata.last_updated_at = datetime.now(timezone.utc)

        # Save the updated metadata
        await conversation_store.save_metadata(metadata)

        # Emit a status update to connected clients about the title change
        try:
            status_update_dict = {
                'status_update': True,
                'type': 'info',
                'message': conversation_id,
                'conversation_title': metadata.title,
            }