Dev.to · 8 min read

The Desktop Looked Right: 2,093 Parse Errors a Boot, and 10.9 Seconds That Weren't Doing Anything

The Desktop Looked Right: 2,093 Parse Errors a Boot, and 10.9 Seconds That Weren't Doing Anything

This article walks through one afternoon of Debian system management, in which the machine turned out to have been reporting two of its own defects continuously, for as long as it had been configured, to nobody. Debian tells you almost everything. It just does not tell you on screen. The kernel ring buffer, the systemd journal, systemd-analyze, dpkg, apt-cache policy and /sys between them describe the state of the machine in far more detail than any settings panel — and a desktop can look completely correct while those sources are recording a failure a few hundred times a minute. What that gap creates is a feedback loop with a missing half. The machine emits the evidence; nothing reads it. The two defects below had both been present since the day the setup script was written, both were sitting in plain text in the journal, and neither was discoverable from the desktop: The GTK theme was discarded at parse time, 161 rules per application launch, 2,093 log lines in the boot I measured, while reporting success and looking approximately right. 10.9 seconds of a 39.3-second boot were spent waiting for something nothing was waiting for. Both were introduced by flex, the script in this repository whose job is to configure the machine: https://github.com/xbill9/chromeos-boot The Machine One laptop, measured on 2026-09-08: Hardware Lenovo Yoga 9i, Intel CometLake-H, NVIDIA GTX 1650 Ti Mobile OS Debian 13 (trixie) Kernel 7.1.8+deb13-amd64 (from trixie-backports) Desktop GNOME 48, Wayland, gdm3 GTK libgtk-4-1 4.18.6, libadwaita-1-0 1.7.6 That GTK version is the whole article, and nothing on the desktop ever mentions it. At This Point You Should Have A Debian desktop you have customised with something — a script, a dotfile repo, a settings panel, an afternoon of gsettings — and that appears to work sudo, or membership of adm / systemd-journal. Check this first, for reasons in Step 0 systemd-analyze, journalctl, dpkg, apt-cache — all base install Somewhere durable to write findings that is not your shell scrollback None of the diagnosis below changes anything. Steps 0 through 3 are reads. Step 0 — Find Out Whether You Can Read the Evidence At All This step exists because the investigation nearly started with a wrong answer. The first journalctl run reported four errors this boot, all of them GNOME session scopes failing to start, and that looked like the whole picture. It was not the whole picture. It was the picture available to an unprivileged user: id uid=1000(xbill) gid=1000(xbill) groups=1000(xbill),24(cdrom),25(floppy), 27(sudo),29(audio),30(dip),44(video),46(plugdev),100(users),101(netdev), 102(scanner),106(bluetooth),108(lpadmin),989(docker),992(render) No adm, no systemd-journal. Without either, journalctl silently narrows to your own user journal rather than refusing — you get output, it is correctly formatted, and it is a small fraction of what happened. The kernel buffer is closed off separately: dmesg | tail -3 dmesg: read kernel buffer failed: Operation not permitted The difference between the two views on this machine: View err and above, this boot Unprivileged journalctl 4 sudo journalctl 17 Four against seventeen, and nothing in the first view indicates the other thirteen exist. A loop that reads a filtered log converges on a wrong answer confidently, which is worse than not reading it. Add yourself to adm and log back in, or accept that everything downstream needs sudo — but decide it deliberately, because the failure mode is silence, not an error. Step 1 — Count Before You Read err and above is where people look, and on this machine it was almost entirely a red herring. The volume was one priority down: sudo journalctl -b -p err --no-pager -q | wc -l sudo journalctl -b -p warning --no-pager -q | wc -l 17 1032 A thousand warnings is too many to read, so collapse them into shapes first. Normalising the numbers and hex out of each message turns a thousand lines into a histogram of distinct problems: sudo journalctl -b -p warning --no-pager -q -o cat \ | sed 's/[0-9a-f]\{8,\}/HEX/g; s/[0-9]\+/N/g' \ | sort | uniq -c | sort -rn | head -5 795 Theme parser error: libadwaita.css:N:N-N: Unknown @ rule 27 blacklist: Duplicate blacklisted hash bin:HEX 24 Can't update stage views actor unnamed [StBin] is on because it needs an allocation. 10 Theme parser error: libadwaita-tweaks.css:N:N-N: Unknown @ rule 7 Unable to get default source 805 of the 1,032 warnings on this system were one defect. It had never appeared on screen, in any settings panel, or in the output of the script that caused it — which had reported, and still reports, success. Step 2 — Attribute the Message Before You Believe the Field The obvious next question is which program is producing them, and the obvious way to ask is the journal's own _COMM field. That gives a wrong answer, and it is worth showing because the same trap applies to any log the agent will read. 1288 cat 322 gnome-control-c 161 gnome-software 161 xdg-desktop-por 161 mutter-x11-fram cat is not a GTK application and does not parse stylesheets. Pulling one of those 1,288 records apart explains it: _COMM=cat _PID=11105 _EXE=/usr/bin/cat MESSAGE=(chrome:11097): Gtk-WARNING **: 09:20:36.757: Theme parser error: ... The webapp launchers this repository installs pipe their stderr, so journald attributes the line to the process that wrote it rather than the process that produced it. The real emitter is in the message payload, in the (name:pid) prefix that GLib puts there. The structured field describes the plumbing; the unstructured payload describes the event. Grouping by _COMM credits 1,288 parse errors to cat and hides Chrome entirely. Parse the prefix instead: sudo journalctl -b -q -o cat | grep "Theme parser error: libadwaita" \ | sed -E 's/^\(([a-zA-Z0-9_.-]+):[0-9]+\).*/\1/' | sort | uniq -c | sort -rn Corrected, the emitters are gnome-control-center, gnome-software, xdg-desktop-portal-gtk, mutter-x11-frames and Chrome — every GTK4 process on the system, each one failing identically, every time it starts. What the Theme Declared, and What GTK Served 161 distinct parse failures per process: 159 in libadwaita.css, 2 in libadwaita-tweaks.css. At warning and above each one appeared exactly five times — 159 × 5 = 795, 2 × 5 = 10 — one set per GTK4 process running at boot. Across all priorities, including the Chrome instances started later from the shelf, 2,093 lines in one boot. The message names the construct: Theme parser error: libadwaita.css:94:1-7: Unknown @ rule Columns 1-7 of line 94: @media (prefers-color-scheme: dark) { @define-color window_bg_color #222226; ... } @media. GTK's CSS parser is not a browser's, and GTK 4.18 does not implement @media. It reports the rule as unknown and discards the entire block. Which block is discarded matters more than the noise. flex installs adw-gtk3 from the upstream release tarball, pinned: grep ADW_VERSION flex ADW_VERSION=${ADW_VERSION:-v6.5} Upstream's own release notes, read in order, are unambiguous about what that pin means: Release Upstream's note @media in the GTK4 CSS v5.7 "New release for GNOME 48 and libadwaita 1.7" 0 v6.3 last release before the change 0 v6.4 "The GTK4 theme now requires GTK 4.20 or later." 3 v6.5 "Release for GNOME 50." 159 The machine runs GNOME 48, GTK 4.18.6 and libadwaita 1.7.6. The pin was v6.5 — GNOME 50, GTK 4.20+. There is a release named for this exact platform, in upstream's own words, and it is two major versions below the pin. git log says the pin was never wrong later; it was wrong from the start: git log --oneline -L58,58:flex 12a0ddc Add flex: Debian desktop customization to mimic ChromeOS Flex +ADW_VERSION=${ADW_VERSION:-v6.5} The newest release at the time, taken as the right one because it was newest, never checked against the desktop it had to run on. Why Nothing Looked Wrong A theme silently dropping 159 rule blocks should be visible, and it was not. Two reasons, and the second is the expensive one. First, libadwaita applications largely do not use the GTK theme at all — they carry their own stylesheet and follow color-scheme directly. Most of the desktop stayed dark because most of the desktop was never asking the theme. Second, and much worse: T=~/.local/share/themes for f in $(cd $T/adw-gtk3/gtk-4.0 && find . -type f | sort); do cmp -s "$T/adw-gtk3/gtk-4.0/$f" "$T/adw-gtk3-dark/gtk-4.0/$f" \ && echo "identical: $f" || echo "DIFFERS: $f" done identical: ./assets/bullet-symbolic.svg identical: ./assets/check-symbolic.svg identical: ./assets/dash-symbolic.svg identical: ./assets/devel-symbolic.svg identical: ./gtk.css identical: ./gtk-dark.css identical: ./libadwaita.css identical: ./libadwaita-tweaks.css At v6.5, every one of the eight files under gtk-4.0/ is byte-identical between adw-gtk3 and adw-gtk3-dark. From v6.4 upstream stopped shipping two GTK4 stylesheets. There is one file, and dark is selected at runtime by the @media query — the query GTK 4.18 throws away. So the dark theme's own file defines the light value, unconditionally, and the dark value only inside the block that never executes: adw-gtk3-dark/gtk-4.0/libadwaita.css:62: @define-color window_bg_color #fafafb;

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

More AI & Machine Learning News