Click to open · ✕ or click outside to close

Flatpak App Freezes on Launch? 7 Steps to a Poisoned Font Cache

🌐 Real case on openSUSE Tumbleweed (KDE Plasma / Wayland) – a Qt5 Flatpak app "freezes" then vanishes: segmentation fault inside fontconfig. 📅 Investigation carried out in October 2026 – a method you can replay on any Qt/KDE Flatpak that crashes at startup.
📅 Published October 2026 ⏱️ Reading time: 15–20 min 🧪 Level: intermediate/advanced
Flatpak fontconfig Qt5 openSUSE Tumbleweed KDE Plasma sharp Linux Forensics

📜 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

  1. 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.
  2. 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.
  3. 2026-09-27 · 04:1514 v12 cache files and 42 links cache-9/10/11 → cache-12 appear in ~/.cache/fontconfig.
  4. 2026-09-30 · 13:11Application update: it switches to the org.kde.Platform 5.15-25.08 runtime, which ships fontconfig 2.17.1.
  5. 2026-10-02 · 18:09First launch after the update: first SIGSEGV. Every following launch crashes too.
  6. 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
ItemMeasured value
Identifiercom.warlordsoftwares.youtube-downloader-4ktube
Version2026.9.7 (Flathub)
Runtimeorg.kde.Platform/x86_64/5.15-25.08 (Qt 5.15.18)
Display socketswayland + fallback-x11
NaturePython 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_KERNEL points 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
SidefontconfigCache format read/written
Flatpak runtime 5.15-25.082.17.1v9 (…-le64.cache-9)
openSUSE Tumbleweed host2.18.1v10 (…-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
  1. #0FcCharSetFindLeafForwardlibfontconfig
  2. #1FcCharSetHasCharlibfontconfig
  3. #3QFontEngineMulti::stringToCMaplibQt5Gui
  4. #4QTextEngine::shapeTextlibQt5Gui
  5. #13QTextDocument::setHtmllibQt5Gui
  6. #18QLabel::sizeHintlibQt5Widgets
  7. #26QLayout::activatelibQt5Widgets
  8. #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.

Candidatefontconfig usedVerdict
Host fontconfig2.18.1 (system)Innocent — writes v10
Chromium 154 (openSUSE package)linked to system fontconfigInnocent
Playwright's headless ChromebundledNot tested
sharp-libvips 1.3.3 / 1.3.4 (Node.js)2.18.3, statically bundledGuilty — 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

📖 Recommended articles

👥 Comments

Comment on this article