STM32 - cannot properly send UART response because EXTI frequently interrupts UART handler with higher priority
Ok, I solved it.
The easiest thing to change that solves my issue is replacing:
HAL_UART_Transmit_IT(&amp;huart1, data, sizeof(data));

with:
HAL_UART_Transmit(&amp;huart1, data, sizeof(data), uartTimeout);

in UART RX callback.
That means I'm sending entire UART response inside UART RX callback instead of creating another interrupt request. I didn't notice any malfunctions during operation.
That being said, I still don't entirely understand why UART TX interrupt did not work correctly.
Based on the comments, I also tried it with a basic circular buffer. That means that my UART IRQ handler now looks like this:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if (isFull(pQueue)) {
    return;
  } else {
    enqueue(pQueue, uartMessage);
  }
  
  HAL_UART_Receive_IT(&amp;huart5, &amp;uartMessage, 1);
}

and the response is generated and sent back in the while loop (a simple backgroud-foreground architecture). Note that it is now possible to properly use TX interrupt (HAL_UART_Transmit_IT()) in spite of a blocking version (HAL_UART_Transmit()):
if (isEmpty(pQueue)) {
      
    } else {
      char msg = dequeue(pQueue);
      
      uint8_t data[50] = {0};
      if (msg == 'p') {
        *pPulseCounterBuffer = *pPulseCounter;
        *pPulseCounter = 0;
        
        sprintf((char*)data, &quot;Pulse counter = &lt;%d&gt;\n&quot;, *pPulseCounterBuffer); 
        
        // HAL_UART_Transmit(&amp;huart5, data, sizeof(data), uartTimeout);
        HAL_UART_Transmit_IT(&amp;huart5, data, sizeof(data));
      } else if (msg == 'f') {
        for (int i=0; i&lt;SAMPLE_TABLE_SIZE; i++) {
          gatePulseCounter[i] = 0;
        }
        
        sprintf((char*)data, &quot;Current frequency = &lt;%d&gt;\n&quot;, pulseFrequency_Hz);
        
        // HAL_UART_Transmit(&amp;huart5, data, sizeof(data), uartTimeout);
        HAL_UART_Transmit_IT(&amp;huart5, data, sizeof(data));
      } else {
        sprintf((char*)data, &quot;Wrong command. Use &lt;p&gt; or &lt;f&gt;\n&quot;); 
      
        // HAL_UART_Transmit(&amp;huart5, data, sizeof(data), uartTimeout);
        HAL_UART_Transmit_IT(&amp;huart5, data, sizeof(data));
      }
    }