<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: 7sh1d0w7x</title>
    <description>The latest articles on DEV Community by 7sh1d0w7x (@7sh1d0w7x).</description>
    <link>https://dev.to/7sh1d0w7x</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4155454%2F417c4d9d-f862-458a-8aa7-1de6a90d7b84.png</url>
      <title>DEV Community: 7sh1d0w7x</title>
      <link>https://dev.to/7sh1d0w7x</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/7sh1d0w7x"/>
    <language>en</language>
    <item>
      <title>Linux Doctor, heal thyself: 20 ways my Linux health checker was wrong</title>
      <dc:creator>7sh1d0w7x</dc:creator>
      <pubDate>Thu, 01 Oct 2026 17:22:03 +0000</pubDate>
      <link>https://dev.to/7sh1d0w7x/linux-doctor-heal-thyself-20-ways-my-linux-health-checker-was-wrong-1b2a</link>
      <guid>https://dev.to/7sh1d0w7x/linux-doctor-heal-thyself-20-ways-my-linux-health-checker-was-wrong-1b2a</guid>
      <description>&lt;p&gt;A diagnostic tool has exactly one job: tell the truth about the machine in&lt;br&gt;
front of it. So when I built &lt;strong&gt;Linux Doctor&lt;/strong&gt; — a read-only checker that&lt;br&gt;
reports what is wrong and &lt;em&gt;prints&lt;/em&gt; the fix without ever running it — I did the&lt;br&gt;
thing a doctor dreads. I audited it against reality: five distro families,&lt;br&gt;
minimal container images, and machines that had been running for years.&lt;/p&gt;

&lt;p&gt;It lied about twenty times. None of them were exotic. Every one is a class of&lt;br&gt;
bug you can hit in any script that reads the output of system tools.&lt;/p&gt;

&lt;p&gt;Here they are, grouped by what they teach.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. A pipeline exits with the status of its &lt;em&gt;last&lt;/em&gt; command
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-P&lt;/span&gt; /boot | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 1     &lt;span class="c"&gt;# a failed df looks successful&lt;/span&gt;
journalctl &lt;span class="nt"&gt;-p&lt;/span&gt; err | &lt;span class="nb"&gt;grep &lt;/span&gt;MCE &lt;span class="c"&gt;# grep exits 1 when nothing matches&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;boot&lt;/code&gt; check read &lt;code&gt;/boot&lt;/code&gt;'s usage through &lt;code&gt;tail&lt;/code&gt;. If &lt;code&gt;df&lt;/code&gt; failed, the exit&lt;br&gt;
status still belonged to &lt;code&gt;tail&lt;/code&gt;, which succeeded — so a failed probe looked&lt;br&gt;
like a good one.&lt;/p&gt;

&lt;p&gt;Worse was &lt;code&gt;hardware&lt;/code&gt;. It used the exit status of &lt;code&gt;journalctl … | grep …&lt;/code&gt; as its&lt;br&gt;
&lt;em&gt;readability&lt;/em&gt; gate. But &lt;code&gt;grep&lt;/code&gt; exits &lt;code&gt;1&lt;/code&gt; when there is &lt;strong&gt;nothing to match&lt;/strong&gt; —&lt;br&gt;
so "no hardware errors" (good news) was read as "I could not read the log", and&lt;br&gt;
the check stayed silent on a healthy machine instead of saying "No hardware&lt;br&gt;
errors logged". A benign EDAC banner made &lt;code&gt;grep&lt;/code&gt; exit &lt;code&gt;0&lt;/code&gt; on my own box, which&lt;br&gt;
hid the bug for weeks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; never gate readability on a pipeline's status. Take the raw output&lt;br&gt;
and decide in code.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. "Nothing found" and "the command is missing" are not the same
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Minimal Debian, Fedora and Ubuntu images do not ship &lt;code&gt;ip&lt;/code&gt; (iproute2). The
probe's &lt;em&gt;failure&lt;/em&gt; was read as an empty route table — a medium
&lt;strong&gt;"No default network route"&lt;/strong&gt; on a machine with a perfectly good route.&lt;/li&gt;
&lt;li&gt;Minimal openSUSE has no &lt;code&gt;awk&lt;/code&gt;. &lt;code&gt;zypper … | awk&lt;/code&gt; produced nothing, so
Tumbleweed reported &lt;strong&gt;"System is up to date"&lt;/strong&gt; with thirteen lines of updates
on screen.&lt;/li&gt;
&lt;li&gt;The engine's own &lt;code&gt;run()&lt;/code&gt; only flagged a command as &lt;em&gt;missing&lt;/em&gt; when the &lt;strong&gt;shell
itself&lt;/strong&gt; was absent — which never happens. A missing tool exits &lt;code&gt;127&lt;/code&gt;
&lt;em&gt;through&lt;/em&gt; the shell. So every check that gated a "could not check" skip on
&lt;code&gt;missing&lt;/code&gt; stayed silent, and a minimal image scored as if those checks had
passed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; distinguish &lt;em&gt;"the tool answered"&lt;/em&gt; from &lt;em&gt;"the tool isn't there."&lt;/em&gt;&lt;br&gt;
Exit &lt;code&gt;127&lt;/code&gt; is the tell.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Success messages contain the words you search for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pacman -Dk&lt;/code&gt; prints &lt;strong&gt;"No database errors have been found!"&lt;/strong&gt; — a bare
&lt;code&gt;error&lt;/code&gt; match flagged a clean Arch system as having broken packages.&lt;/li&gt;
&lt;li&gt;The EDAC driver's startup line is &lt;strong&gt;"EDAC ie31200: No ECC support"&lt;/strong&gt; — the
check matched it as an ECC problem. It was the &lt;em&gt;top item&lt;/em&gt; in the report and
cost 9 points.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mce: CPU supports N MCE banks&lt;/code&gt; is printed once per CPU at boot on every
Intel machine. A bare &lt;code&gt;mce&lt;/code&gt; match read it as a machine-check exception.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; a keyword match is not a diagnostic. Require an actual event, and&lt;br&gt;
explicitly reject the routine preamble lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Kernel interfaces are not namespaced
&lt;/h2&gt;

&lt;p&gt;Inside a 256 MB container, &lt;code&gt;free -b&lt;/code&gt; reported the &lt;strong&gt;host's&lt;/strong&gt; 15 GB.&lt;br&gt;
&lt;code&gt;/proc/loadavg&lt;/code&gt; is the host's load average while &lt;code&gt;nproc&lt;/code&gt; reports the&lt;br&gt;
container's CPU count — and the ratio between them invented an &lt;em&gt;overloaded&lt;/em&gt;&lt;br&gt;
system out of an idle container. (All five test images reported it.) &lt;code&gt;lsblk&lt;/code&gt;&lt;br&gt;
and &lt;code&gt;/proc/swaps&lt;/code&gt; are not namespaced either, so the container was told about&lt;br&gt;
the host's disks and swap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; detect the container and say why you are skipping. That is a&lt;br&gt;
limitation of the environment, not a verdict about the host.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Sometimes the bug is you
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;fuser&lt;/code&gt;-based lock probe ran next to Linux Doctor's &lt;em&gt;own&lt;/em&gt; &lt;code&gt;apt-get check&lt;/code&gt;. So&lt;br&gt;
it found the tool itself holding the dpkg lock — and told the user to wait for,&lt;br&gt;
or kill, a process that &lt;strong&gt;was the tool&lt;/strong&gt;. It only happened when run as root, so&lt;br&gt;
almost nobody would ever have seen it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; when you inspect a global resource, exclude your own process group.&lt;/p&gt;

&lt;h2&gt;
  
  
  The wrong direction of wrong
&lt;/h2&gt;

&lt;p&gt;The dangerous bug is not a false alarm. It is a false &lt;em&gt;all-clear&lt;/em&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A fresh image that never ran &lt;code&gt;apt update&lt;/code&gt; answers &lt;strong&gt;"0 upgraded" with exit
0&lt;/strong&gt; — which read as "up to date". That is not being up to date. The check now
says it &lt;em&gt;cannot tell&lt;/em&gt; and points at &lt;code&gt;apt update&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;apk info -u&lt;/code&gt; is not a valid command at all (it exits &lt;code&gt;1&lt;/code&gt; with
&lt;code&gt;unrecognized option 'u'&lt;/code&gt;), so Alpine said &lt;strong&gt;nothing&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Void had no update branch, so a machine with &lt;strong&gt;54 pending updates&lt;/strong&gt; was
skipped and scored as current.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;flatpak remote-ls --updates&lt;/code&gt; prints a column table whose fields contain no
&lt;code&gt;/&lt;/code&gt;, which is exactly what the count looked for → "apps are up to date" with
updates pending.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; prefer &lt;em&gt;"unknown"&lt;/em&gt; to a confident lie. A check that says "I could&lt;br&gt;
not determine this, and here is the package that would let me" is worth more&lt;br&gt;
than one that silently scores a broken machine as healthy.&lt;/p&gt;

&lt;h2&gt;
  
  
  And sometimes it answered a different question than the one asked
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;processes&lt;/code&gt; warned whenever one app used more than 20% of &lt;em&gt;total&lt;/em&gt; RAM,
ignoring what was actually free — so a 15 GB box with 9.4 GB free got a
medium warning about a browser.&lt;/li&gt;
&lt;li&gt;The same check listed a browser &lt;strong&gt;once per process&lt;/strong&gt; (its memory is spread
across many processes sharing one binary) and reported the largest single
process as "the app".&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;timers&lt;/code&gt; called &lt;code&gt;dnf-makecache.timer&lt;/code&gt; a broken schedule. It is enabled on an
immutable system and &lt;strong&gt;can never run&lt;/strong&gt; — its start condition is unmet by
design, and &lt;code&gt;systemctl status&lt;/code&gt; says so.&lt;/li&gt;
&lt;li&gt;The KDE lock screen logs &lt;code&gt;Authentication attempt too soon&lt;/code&gt; every time you
retype a wrong password quickly. A healthy desktop got &lt;strong&gt;"6 recognized
errors"&lt;/strong&gt; and lost 8 points. The same string from &lt;code&gt;sshd&lt;/code&gt; &lt;em&gt;is&lt;/em&gt; worth seeing —
only the screen locker's copy is noise.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; "the number is real" is not the same as "the number means what you&lt;br&gt;
think it means."&lt;/p&gt;

&lt;h2&gt;
  
  
  How these are caught now
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;clean-image gate&lt;/strong&gt; runs the engine inside Fedora, Debian, Ubuntu, Alpine
and Arch containers, and fails when an unexplained high or medium finding
appears.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recorded fixtures&lt;/strong&gt; from real machines replay through the same pipeline,
and every high/medium finding they produce needs a written reason.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;severity rubric&lt;/strong&gt; and a finding-code registry so severity cannot drift
silently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No fix lands without a regression test that fails first.&lt;/strong&gt; A wrong result
is reproduced before it is changed.&lt;/li&gt;
&lt;li&gt;The whole list is public, in both directions:
&lt;a href="https://github.com/7sh1d0w7x/linux-doctor/blob/main/docs/limitations.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/limitations.md&lt;/code&gt;&lt;/a&gt;
names every false positive and false negative it has shipped, with the test
that guards each one.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why tell on yourself
&lt;/h2&gt;

&lt;p&gt;The entire value of a diagnostic is trust — and trust is built by being&lt;br&gt;
explicit about where you might be wrong, not by never being wrong. A tool that&lt;br&gt;
admits &lt;em&gt;"I could not check this; here is the package that fixes it"&lt;/em&gt; is far&lt;br&gt;
more useful than one that quietly scores a minimal image as perfect.&lt;/p&gt;

&lt;p&gt;Linux Doctor is read-only by construction: it prints the fix and never runs it.&lt;br&gt;
One engine sits behind a CLI, a web dashboard and a desktop app — and the&lt;br&gt;
honesty document ships with it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://github.com/7sh1d0w7x/linux-doctor" rel="noopener noreferrer"&gt;https://github.com/7sh1d0w7x/linux-doctor&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you run it and it gets something wrong, that is the most useful bug report&lt;br&gt;
the project can get — and it comes with a replayable fixture.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>bash</category>
      <category>opensource</category>
      <category>debugging</category>
    </item>
  </channel>
</rss>
