Flatpak App Freezes on Launch? 7 Steps to a Poisoned Font Cache
📜 Preamble
A failure that looks like slowness
When an application freezes on launch, it is tempting to blame "a slow machine". Here, the window of 4KTUBE — a video download application distributed as a Flatpak, written in Python with Qt5 — never appeared: the process stayed visible for about a minute, then vanished without a single message.
The investigation showed that the application was not freezing: it was dying on every launch, from a segmentation fault inside fontconfig, the library that selects fonts. The culprit was neither the application, nor Flatpak, nor Wayland, but a font cache file written by a completely different program: sharp, the image library of the Node.js ecosystem — the very one used to build this blog.
🎯 Goal: a reproducible method for any Qt/KDE Flatpak that crashes or "freezes" at startup, plus a lasting safeguard.
🧊 The symptom: "it freezes"
Click the icon: nothing. No window, no error. The process shows up in the task manager, stays there for about a minute, then disappears. Started from a terminal with flatpak run, the application prints nothing for 60 seconds.
The context muddied the waters: the machine — a four-core AMD APU — was heavily loaded that day (load average measured at 13.8 on 4 cores, 0% idle CPU). Everything pointed to "it's just slow". That was wrong.
💡 A reflex worth having
Before blaming slowness, ask the kernel what the process is actually doing. A slow program is working; a frozen program is waiting for something; a crashing program… is sometimes busy writing its core dump for many seconds. On screen, all three look the same.
🧠 Visual overview and timeline
Here is the full map before diving into the details. It serves as a roadmap for the investigation that follows.
🌐 QT5 FLATPAK APP "FROZEN" AT LAUNCH
│
├── 🚨 SYMPTOM
│ ├── Window never shown, process visible ~1 min
│ └── No error message
│
├── ⚠️ SYSTEM REALITY
│ ├── SIGSEGV on every launch (core dump)
│ └── The "freeze" = writing the core dump (wchan: vfs_coredump)
│
├── 🔍 IMMEDIATE CAUSE
│ └── fontconfig 2.17.1 (Flatpak runtime) reads a v12-format cache
│ └── through a link <hash>-le64.cache-9 → <hash>-le64.cache-12
│
├── 🧬 ROOT CAUSE
│ └── sharp-libvips (bundled fontconfig 2.18.3)
│ └── writes v12 caches + backward-compatibility links
│ into ~/.cache/fontconfig, shared with Flatpaks
│
└── 🛠️ RESPONSE
├── Fix: archive the 42 poisoned links
└── Prevention: a systemd guard (.path) that archives any new link
The reconstructed timeline
- 2026-08-16fontconfig 2.18.1 is installed on the host. It writes its caches in v10 format: as we will see, it is innocent.
- 2026-09-25Chromium 154 is installed. A tempting red herring (a Chrome 154 bug is reported elsewhere), but the openSUSE package uses the system fontconfig.
- 2026-09-27 · 04:1514 v12 cache files and 42 links
cache-9/10/11 → cache-12appear in~/.cache/fontconfig. - 2026-09-30 · 13:11Application update: it switches to the
org.kde.Platform 5.15-25.08runtime, which ships fontconfig 2.17.1. - 2026-10-02 · 18:09First launch after the update: first SIGSEGV. Every following launch crashes too.
- 2026-10-05Full diagnosis, fix, then an automatic guard is put in place.
The poison was in place three days before the update that made it lethal. That is typical: two harmless changes, days apart, with no visible connection.
🧭 Step 1: identify the application and its runtime
First reflex with a Flatpak: find out which runtime it uses. The runtime, not your distribution, provides Qt, fontconfig, glibc and most libraries.
flatpak list --app | grep -i 4ktube flatpak info com.warlordsoftwares.youtube-downloader-4ktube flatpak info --show-permissions com.warlordsoftwares.youtube-downloader-4ktube
| Item | Measured value |
|---|---|
| Identifier | com.warlordsoftwares.youtube-downloader-4ktube |
| Version | 2026.9.7 (Flathub) |
| Runtime | org.kde.Platform/x86_64/5.15-25.08 (Qt 5.15.18) |
| Display sockets | wayland + fallback-x11 |
| Nature | Python script (PyQt5) with QtWebEngine |
📌 Key takeaway
Two identical machines can run the same application on two different runtimes, depending on when they were updated. Always write down the exact runtime branch.
⏱️ Step 2: frozen or dying? Read the process state
Start the application in the background, then ask the kernel for the process state and the kernel function it is waiting in (wchan).
flatpak run com.warlordsoftwares.youtube-downloader-4ktube & sleep 30 ps -o pid,stat,etime,time,wchan:25,cmd -p "$(pgrep -f '[4]ktube.py')"
PID STAT ELAPSED TIME WCHAN CMD 37866 DN 00:35 00:00:03 folio_wait_bit_common python3 /app/4ktube/4ktube.py … about 70 seconds later: 37866 SNl 01:42 00:00:13 vfs_coredump python3 /app/4ktube/4ktube.py
The second line says it all: vfs_coredump is the kernel function that writes the core dump of a process that has just crashed. The "freeze" was nothing but that write, slowed down by a saturated machine. The application was already dead.
⚠️ Real-world trap: pkill -f killing itself
A pkill -f "4ktube.py" run inside a compound command kills… the very shell running it, since its own command line contains the pattern. The bracket trick — pgrep -f '[4]ktube.py' — matches the target process without matching the pattern itself.
💥 Step 3: make the core dumps talk
On a systemd system, every crash is archived by systemd-coredump. Two commands are enough for a first reading (abridged output).
coredumpctl list --no-pager | grep -i python coredumpctl info <PID> --no-pager
Fri 2026-10-02 18:09:53 CEST 68128 1000 1000 SIGSEGV present /usr/bin/python3.13 39.7M
Fri 2026-10-02 18:15:57 CEST 70100 1000 1000 SIGSEGV present /usr/bin/python3.13 30.2M
Mon 2026-10-05 18:56:50 CEST 37164 1000 1000 SIGSEGV present /usr/bin/python3.13 31.1M
Mon 2026-10-05 18:58:08 CEST 37866 1000 1000 SIGSEGV present /usr/bin/python3.13 31M
Signal: 11 (SEGV) si_code: SI_KERNEL
Command Line: python3 /app/4ktube/4ktube.py
#0 libfontconfig.so.1.16.0 + 0xc26f
#1 libQt5WaylandClient.so.5.15.18 + 0xb1456
Three decisive facts:
- The crash is systematic: every launch since October 2 produces a core dump.
- The first one dates from October 2 at 18:09: a date to correlate with updates.
- The stack ends inside fontconfig, called by Qt's Wayland module. The
si_code SI_KERNELpoints to a general protection fault, often the sign of a garbage pointer.
🔗 Step 4: correlate with Flatpak updates
Your distribution's package history does not show Flatpak updates. They have their own log.
flatpak history --columns=time,change,application,branch | tail -n 15
Sep 30 13:04:22 deploy install org.kde.KStyle.Adwaita 5.15-25.08 Sep 30 13:04:28 deploy install org.kde.Platform.Locale 5.15-25.08 Sep 30 13:09:50 deploy install org.kde.Platform 5.15-25.08 Sep 30 13:11:35 deploy install com.warlordsoftwares.youtube-downloader-4ktube stable
On September 30, the application was redeployed on a brand-new runtime. Let's compare the runtime's fontconfig with the host's:
flatpak run --command=fc-match com.warlordsoftwares.youtube-downloader-4ktube --version rpm -q fontconfig
| Side | fontconfig | Cache format read/written |
|---|---|---|
| Flatpak runtime 5.15-25.08 | 2.17.1 | v9 (…-le64.cache-9) |
| openSUSE Tumbleweed host | 2.18.1 | v10 (…-le64.cache-reindex1-10) |
Two different versions of the same library sharing cache directories: the trail narrows.
🔬 Step 5: change one layer, then read the real stack
5a. Wayland or X11?
The stack mentions Qt's Wayland module. Before blaming Wayland, change that single layer and relaunch under X11 (XWayland):
flatpak run --socket=x11 --nosocket=wayland --env=QT_QPA_PLATFORM=xcb com.warlordsoftwares.youtube-downloader-4ktube
Signal: 11 (SEGV) si_code: SI_KERNEL
#0 libfontconfig.so.1.16.0 + 0xc26f
#1 libQt5XcbQpa.so.5.15.18 + 0xbc5b6
Same crash, same address inside fontconfig: only the caller changes. Wayland is innocent; the problem lies in fontconfig or in what it reads.
5b. The "nearest symbol" trap
Classic temptation: use nm -D to find the exported symbol closest to offset 0xc26f. Result: FcCharSetDestroy + 0xcf. That was wrong: the function actually involved is internal (not exported), and the tool simply returned the last public symbol before it.
5c. The real stack, with gdb and a Flatpak sysroot
The libraries in the core dump carry paths internal to the sandbox (/usr/lib/x86_64-linux-gnu/…). So we rebuild a "sysroot" made of links to the runtime and the application, then hand it to gdb:
RT=/var/lib/flatpak/runtime/org.kde.Platform/x86_64/5.15-25.08/active/files
APP=/var/lib/flatpak/app/com.warlordsoftwares.youtube-downloader-4ktube/x86_64/stable/active/files
mkdir -p ~/sysroot
ln -sfn "$RT" ~/sysroot/usr
ln -sfn "$APP" ~/sysroot/app
ln -sfn usr/lib ~/sysroot/lib
ln -sfn usr/lib ~/sysroot/lib64
coredumpctl dump <PID> -o ~/core.4ktube
gdb -q -batch -nx -ex "set sysroot $HOME/sysroot" -ex "set debuginfod enabled off" \
-ex "core-file $HOME/core.4ktube" -ex "bt 30" ~/sysroot/usr/bin/python3.13
- #0FcCharSetFindLeafForwardlibfontconfig
- #1FcCharSetHasCharlibfontconfig
- #3QFontEngineMulti::stringToCMaplibQt5Gui
- #4QTextEngine::shapeTextlibQt5Gui
- #13QTextDocument::setHtmllibQt5Gui
- #18QLabel::sizeHintlibQt5Widgets
- #26QLayout::activatelibQt5Widgets
- #27QWidgetPrivate::setVisiblelibQt5Widgets
Stack from the X11 test's core dump (excerpt). Read bottom-up, the story becomes clear: while showing the window, Qt computes the size of a rich-text label, lays out that text and asks fontconfig whether a fallback font contains a given character (FcCharSetHasChar). fontconfig then walks the font's character table… and reads memory that makes no sense. Those character tables come from the font cache.
🧪 Step 6: refute, then find the poison
A first hypothesis, ruled out
Ruled out
"Caches written by the host's fontconfig 2.18.1 are incompatible with the runtime's 2.17.1."
Inside the sandbox, fc-list (461 fonts), fc-list ':charset=4e00' and fc-match -s sans all work. And the host's caches carry a different name (cache-reindex1-10): a v9 reader does not open them.
Retained
"The sandbox reads, under the name of a v9 cache, a file that is not one."
To be checked in two moves: where the sandbox looks for its caches, then what it finds there.
⚠️ A green test proves nothing
The fc-list commands above did not crash even though the poison was present. We did not establish why. Lesson: a working tool only proves that that particular path works, not that the data is sound.
Where does the sandbox look for caches?
flatpak run --command=sh com.warlordsoftwares.youtube-downloader-4ktube -c \ 'grep -h -o "<cachedir[^<]*</cachedir>" /etc/fonts/conf.d/50-flatpak.conf'
<cachedir>/usr/lib/fontconfig/cache</cachedir> <cachedir>/app/cache/fontconfig</cachedir> <cachedir prefix="xdg">fontconfig</cachedir> <cachedir>/run/host/fonts-cache</cachedir> <cachedir>/run/host/user-fonts-cache</cachedir>
/run/host/user-fonts-cache is nothing other than the user's ~/.cache/fontconfig on the host, mounted read-only into the sandbox. This sharing is intentional: it is the configuration shipped with the runtime, and it spares each application from re-indexing the host's fonts.
What it finds there
Abridged output (hashes shortened):
find ~/.cache/fontconfig -maxdepth 1 -type l -printf '%TY-%Tm-%Td %TH:%TM %f -> %l\n' | head -n 3 find ~/.cache/fontconfig -maxdepth 1 -type l | wc -l
2026-09-27 04:15 8e3a56fb…-le64.cache-9 -> 8e3a56fb…-le64.cache-12 2026-09-27 04:15 8e3a56fb…-le64.cache-10 -> 8e3a56fb…-le64.cache-12 2026-09-27 04:15 8e3a56fb…-le64.cache-11 -> 8e3a56fb…-le64.cache-12 42
42 symbolic links (14 caches × 3 older formats) that pass a v12 file off as a v9, v10 or v11 file. To be certain, read the binary header of each file type: four bytes of signature, then four bytes of version.
python3 - <<'EOF'
import glob, os, struct
for pattern in ('*.cache-12', '*reindex1-10'):
f = sorted(glob.glob(os.path.expanduser('~/.cache/fontconfig/' + pattern)))[0]
magic, version = struct.unpack('<Ii', open(f, 'rb').read(8))
print(pattern, hex(magic), 'version =', version)
EOF
*.cache-12 0xfc02fc04 version = 12 *reindex1-10 0xfc02fc04 version = 10
The proof is material: behind the name cache-9 sits content in format 12. The runtime's fontconfig 2.17.1 follows the link and reads structures it cannot interpret — exactly the mechanism described in a public report about another piece of software (see sources).
🕵️ Step 7: unmask the writer
Who, on this machine, can write a v12 fontconfig cache? Let's review the candidates, with evidence.
| Candidate | fontconfig used | Verdict |
|---|---|---|
| Host fontconfig | 2.18.1 (system) | Innocent — writes v10 |
| Chromium 154 (openSUSE package) | linked to system fontconfig | Innocent |
| Playwright's headless Chrome | bundled | Not tested |
| sharp-libvips 1.3.3 / 1.3.4 (Node.js) | 2.18.3, statically bundled | Guilty — proof below |
The versions.json file of sharp-libvips lists a fontconfig 2.18.3 compiled into the library. To catch it red-handed without touching the real cache, make it draw some text while giving it an isolated cache:
cd /path/to/a/node-project-with-sharp
XDG_CACHE_HOME=/tmp/fc-test node -e "require('sharp')(Buffer.from('<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"200\" height=\"50\"><text x=\"5\" y=\"30\">Test</text></svg>')).png().toBuffer().then(() => console.log('render OK'))"
ls /tmp/fc-test/fontconfig | sed -E 's/^[0-9a-f]+-le64\.//' | sort | uniq -c
14 cache-12
1 CACHEDIR.TAG
Fourteen v12 files, exactly the number found on September 27. The writer is identified. Two points honestly remain open:
- in an empty directory, sharp created no links: the backward-compatibility links appear when older caches already exist, as the report cited in the sources describes — we did not reproduce that part;
- the exact process that ran sharp on September 27 at 04:15 was not identified.
🙃 The irony of it
sharp is a dependency of the site generator that builds SafeITExperts. The tool that makes this blog unwittingly broke an application on the machine that writes it.
🛠️ The fix: archive the poisoned links
No need to touch the application or the runtime: removing the links is enough. We archive them rather than delete them, so the change can be rolled back.
mkdir -p ~/archives/fontconfig-cache12/links
find ~/.cache/fontconfig -maxdepth 1 -type l -lname '*cache-12' -exec mv -n {} ~/archives/fontconfig-cache12/links/ \;
ls ~/archives/fontconfig-cache12/links | wc -l
42
The 14 cache-12 files stay in place: only a v12 reader opens them. Deprived of its links, the 2.17.1 runtime no longer finds a fake v9 cache, rebuilds its own cache in the application's directory, and everything falls back into place.
Verification
flatpak run com.warlordsoftwares.youtube-downloader-4ktube & sleep 120 coredumpctl list --no-pager --since "-3min"
No coredumps found.
✅ Measured result
Process still alive after more than 2 minutes, no new core dump, 13 cache files rebuilt in the application's directory, window displayed. Rollback: mv -n ~/archives/fontconfig-cache12/links/* ~/.cache/fontconfig/ (which, of course, brings the crash back).
A more radical alternative circulates: wipe ~/.cache/fontconfig entirely, then run fc-cache -f. It works too, but destroys healthy caches, and the poison will come back the next time sharp runs.
🛡️ Prevention: a systemd guard
As long as a program bundling a recent fontconfig writes into ~/.cache/fontconfig, the links can come back. Rather than watching every tool, we watch the directory: a systemd .path unit triggers a small script as soon as its content changes.
The script
#!/bin/bash
# ~/.local/bin/fontconfig-cache12-guard.sh
# Archives the links <hash>-le64.cache-{9,10,11} -> *.cache-12 (poison for fontconfig v9 readers).
set -u
SRC=${FCG_SRC:-$HOME/.cache/fontconfig}
AR_ROOT=${FCG_AR:-$HOME/archives}
[ -d "$SRC" ] || exit 0
mapfile -d '' links < <(find "$SRC" -maxdepth 1 -type l -lname '*cache-12' -print0)
[ "${#links[@]}" -eq 0 ] && exit 0
DEST="$AR_ROOT/$(date +%Y-%m-%d)_fontconfig_links_cache12/$(date +%H%M%S)_$$"
mkdir -p "$DEST" || exit 1
n=0
for l in "${links[@]}"; do
mv -n -- "$l" "$DEST/" && [ ! -L "$l" ] && n=$((n + 1))
done
echo "fontconfig-guard: ${n}/${#links[@]} link(s) archived in $DEST"
[ "$n" -eq "${#links[@]}" ]
The two systemd (user) units
# ~/.config/systemd/user/fontconfig-cache12-guard.path [Unit] Description=fontconfig guard: watches ~/.cache/fontconfig (links -> cache-12) [Path] PathChanged=%h/.cache/fontconfig Unit=fontconfig-cache12-guard.service [Install] WantedBy=default.target # ~/.config/systemd/user/fontconfig-cache12-guard.service [Unit] Description=fontconfig guard: archives links -> cache-12 [Service] Type=oneshot ExecStart=/usr/bin/bash %h/.local/bin/fontconfig-cache12-guard.sh [Install] WantedBy=default.target
bash -n ~/.local/bin/fontconfig-cache12-guard.sh
systemd-analyze --user verify ~/.config/systemd/user/fontconfig-cache12-guard.{service,path}
systemctl --user daemon-reload
systemctl --user enable fontconfig-cache12-guard.service fontconfig-cache12-guard.path
systemctl --user start fontconfig-cache12-guard.path
The .service is also enabled at login: a check runs even if links appeared while the guard was not running.
Test before you trust
The script was first proven on a test directory (variables FCG_SRC and FCG_AR): directory without links, three links to archive mixed with files and one link that must not move, idempotent rerun, missing directory. Then, once the guard was live, a real test link (output paths adapted to the generic version of the script):
ln -s WITNESS-le64.cache-12 ~/.cache/fontconfig/WITNESS-le64.cache-9 sleep 2 ls ~/.cache/fontconfig/WITNESS* 2>/dev/null || echo "test link archived" journalctl --user -u fontconfig-cache12-guard.service -n 3 --no-pager
test link archived fontconfig-guard: 1/1 link(s) archived in ~/archives/2026-10-05_fontconfig_links_cache12/193511_54755
⚠️ Known limitation
The guard only handles links. A program writing v12 content directly under a cache-9 name would slip through. That case was not observed.
🎯 Who is affected?
The scenario needs two ingredients, and both are common.
The victims
Applications whose fontconfig only reads older cache formats — here a Qt5 Flatpak on the org.kde.Platform 5.15-25.08 runtime (fontconfig 2.17.1). plasmashell crashes in the same function (FcCharSetFindLeafForward) are publicly reported; we did not reproduce them.
The writers
Programs that bundle a recent fontconfig and write into ~/.cache/fontconfig: here sharp-libvips, a common dependency of Node.js projects — including this blog's generator. A public report describes the same effect from Chrome 154.
One-line check — if the result is not zero, you are exposed:
find ~/.cache/fontconfig -maxdepth 1 -type l -lname '*cache-12' | wc -l
📚 What this failure teaches
1
A "freeze" can hide a crash. Read STAT and wchan before concluding it is slowness.
2
Think runtime, not distribution. A Flatpak changes libraries without your package manager knowing: check flatpak history.
3
One layer at a time. Switching from Wayland to X11 cleared Wayland in a single try.
4
The "nearest symbol" can lie. For a real stack, use gdb and a Flatpak sysroot.
5
A green test proves nothing. fc-list worked while the poison was there.
6
The binary header is the truth, not the file name. cache-9 contained format 12.
7
Archive rather than delete, then install a tested guard. The fix is reversible, the prevention verified.
🏁 Conclusion
A Flatpak application "frozen" at launch was in fact being killed by a segmentation fault, itself caused by a font cache in the wrong format, written three days earlier by a Node.js image library. None of the three actors was at fault alone: it is their meeting in a shared directory that produced the failure.
The case is a reminder of an often-forgotten reality: Flatpak isolation is not total, and that is by design. Some host caches, such as the font cache, are shared on purpose. They then become an implicit interface between the host and the sandbox — an interface nobody versions.
🎯 The method, however, is reusable: observe the real state, read the core dump, correlate, change one layer at a time, prove by the header rather than by the name.
🔗 Sources and references
- orca #23205 — sharp-libvips writes a fontconfig v12 cache and cache-9/10/11 links, crashing Qt/KDE apps
- KDE Bug 525636 — plasmashell crashes in FcCharSetFindLeafForward on startup
- Google Issue Tracker 565132857 — Chrome 154 creates cache-9 links to cache-12; Plasma crashes
- sharp-libvips — prebuilt libvips binaries used by sharp
- fontconfig — official project page (freedesktop.org)
- systemd.path — official documentation of path-watching units
- coredumpctl — official documentation
- Official Flatpak documentation
📖 Recommended articles
Fix PipeWire Audio: 7-Step Linux Troubleshooting
Real case on openSUSE Tumbleweed: WirePlumber SIGSEGV crash, core-dump loop and ABI conflict.
Wayland 2026: The Post-X11 Era for Linux
Why Wayland replaces X11, what changes for Linux users, and the real state of compatibility.
Mastering Zypper: Complete openSUSE Package Management
The central software management tool of openSUSE, Leap or Tumbleweed: a clear and structured reference.
Is Linux Really Becoming More Like Windows?
KDE, GNOME, Flatpak, Snap, Proton: Linux 2026 adopts Windows' codes without losing its open source DNA.
👥 Comments
Comment on this article