Python and Jupyter on an Android phone
The starting point#
VS Code was running on the phone (through code-server, VS Code in a browser), but there was no Python at all. The goal: Python for scripts, and Jupyter notebooks for data work.
Step 1: install Python#
The phone's Linux is Ubuntu 22.04, which ships Python 3.10, reaching end of life. We added a trusted extra package source (the deadsnakes PPA) and installed Python 3.12.
Step 2: virtual environments#
What is a virtual environment (venv)?
A private folder with its own Python and its own installed libraries. Project A can use pandas 2 while project B uses pandas 3, without conflicts. You "activate" one to use it.
We created one shared environment for data work with pandas, numpy, matplotlib, seaborn, openpyxl and JupyterLab, and made VS Code use it by default.
\"Permission denied\" creating a symlink
Venvs use symbolic links (shortcuts to the real Python). Android's shared storage forbids symlinks. Solution: keep environments in the Linux home folder (~/.venvs/) and keep project files in phone storage. VS Code connects the two. A small newenv command now creates a project environment correctly in one step.
Step 3: the kernel that kept dying#
Jupyter runs your notebook's code in a separate process called a kernel, which talks to the notebook through a messaging library called ZeroMQ. Every notebook failed with "Kernel died before replying".
Detective work#
- Run the kernel directly to see the real error:
Permission denied (src/ip_resolver.cpp:542). - Even a two-line ZeroMQ test crashed: the problem was ZeroMQ itself, not Jupyter.
- Reading the source location: when ZeroMQ opens a network port, it first asks the system for the list of network interfaces (
getifaddrs()). - Android blocks apps from asking that, so ZeroMQ treated it as impossible and aborted.
The fix: answer the question ourselves#
We wrote a 30-line C library providing our own getifaddrs() that simply reports "there is one interface: loopback (the machine talking to itself)", and told programs to load it first using LD_PRELOAD.
int getifaddrs(struct ifaddrs **ifap) {
/* report only the loopback interface, which is all a local kernel needs */
...
}
What is LD_PRELOAD?
An instruction to Linux: "when a program asks for a library function, look in this library first". It lets you replace one system function without changing the program. It's a powerful, slightly magical tool, so use it narrowly.
We wired it into the kernel settings, the terminal and VS Code. Notebooks worked immediately.
How to debug like this
1) Get the real error message (run the failing piece directly). 2) Shrink the problem to the smallest failing example. 3) Read where it fails (file and line). 4) Fix the cause, not the symptom.
Key takeaways#
- Use a modern Python and a virtual environment per project.
- Android storage blocks symlinks: environments live in the home folder.
- Jupyter = notebook + kernel talking over ZeroMQ.
LD_PRELOADcan replace a single blocked system call.
Quick quiz#
1. Why not create the venv inside the project folder on phone storage?
Venvs need symlinks and Android storage forbids them.
2. What did the custom library actually return?
A single loopback network interface, enough for ZeroMQ to open local connections.
Try it yourself#
Make a project environment
Run newenv myproject requests, then in VS Code pick the kernel "Python 3.12 (myproject)" in a new notebook and run import requests.