<?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: tinyproof</title>
    <description>The latest articles on DEV Community by tinyproof (@tinyproof).</description>
    <link>https://dev.to/tinyproof</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%2F4084984%2Ff8a9e7ec-9f9b-4670-b290-c03a130425c8.png</url>
      <title>DEV Community: tinyproof</title>
      <link>https://dev.to/tinyproof</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tinyproof"/>
    <language>en</language>
    <item>
      <title>I fixed one silent failure and stopped looking. A review found three more</title>
      <dc:creator>tinyproof</dc:creator>
      <pubDate>Wed, 19 Aug 2026 13:37:22 +0000</pubDate>
      <link>https://dev.to/tinyproof/i-fixed-one-silent-failure-and-stopped-looking-a-review-found-three-more-3hf1</link>
      <guid>https://dev.to/tinyproof/i-fixed-one-silent-failure-and-stopped-looking-a-review-found-three-more-3hf1</guid>
      <description>&lt;p&gt;I had a scheduled script that ran fine by hand and produced nothing on a schedule. Exit code 0, no error in the log, an output file created and empty.&lt;/p&gt;

&lt;p&gt;The script calls a CLI tool that reads credentials from the macOS keychain. Run it in my terminal: works. Run it from &lt;code&gt;launchd&lt;/code&gt;: &lt;code&gt;Not logged in · Please run /login&lt;/code&gt;. The credentials had not expired.&lt;/p&gt;

&lt;p&gt;This post is about the fix, and then about the three other places the same shape was hiding — which I did not find. Someone reviewing my code did.&lt;/p&gt;

&lt;h2&gt;
  
  
  The smallest difference I could reproduce
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;launchd&lt;/code&gt; hands a process a much smaller environment than a login shell does. No &lt;code&gt;.zprofile&lt;/code&gt;, no &lt;code&gt;.zshrc&lt;/code&gt;, so nothing those files set is present.&lt;/p&gt;

&lt;p&gt;I bisected down to a single variable. Both runs below are identical except for one thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# A — with USER&lt;/span&gt;
&lt;span class="nb"&gt;env&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"myname"&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$P&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; mytool &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"say hi"&lt;/span&gt;
&lt;span class="c"&gt;# → responds normally&lt;/span&gt;

&lt;span class="c"&gt;# B — same command, USER removed&lt;/span&gt;
&lt;span class="nb"&gt;env&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$P&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; mytool &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"say hi"&lt;/span&gt;
&lt;span class="c"&gt;# → Not logged in · Please run /login&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the smallest difference I could reproduce. &lt;code&gt;PATH&lt;/code&gt; was already correct in both, &lt;code&gt;HOME&lt;/code&gt; was set in both. Remove &lt;code&gt;USER&lt;/code&gt; and the tool decides you are not logged in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I do not know why the tool needs &lt;code&gt;USER&lt;/code&gt;.&lt;/strong&gt; I did not read its source. It might use it to look up the keychain entry, it might be something else. What I can tell you is the reproduction above, because I ran it both ways.&lt;/p&gt;

&lt;p&gt;The fix is to set the environment explicitly instead of hoping the scheduler provides it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/Users/myname"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"myname"&lt;/span&gt;      &lt;span class="c"&gt;# without this the CLI reports "not logged in"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The part that mattered more
&lt;/h2&gt;

&lt;p&gt;The login failure &lt;strong&gt;exited 0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;My script checked the exit code to decide whether the run succeeded. The run had done nothing, and the exit code said fine. So the real fix is not the &lt;code&gt;export&lt;/code&gt; line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;run_job&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TOOL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$prompt&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$out&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;&amp;amp;1

  &lt;span class="c"&gt;# exit code is not enough — the login failure exits 0&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qE&lt;/span&gt; &lt;span class="s2"&gt;"Not logged in|Please run /login|Invalid API key"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$out&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;record_incident &lt;span class="s2"&gt;"auth failed"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;return &lt;/span&gt;1
  &lt;span class="k"&gt;fi&lt;/span&gt;
  &lt;span class="c"&gt;# neither is "the file exists" — check it has content&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;stat&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt;%z &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$out&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-lt&lt;/span&gt; 50 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;record_incident &lt;span class="s2"&gt;"no output"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;return &lt;/span&gt;1
  &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What I actually verified, so you know how far to trust it: I copied that logic into a standalone script and fed it five files. Three contained one of the error strings each, padded past 50 bytes so the size check could not be what caught them. One was empty. One was 201 bytes of ordinary output. The first four are caught, the last passes. That exercises the guard. It is not a test of the original failure — I did not break a live login to reproduce it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three I did not find
&lt;/h2&gt;

&lt;p&gt;I wrote that guard and moved on. Then someone reviewed the rest of my code and came back with three more instances of the same shape. All three are theirs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A linter I wrote&lt;/strong&gt; printed an error for an unreadable input file and exited 0. Tested with &lt;code&gt;/dev/null&lt;/code&gt;: &lt;code&gt;0 workflow(s) checked&lt;/code&gt;, exit 0. CI would read that as "nothing wrong".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A checker I wrote&lt;/strong&gt; reported "0 segments, 0 issues" on an empty file — the same exit code as a genuinely clean file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A pipeline swallowing an exit status&lt;/strong&gt;, which showed up in two places I had been counting as separate bugs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The pipeline one, and the correction I owe
&lt;/h3&gt;

&lt;p&gt;I had this filed as two findings: "&lt;code&gt;timeout&lt;/code&gt; is missing on macOS and fails silently" and "I read an exit status through a pipe". The first half of that was wrong.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;timeout &lt;/span&gt;5 &lt;span class="nb"&gt;echo &lt;/span&gt;hi
zsh: &lt;span class="nb"&gt;command &lt;/span&gt;not found: &lt;span class="nb"&gt;timeout&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt;
127                      &lt;span class="c"&gt;# ← correct. A missing command does report failure.&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;timeout &lt;/span&gt;5 &lt;span class="nb"&gt;echo &lt;/span&gt;hi | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;
zsh: &lt;span class="nb"&gt;command &lt;/span&gt;not found: &lt;span class="nb"&gt;timeout&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt;
0                        &lt;span class="c"&gt;# ← the pipeline reports head's status, not timeout's&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A missing command exits 127 on its own. What swallowed it was the pipe: by default a shell pipeline reports the exit status of the &lt;strong&gt;last&lt;/strong&gt; command. &lt;code&gt;timeout&lt;/code&gt; was not a silent failure, it was the thing that made the pipe's behaviour visible. Two findings, one bug.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;set -o pipefail&lt;/code&gt; makes the pipeline return non-zero if any command in it fails — same commands as above, 0 becomes 127. It is worth turning on where you are branching on a pipeline's status, which in an unattended script is usually.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I check now
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;An unreadable or empty input never exits 0. It gets its own code, distinct from "scanned and found nothing".&lt;/li&gt;
&lt;li&gt;Success is not "exit code 0". It is "the thing I expected to be produced exists and has content".&lt;/li&gt;
&lt;li&gt;On macOS, check that &lt;code&gt;timeout&lt;/code&gt; exists before a script depends on it. Same for anything else assumed to be standard.&lt;/li&gt;
&lt;li&gt;If a status matters, do not read it through a pipe without &lt;code&gt;pipefail&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of those is the part worth passing on. I found one instance, fixed it, and did not go looking for others. The three above came from someone reading the same code who had not just solved it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was written with the help of AI. The incident, the commands and the outputs are real and from my own machine; the draft was AI-written, then checked against the original logs and corrected — including the &lt;code&gt;timeout&lt;/code&gt; explanation above, which the first draft got wrong.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>macos</category>
      <category>automation</category>
      <category>cli</category>
      <category>debugging</category>
    </item>
  </channel>
</rss>
