Described situation is a typical use case for overloading.
You can annotate the function such that for True argument it returns 3-tuple and for False - 2-tuple:
from typing import overload, Literal
@overload
def func(third: Literal[True]) -> tuple[int, int, int]:
...
@overload
def func(third: Literal[False] = ...) -> tuple[int, int]:
...
def func(third: bool = False) -> tuple[int, int] | tuple[int, int, int]:
if third:
return 1, 2, 3
else:
return 1, 2
a, b, c = func(True)
a, b = func(False)
a, b = func()
Playground
The problem in your current code is that mypy doesn't know whether return type will be 2-tuple or 3-tuple. It does not make additional assumptions looking at implementation, type hints are strict. Your code could look like this (interpreted by mypy exactly the same way):
import random
def func(three: bool = False) -> tuple[int, int] | tuple[int, int, int]:
if random.random() < 0.5:
return 1, 1
else:
return 1, 1, 1
a, b, c = func()
... And now your code fails in (approx) 50% cases and mypy points to that error.