This is related to how the Python import system and module search paths work.
In the script version:
import os
os.chdir("/app/my-favorite-module")
from my_favorite_module.foo import Bar
os.chdir("/app")
When you change the current working directory using os.chdir, it only affects the script's current process and its child processes (if any). When the script is importing the module my_favorite_module.foo, Python searches for the module in the standard module search paths. Since you changed the working directory, the current directory is /app/my-favorite-module, and Python won't look in this directory for modules. Instead, it will look in the standard module paths, and thus, it will not find my_favorite_module.foo.
On the other hand, in the Python REPL version:
import os; os.chdir("/app/my-favorite-module")
from my_favorite_module.foo import Bar
os.chdir("/app")
The Python REPL runs interactively, and when you change the current working directory using os.chdir in the REPL, it affects the Python process running the REPL itself. As a result, when you then import my_favorite_module.foo, Python will search for the module in the current directory (/app/my-favorite-module), find it, and import it successfully.
As you've mentioned, using sys.path is a proper workaround for this situation. By adding the directory to sys.path, you are essentially telling Python to look in that directory when searching for modules, and this works consistently both in scripts and the Python REPL.