<?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: Xiantao Cai</title>
    <description>The latest articles on DEV Community by Xiantao Cai (@cxtao).</description>
    <link>https://dev.to/cxtao</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%2F1205725%2F792a246e-d4df-4789-84be-7594ad2cdbed.png</url>
      <title>DEV Community: Xiantao Cai</title>
      <link>https://dev.to/cxtao</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cxtao"/>
    <language>en</language>
    <item>
      <title>A container restarted 39,352 times. Every exit code was 0.</title>
      <dc:creator>Xiantao Cai</dc:creator>
      <pubDate>Sat, 19 Sep 2026 07:05:01 +0000</pubDate>
      <link>https://dev.to/cxtao/a-container-restarted-39352-times-every-exit-code-was-0-2g6i</link>
      <guid>https://dev.to/cxtao/a-container-restarted-39352-times-every-exit-code-was-0-2g6i</guid>
      <description>&lt;p&gt;A container on one of my servers had restarted &lt;strong&gt;39,352 times&lt;/strong&gt;. Every single exit code was &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That number is why nobody noticed. Monitoring watches for crashes, and this was not a crash. The process started, found nothing to do, exited &lt;em&gt;successfully&lt;/em&gt; — and &lt;code&gt;restart: unless-stopped&lt;/code&gt; dutifully brought it back. About 39,000 times.&lt;/p&gt;

&lt;p&gt;This turned into one of those debugging sessions where every layer is behaving "correctly" and the system as a whole is broken. Here's the walk-through, and the one metric I now trust more than exit codes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The successful infinite loop
&lt;/h2&gt;

&lt;p&gt;The service was an MCP server running in &lt;strong&gt;stdio mode&lt;/strong&gt;. In stdio mode the process talks over standard input/output — it reads requests from &lt;code&gt;stdin&lt;/code&gt; and writes responses to &lt;code&gt;stdout&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's the compose block, simplified:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mcp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my/mcp-server&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="c1"&gt;# note what's NOT here:&lt;/span&gt;
    &lt;span class="c1"&gt;# stdin_open: true&lt;/span&gt;
    &lt;span class="c1"&gt;# tty: true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without &lt;code&gt;stdin_open&lt;/code&gt; (the compose equivalent of &lt;code&gt;docker run -i&lt;/code&gt;), the container gets &lt;strong&gt;no open standard input&lt;/strong&gt;. So the MCP server boots, reads from &lt;code&gt;stdin&lt;/code&gt;, and immediately hits &lt;strong&gt;EOF&lt;/strong&gt;. There's nothing to read and never will be. It does the correct thing on EOF: it shuts down cleanly and exits &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;restart: unless-stopped&lt;/code&gt; sees a stopped container and restarts it. The new process boots, reads &lt;code&gt;stdin&lt;/code&gt;, hits EOF, exits &lt;code&gt;0&lt;/code&gt;. Restart. Boot. EOF. Exit &lt;code&gt;0&lt;/code&gt;. Restart.&lt;/p&gt;

&lt;p&gt;Every individual decision in that loop is right. The process &lt;em&gt;should&lt;/em&gt; exit when its input stream closes. The restart policy &lt;em&gt;should&lt;/em&gt; revive a service that stopped without being told to. Compose it together and you get a clean, successful, infinite loop that a crash-based alert will never fire on.&lt;/p&gt;

&lt;p&gt;The fix is one line — give it a stdin to hold open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;stdin_open&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;tty&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the fix isn't the interesting part. The interesting part is why it stayed invisible for 39,352 iterations.&lt;/p&gt;

&lt;h2&gt;
  
  
  The health signal was inverted in &lt;em&gt;both&lt;/em&gt; directions
&lt;/h2&gt;

&lt;p&gt;While I was in there, I looked at the other containers on the same box. Two of them had been marked &lt;code&gt;unhealthy&lt;/code&gt; for &lt;strong&gt;four weeks&lt;/strong&gt;. Both were completely fine the whole time.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Health check #1&lt;/strong&gt; probed a port that nothing in the container listens on. The service was up; the check was pointed at the wrong door.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health check #2&lt;/strong&gt; connected to &lt;code&gt;localhost&lt;/code&gt; from a Node 18 process. Since Node 17, Node no longer reorders DNS results, so &lt;code&gt;localhost&lt;/code&gt; resolves to &lt;strong&gt;IPv6 &lt;code&gt;::1&lt;/code&gt; first&lt;/strong&gt;. The service bound &lt;strong&gt;IPv4 only&lt;/strong&gt;. Result: &lt;code&gt;ECONNREFUSED&lt;/code&gt;, forever — against a service that was answering fine on &lt;code&gt;127.0.0.1&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Node 18+: this can resolve to ::1 and miss an IPv4-only server&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:3000/health&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Force IPv4 if that's what your service binds:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://127.0.0.1:3000/health&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So on one machine, at the same time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Two containers screaming &lt;code&gt;unhealthy&lt;/code&gt;&lt;/strong&gt; that were serving traffic perfectly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One container reporting &lt;code&gt;exit 0&lt;/code&gt;&lt;/strong&gt; that was broken and had been for tens of thousands of restarts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The health signal wasn't just wrong. It was inverted in both directions at once. If I'd trusted it, I'd have "fixed" the two healthy ones and never looked at the broken one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually changed
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;stdin_open: true&lt;/code&gt; + &lt;code&gt;tty: true&lt;/code&gt; for the stdio service.&lt;/li&gt;
&lt;li&gt;Health check #1 repointed at a port the service actually listens on.&lt;/li&gt;
&lt;li&gt;Health check #2 switched to &lt;code&gt;127.0.0.1&lt;/code&gt; (and I now treat "works in &lt;code&gt;docker run&lt;/code&gt;, fails in a health check" as an IPv4/IPv6 smell).&lt;/li&gt;
&lt;li&gt;An alert on &lt;strong&gt;restart count velocity&lt;/strong&gt; — restarts per hour — independent of exit code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last one is the takeaway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exit code 0 is not a success signal
&lt;/h2&gt;

&lt;p&gt;Exit &lt;code&gt;0&lt;/code&gt; only tells you the process &lt;em&gt;agreed to leave&lt;/em&gt;. It does not tell you it &lt;em&gt;should have&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Restart count, by contrast, is honest. A number that climbs into the thousands means something is wrong regardless of how politely each instance exited. It's a better smoke alarm than exit code, and a far better one than a health check that can be pointed at the wrong port or the wrong IP stack.&lt;/p&gt;

&lt;p&gt;Cheap checks worth stealing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Alert on &lt;strong&gt;restart count / restart velocity&lt;/strong&gt;, not just non-zero exits.&lt;/li&gt;
&lt;li&gt;A health check that has never once reported &lt;code&gt;unhealthy&lt;/code&gt; is probably not testing anything.&lt;/li&gt;
&lt;li&gt;When something works under &lt;code&gt;docker run&lt;/code&gt; but a health check says it's down, suspect &lt;strong&gt;&lt;code&gt;localhost&lt;/code&gt; → IPv6&lt;/strong&gt; before you suspect your code.&lt;/li&gt;
&lt;li&gt;A stdio service with no &lt;code&gt;stdin_open&lt;/code&gt; will exit cleanly on boot. "Clean" is not the same as "correct."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What's the longest a silent restart loop has run in your infrastructure before anyone noticed?&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>debugging</category>
      <category>node</category>
    </item>
    <item>
      <title>Four versions of one translate() function</title>
      <dc:creator>Xiantao Cai</dc:creator>
      <pubDate>Sun, 06 Sep 2026 14:47:54 +0000</pubDate>
      <link>https://dev.to/cxtao/four-versions-of-one-translate-function-4jea</link>
      <guid>https://dev.to/cxtao/four-versions-of-one-translate-function-4jea</guid>
      <description>&lt;p&gt;I build Connexa, a chat product for cross-language teams: you write Chinese, your teammate reads English.&lt;/p&gt;

&lt;p&gt;I assumed translation was one function. The file now holds four versions of it, and each one exists because the previous one dropped something.&lt;/p&gt;

&lt;h2&gt;
  
  
  v1: ten lines
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetLang&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;12000&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12000&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;…`&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;([{&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Translate the following text into &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;targetLang&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Return only the translation, no explanations:\n\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="p"&gt;}]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output was correct. The problem is that it translates &lt;strong&gt;sentences&lt;/strong&gt;, not &lt;strong&gt;conversations&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Example: &lt;code&gt;这个我看一下&lt;/code&gt; → &lt;code&gt;I'll take a look at this.&lt;/code&gt; Literally accurate. In Chinese that line is sometimes a polite refusal. The words arrived, the intent stayed behind.&lt;/p&gt;

&lt;h2&gt;
  
  
  v2: make tone a field, not a hope
&lt;/h2&gt;

&lt;p&gt;The tempting fix is to ask for a "more natural" translation. That isn't controllable and isn't testable.&lt;/p&gt;

&lt;p&gt;Instead I made tone a &lt;strong&gt;schema-constrained field&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;generationConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;topP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;maxOutputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2048&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;responseMimeType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;responseSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OBJECT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;translated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;STRING&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;tone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;STRING&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;translated&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with a system instruction pinning the vocabulary:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tone should be a short lowercase label such as casual, formal, urgent, direct, friendly, sarcastic, frustrated, or neutral.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Note &lt;code&gt;required: ['translated']&lt;/code&gt; — tone is optional on purpose. That matters in v5 below.&lt;/p&gt;

&lt;p&gt;Tone went from something I hoped the model conveyed into something I can render in the UI, store, and reason about.&lt;/p&gt;

&lt;h2&gt;
  
  
  v3: context, and the bug it introduced
&lt;/h2&gt;

&lt;p&gt;A single message can't be translated without what came before it, so I appended the last 10 lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contextLines&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;`Translate the following message into &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;targetLang&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;glossaryText&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`Team glossary (use these terms):\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;glossaryText&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`Conversation context (tone/reference only):\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;`Message to translate:\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Return ONLY the translation.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bug: &lt;strong&gt;the model helpfully translates the context too.&lt;/strong&gt; You ask for one translated line and get a translated transcript.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;(tone/reference only)&lt;/code&gt; is not a comment for human readers. It is the constraint. Remove that parenthetical and the return shape changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  v4: glossary, and the verb you use to introduce it
&lt;/h2&gt;

&lt;p&gt;If the same term renders as A in one message and B in the next, the conversation quietly desyncs. So: a per-team glossary, flattened line by line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;glossaryText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;glossary&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;glossary&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;glossary&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&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="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; → &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The part worth noting is the wording change between versions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- `Team glossary (use these terms):\n${glossaryText}`
&lt;/span&gt;&lt;span class="gi"&gt;+ `Team glossary (must follow):\n${glossaryText}`
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I did not A/B this. I am not claiming a measured lift. I changed it because "use these terms" phrases a constraint as a suggestion, and I had no reason to keep it phrased that way.&lt;/p&gt;

&lt;h2&gt;
  
  
  v5 is the fallback, and its direction matters
&lt;/h2&gt;

&lt;p&gt;Structured output fails sometimes. The question is what you drop when it does.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extractText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateRaw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;translated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;translated&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;fallbackText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;tone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;tone&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`translateWithTone fallback: &lt;/span&gt;&lt;span class="p"&gt;${(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;translated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;([{&lt;/span&gt;
      &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s1"&gt;Return only the translated text.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;}]),&lt;/span&gt;
    &lt;span class="na"&gt;tone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drop to a plain-text request, return &lt;code&gt;tone: null&lt;/code&gt;. In a chat product, translation is the primary path and tone is an enhancement. Degrade the enhancement, never the path. That is why &lt;code&gt;tone&lt;/code&gt; was optional in the schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd keep
&lt;/h2&gt;

&lt;p&gt;Translation is not converting language A into B. It is reconstructing A's &lt;strong&gt;intent&lt;/strong&gt; in B.&lt;/p&gt;

&lt;p&gt;The literal layer is the easiest part to move, which is why it gets finished first — and exactly why it is easy to believe you are done. Tone, prior context, and term consistency do not show up in the output on their own. Each one has to be added back deliberately, and each one costs you a prompt section and a failure mode.&lt;/p&gt;

&lt;p&gt;What is the last output you shipped that was correct and still wrong?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Shorter versions of this appeared on &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7502228495708798976/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; and on my Chinese-language channels.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>typescript</category>
      <category>llm</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Eastern Philosophy for Developers: Using Wang Yangming to Combat Burnout</title>
      <dc:creator>Xiantao Cai</dc:creator>
      <pubDate>Sun, 19 Jul 2026 04:53:44 +0000</pubDate>
      <link>https://dev.to/cxtao/eastern-philosophy-for-developers-using-wang-yangming-to-combat-burnout-o3j</link>
      <guid>https://dev.to/cxtao/eastern-philosophy-for-developers-using-wang-yangming-to-combat-burnout-o3j</guid>
      <description>&lt;p&gt;As developers, we are constantly bombarded with noise. There is always a new framework to learn (Rust, Go, Next.js), a new architectural pattern to master (Microservices, K8s), and endless workplace politics to navigate.&lt;/p&gt;

&lt;p&gt;The result is often "mental friction" (精神内耗) or severe burnout. We get stuck in our own heads, analyzing every problem until we are paralyzed by anxiety.&lt;/p&gt;

&lt;p&gt;When I faced this, I didn't turn to typical Silicon Valley productivity hacks. Instead, I found my solution in the 16th-century Eastern philosophy of &lt;strong&gt;Wang Yangming&lt;/strong&gt; (王阳明心学). Here is how I compiled his ancient philosophy into a modern developer's anti-burnout script.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. "Nothing Outside the Mind" (心外无物): Your Mental Firewall
&lt;/h2&gt;

&lt;p&gt;Wang Yangming taught that all perceptions and emotions are generated internally. If something doesn't directly relate to your core goals, it effectively doesn't exist for you. &lt;/p&gt;

&lt;p&gt;In a workplace context, this is the ultimate firewall. When you are worried about an upcoming performance review, or what a toxic coworker thinks of you, remember: &lt;em&gt;you are generating that anxiety, not them.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Patch:&lt;/strong&gt; Stop trying to change how others view you. Prejudices in the minds of others are like mountains—you cannot move them. You are only working for yourself. Configure your mental firewall to block out any external "noise" that doesn't align with your personal roadmap. If a situation doesn't serve your goals, drop the packet.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. "Unity of Knowledge and Action" (知行合一): The Ultimate Anti-Loop Mechanism
&lt;/h2&gt;

&lt;p&gt;Many developers get stuck in a "while(true)" loop of overthinking. We analyze architecture, debate trade-offs, and worry about edge cases without writing a single line of code.&lt;/p&gt;

&lt;p&gt;Wang Yangming argued that true knowledge only comes from &lt;em&gt;action&lt;/em&gt; and &lt;em&gt;direct perception&lt;/em&gt; (the five senses), not from cognitive habits or endless theory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Patch:&lt;/strong&gt; When you feel your brain entering a state of contradiction or mental friction, &lt;strong&gt;execute an immediate physical interrupt (Ctrl+C).&lt;/strong&gt; Stop thinking. Shift your awareness entirely to your five senses. Focus on your breathing. Look at the screen from a third-person perspective. By anchoring yourself to physical reality and taking immediate, small actions, you break the infinite loop of overthinking.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Master Your Own Source Code
&lt;/h2&gt;

&lt;p&gt;Philosophy isn't just about reading quotes; it's a utility tool. Just like you choose the right tech stack for a specific project, you must choose the right mental framework for a specific problem.&lt;/p&gt;

&lt;p&gt;The core utility of Wang Yangming's philosophy is its ability to &lt;em&gt;reduce noise and increase focus&lt;/em&gt;. It teaches you to identify your true desires, overcome your biases (bugs in your cognitive habits), and establish a new, objective understanding of the world through direct action.&lt;/p&gt;

&lt;p&gt;The next time you feel overwhelmed by the tech world, try running this ancient algorithm. Turn off the external noise, trust your five senses, and hit compile.&lt;/p&gt;

</description>
      <category>mentalhealth</category>
      <category>productivity</category>
      <category>programming</category>
      <category>career</category>
    </item>
  </channel>
</rss>
