<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Troubleshooting on Margrop Blog</title>
    <link>https://blog.margrop.net/en/tag/troubleshooting/</link>
    <description>Recent content in Troubleshooting on Margrop Blog</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-US</language>
    <lastBuildDate>Sun, 19 Jul 2026 22:00:00 +0800</lastBuildDate>
    <atom:link href="https://blog.margrop.net/en/tag/troubleshooting/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Stop Installing Another PC Cleaner: What Desktop Software Can an AI Agent Actually Replace?</title>
      <link>https://blog.margrop.net/en/post/ai-agent-replace-desktop-software/</link>
      <pubDate>Sun, 19 Jul 2026 22:00:00 +0800</pubDate>
      <guid>https://blog.margrop.net/en/post/ai-agent-replace-desktop-software/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;The short answer&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;An AI Agent can replace many desktop utilities whose main job is clicking the same buttons over and over. It does not turn every application into a chat box, and it is not a magical “computer doctor.” Its real advantage is orchestration: it can inspect the machine, understand the goal, combine built-in tools, execute a sequence, and explain the evidence.&lt;/p&gt;&#xA;&lt;p&gt;Across five categories—file conversion, junk scanning, performance optimization, configuration changes, and computer repair—the replacement level is different. File conversion and reporting are highly automatable. Performance work and configuration changes are semi-automatic. Hardware repair remains a human job, with the Agent acting as a diagnostic assistant.&lt;/p&gt;&#xA;&lt;p&gt;The safety rule for every script in this article is simple: &lt;strong&gt;read first, write second; back up first, change second; verify before claiming success.&lt;/strong&gt; All three scripts are local-only and start in report mode. Destructive actions require an explicit flag and a second confirmation.&lt;/p&gt;&#xA;&lt;/blockquote&gt;</description>
    </item>
    <item>
      <title>Docker Container Crash Loop After v3.7.0? You&#39;re Missing One Word: serve</title>
      <link>https://blog.margrop.net/en/post/siyuan-v370-docker-restart-loop-fix/</link>
      <pubDate>Thu, 02 Jul 2026 19:30:00 +0800</pubDate>
      <guid>https://blog.margrop.net/en/post/siyuan-v370-docker-restart-loop-fix/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;After upgrading Siyuan (SiYuan Note) from v3.6.x to v3.7.0, the Docker container entered an infinite restart loop. &lt;code&gt;docker logs&lt;/code&gt; showed &lt;code&gt;Error: unknown flag: --accessAuthCode&lt;/code&gt;. The root cause: v3.7.0 introduced a CLI subcommand architecture. The old top-level flags now require a &lt;code&gt;serve&lt;/code&gt; subcommand. The fix is adding one word — &lt;code&gt;&#39;serve&#39;&lt;/code&gt; — to the beginning of the &lt;code&gt;command&lt;/code&gt; array in docker-compose.yml. Seven characters. From crash loop to running.&lt;/p&gt;&#xA;&lt;p&gt;This article covers the complete troubleshooting process, one-command fix scripts for Windows 11, Ubuntu 26.04, and macOS 26, plus both manual and AI Agent approaches. All scripts use only Docker CLI and SSH — no third-party services required.&lt;/p&gt;&#xA;&lt;/blockquote&gt;</description>
    </item>
    <item>
      <title>One sentence let Claude upgrade Ubuntu 22.04 to 24.04: a near-zero-screen-time cross-LTS run</title>
      <link>https://blog.margrop.net/en/post/upgrade-ubuntu-via-agent/</link>
      <pubDate>Fri, 19 Jun 2026 10:58:00 +0800</pubDate>
      <guid>https://blog.margrop.net/en/post/upgrade-ubuntu-via-agent/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;A remote home-lab box had been sitting on Ubuntu 22.04.4 LTS (jammy, kernel 5.15) for a long time. After one sentence — &amp;ldquo;upgrade the box at 192.168.103.182 to Ubuntu 24.04&amp;rdquo; — Claude opened a tmux session to babysit the run, brought up a fallback sshd on port 1022 so a mid-upgrade sshd restart would not strand me, then drove &lt;code&gt;do-release-upgrade -f DistUpgradeViewNonInteractive&lt;/code&gt; end to end. Twenty-five minutes later the host came back: kernel 6.8.0-124, every service still listening.&lt;/p&gt;&#xA;&lt;p&gt;This is not a tutorial on &lt;code&gt;do-release-upgrade&lt;/code&gt;; the Ubuntu docs already cover that. This is about what an Agent does once it gets an SSH handle on a box, why it does each step, and which pitfalls you have to clear out of the way before letting it loose.&lt;/p&gt;&#xA;&lt;/blockquote&gt;</description>
    </item>
    <item>
      <title>Ubuntu shutdown stuck for 90 seconds? A Python asyncio service that won&#39;t honour SIGTERM, and how to fix it</title>
      <link>https://blog.margrop.net/en/post/ubuntu-shutdown-stuck/</link>
      <pubDate>Fri, 19 Jun 2026 10:58:00 +0800</pubDate>
      <guid>https://blog.margrop.net/en/post/ubuntu-shutdown-stuck/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;After upgrading my home proxy box to Ubuntu 24.04, &lt;code&gt;systemctl reboot&lt;/code&gt; stopped being fast: SSH would hang on for &lt;strong&gt;90 seconds&lt;/strong&gt; before the box actually shut down. &lt;code&gt;journalctl&lt;/code&gt; made it obvious: &lt;code&gt;smart-proxy.service: State &#39;stop-sigterm&#39; timed out. Killing.&lt;/code&gt; — systemd sent SIGTERM, waited 90 seconds, nobody reacted, and SIGKILL was the only option left.&lt;/p&gt;&#xA;&lt;p&gt;The root cause is not systemd&amp;rsquo;s fault and not Ubuntu&amp;rsquo;s fault. It is &lt;strong&gt;Python&amp;rsquo;s asyncio runtime not actually ending its child tasks when SIGTERM arrives&lt;/strong&gt; — it is parked on a &lt;code&gt;socket.recv&lt;/code&gt; and never voluntarily looks at the signal queue.&lt;/p&gt;&#xA;&lt;p&gt;The fix has two halves, both required: &lt;strong&gt;(1)&lt;/strong&gt; a systemd drop-in that lowers &lt;code&gt;TimeoutStopSec&lt;/code&gt; from the default 90s down to 20s on the affected Python units; &lt;strong&gt;(2)&lt;/strong&gt; inside the Python service itself, walk &lt;code&gt;asyncio.all_tasks()&lt;/code&gt;, &lt;code&gt;.cancel()&lt;/code&gt; the in-flight connection handlers on SIGTERM, and wrap &lt;code&gt;self.stop()&lt;/code&gt; in &lt;code&gt;asyncio.wait_for(..., timeout=10)&lt;/code&gt; as a safety net. After both halves, the same &lt;code&gt;reboot&lt;/code&gt; drops from 90 seconds to one second.&lt;/p&gt;&#xA;&lt;/blockquote&gt;</description>
    </item>
    <item>
      <title>I dockerized AnythingLLM and the container turned into a wound-up squirrel — restarting forever until I handed it that magic 1000:1000</title>
      <link>https://blog.margrop.net/en/post/anythingllm-docker-deploy/</link>
      <pubDate>Wed, 17 Jun 2026 20:30:00 +0800</pubDate>
      <guid>https://blog.margrop.net/en/post/anythingllm-docker-deploy/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;AnythingLLM, Mintplex Labs&amp;rsquo; &amp;ldquo;stuff-anything-in-a-LLM&amp;rdquo; desktop, ships an official Docker image. In the happy path one &lt;code&gt;docker run&lt;/code&gt; is all you need. But the in-container user &lt;code&gt;anythingllm&lt;/code&gt; is picky: the host directory you bind-mount has to be owned by &lt;code&gt;1000:1000&lt;/code&gt;, otherwise it can&amp;rsquo;t write its own SQLite file, the Prisma migration step on startup crashes, and the container enters a &lt;em&gt;restart-crash-restart&lt;/em&gt; loop. The fix takes under a minute: &lt;code&gt;chown -R 1000:1000 /your/data/dir&lt;/code&gt;, then &lt;code&gt;docker compose up -d&lt;/code&gt; again, and it dutifully listens on &lt;code&gt;3001&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ll also explain why Prisma&amp;rsquo;s &lt;code&gt;file:../storage/anythingllm.db&lt;/code&gt; relative path is a footgun, give you a Portainer-ready stack file, and finish with a Q&amp;amp;A on the most common gotchas.&lt;/p&gt;&#xA;&lt;/blockquote&gt;</description>
    </item>
    <item>
      <title>PhotoPrism Stuck for 5 Minutes on First Boot? A Postmortem on PHOTOPRISM_INIT=intel</title>
      <link>https://blog.margrop.net/en/post/photoprism-init-intel-stuck/</link>
      <pubDate>Sat, 13 Jun 2026 08:30:00 +0800</pubDate>
      <guid>https://blog.margrop.net/en/post/photoprism-init-intel-stuck/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;TL;DR (The Short Version)&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;The &lt;code&gt;PHOTOPRISM_INIT: &amp;quot;intel&amp;quot;&lt;/code&gt; line in PhotoPrism&amp;rsquo;s Plus Docker image is &lt;strong&gt;not&lt;/strong&gt; just a flag to enable Intel hardware acceleration at runtime — it&amp;rsquo;s a one-shot &lt;strong&gt;system-level installer&lt;/strong&gt; that runs &lt;code&gt;apt-get dist-upgrade&lt;/code&gt; against &lt;code&gt;archive.ubuntu.com&lt;/code&gt; and then installs 8 Intel/VA-API packages before the main process is allowed to start. On a fresh container this takes &lt;strong&gt;5–10 minutes&lt;/strong&gt; (sometimes more), during which nothing inside the container listens on port &lt;code&gt;2342&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Meanwhile: &lt;code&gt;docker ps&lt;/code&gt; says &lt;code&gt;Up&lt;/code&gt;, Portainer is green, &lt;code&gt;ss -ltn&lt;/code&gt; on the host shows &lt;code&gt;:2342&lt;/code&gt; LISTEN — but your browser gets &lt;code&gt;ERR_CONNECTION_RESET&lt;/code&gt; / &lt;code&gt;Connection reset by peer&lt;/code&gt;. Because host-side &lt;code&gt;docker-proxy&lt;/code&gt; accepts the connection and then tries to forward to a port inside the container that has no listener, the kernel sends back an RST.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; just remove or empty that one line. If you already pass &lt;code&gt;/dev/dri/renderD128&lt;/code&gt; via &lt;code&gt;devices:&lt;/code&gt;, the host&amp;rsquo;s drivers and userland libraries are perfectly usable from the container — there&amp;rsquo;s nothing to &amp;ldquo;install&amp;rdquo; in there.&lt;/p&gt;&#xA;&lt;/blockquote&gt;</description>
    </item>
    <item>
      <title>The Portainer 500 Error That Wasn&#39;t the YAML — A Two-librespeed_default-Network Story</title>
      <link>https://blog.margrop.net/en/post/portainer-500-duplicate-compose-network-enigma/</link>
      <pubDate>Sat, 13 Jun 2026 08:00:00 +0800</pubDate>
      <guid>https://blog.margrop.net/en/post/portainer-500-duplicate-compose-network-enigma/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;The short version&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;You update a stack in Portainer. The browser slaps you with a red &lt;code&gt;500 Internal Server Error&lt;/code&gt;. You assume the YAML is wrong, fix the indentation, swap the quotes, drop the image tag. You hit Update again. Same 500. And again. And again.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;The real culprit is buried at the deepest level of the HTTP response body: &lt;code&gt;network librespeed_default is ambiguous (2 matches found on name)&lt;/code&gt;.&lt;/strong&gt; Two networks with the same name exist in the same Docker engine—two IDs, two &lt;code&gt;Created&lt;/code&gt; timestamps, two &lt;code&gt;com.docker.compose.config-hash&lt;/code&gt; labels, but a single shared name. Compose asks the engine to look up that name; the engine refuses to pick between them; &lt;code&gt;compose up&lt;/code&gt; fails; Portainer wraps the error as a 500 and returns it to your browser.&lt;/p&gt;&#xA;&lt;p&gt;The fix is embarrassingly simple: &lt;strong&gt;delete one of the two orphan networks&lt;/strong&gt; (&lt;code&gt;docker network rm &amp;lt;id&amp;gt;&lt;/code&gt; or click Remove in Portainer&amp;rsquo;s Networks page), then re-run Update the stack with the exact same content. It just works.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;This post is a real debugging session: a stack update that was supposed to be a 30-second mount-path change turned into a 1-hour investigation. All private details (internal addresses, registry URLs, volume paths, container names, credentials) have been replaced with &lt;code&gt;&amp;lt;PLACEHOLDER&amp;gt;&lt;/code&gt;. Only public source code, official docs, and the error text itself are preserved.&lt;/p&gt;</description>
    </item>
    <item>
      <title>The Docker Container Is Running, the Port Is Dead: A Complete Walkthrough of a Silent Host-Bind Failure</title>
      <link>https://blog.margrop.net/en/post/docker-port-bind-half-silent/</link>
      <pubDate>Sat, 13 Jun 2026 07:30:00 +0800</pubDate>
      <guid>https://blog.margrop.net/en/post/docker-port-bind-half-silent/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;docker ps&lt;/code&gt; says everything is fine. &lt;code&gt;docker inspect&lt;/code&gt;&amp;rsquo;s &lt;code&gt;HostConfig.PortBindings&lt;/code&gt; clearly says &lt;code&gt;203.0.113.14:3001:3000&lt;/code&gt;. But on the host, &lt;strong&gt;no &lt;code&gt;docker-proxy&lt;/code&gt; is listening&lt;/strong&gt;, the iptables &lt;code&gt;nat/DOCKER&lt;/code&gt; chain has &lt;strong&gt;no DNAT rule&lt;/strong&gt;, and &lt;code&gt;NetworkSettings.Networks&lt;/code&gt; and &lt;code&gt;Ports&lt;/code&gt; are both &lt;strong&gt;empty &lt;code&gt;{}&lt;/code&gt;&lt;/strong&gt;. This &amp;ldquo;container alive, port dead&amp;rdquo; state is what happens when libnetwork silently rolls back the endpoint creation because the target interface wasn&amp;rsquo;t up at attach time — and Docker doesn&amp;rsquo;t bother to tell you that the port publish never actually happened. &lt;strong&gt;The fix is a single 30-second command&lt;/strong&gt;: &lt;code&gt;docker network connect &amp;lt;net&amp;gt; &amp;lt;ctr&amp;gt;&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;</description>
    </item>
    <item>
      <title>From 5m14s to 0.6s: How I Rebuilt My Docker Hub Mirror After Hitting Three Walls</title>
      <link>https://blog.margrop.net/en/post/docker-registry-mirror-rebuild-2026/</link>
      <pubDate>Sat, 13 Jun 2026 07:20:00 +0800</pubDate>
      <guid>https://blog.margrop.net/en/post/docker-registry-mirror-rebuild-2026/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;My home lab had a &lt;code&gt;registry:2&lt;/code&gt; pull-through cache fronting Docker Hub for years, and it was fine. Then one day pulling a 5 MB &lt;code&gt;alpine:3.19&lt;/code&gt; took 5 minutes and 14 seconds. This post is the full forensic log of how I traced the slowdown, evaluated 8 candidate mirrors with real measurements (not vibes), and finished with a 5-line bash script on cron that &lt;strong&gt;auto-fails-over&lt;/strong&gt; when the primary mirror goes down. Every number in this article was captured on my own hardware, in my own network, at one specific moment in time.&lt;/p&gt;&#xA;&lt;p&gt;If you also self-host a Docker Hub mirror, or your team runs an internal registry, the Q&amp;amp;A at the bottom will probably save you 3 hours of pain.&lt;/p&gt;&#xA;&lt;/blockquote&gt;</description>
    </item>
    <item>
      <title>OpenClaw TUI Keeps Repeating Itself? A Temporary Patch for Duplicate Thinking and Replies</title>
      <link>https://blog.margrop.net/en/post/openclaw-tui-duplicate-thinking-stream-patch/</link>
      <pubDate>Tue, 02 Jun 2026 10:42:00 +0800</pubDate>
      <guid>https://blog.margrop.net/en/post/openclaw-tui-duplicate-thinking-stream-patch/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;The short version&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;If OpenClaw TUI shows the same thinking block twice and repeats the final answer, do not immediately delete every session or rebuild every model provider. In many OpenAI-compatible setups, the real issue is a streaming compatibility edge case: the upstream stream emits normal &lt;code&gt;delta&lt;/code&gt; chunks and then an extra full &lt;code&gt;message&lt;/code&gt; tail. Some OpenClaw paths may aggregate both, so the TUI and the persisted session both end up with duplicated content.&lt;/p&gt;&#xA;&lt;p&gt;The practical temporary fix is to back up the local OpenClaw installation, add a conservative dedupe guard in the OpenClaw aggregation layer, restart the gateway, and verify with a unique marker that both the visible reply and the session file contain only one copy.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;This is a privacy-safe incident note. All endpoints, model names, tokens, internal addresses, and session identifiers are replaced by placeholders. Use your own installation path and model identifiers when applying the workaround.&lt;/p&gt;</description>
    </item>
    <item>
      <title>VPN Connected, But Internal Hostnames Won&#39;t Resolve? A Complete macOS Routing Table Walkthrough</title>
      <link>https://blog.margrop.net/en/post/macos-routing-table-vpn-troubleshooting/</link>
      <pubDate>Mon, 01 Jun 2026 18:00:00 +0800</pubDate>
      <guid>https://blog.margrop.net/en/post/macos-routing-table-vpn-troubleshooting/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;When two VPNs run side-by-side on macOS (e.g. &lt;code&gt;utun0&lt;/code&gt; and &lt;code&gt;utun15&lt;/code&gt;), an aggregate &lt;code&gt;10.0.0.0/8&lt;/code&gt; route pushed by one of them can silently &amp;ldquo;swallow&amp;rdquo; every address in the 10.x.x.x range — including the one you actually wanted to reach on the other VPN. DNS resolves fine; TCP/ICMP just hangs. &lt;strong&gt;&lt;code&gt;route -n get&lt;/code&gt; is the first knife you should reach for.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;</description>
    </item>
    <item>
      <title>Your Mac App Is Not Broken: Gatekeeper May Just Distrust an Unsigned Tool</title>
      <link>https://blog.margrop.net/en/post/macos-gatekeeper-unsigned-app-fix/</link>
      <pubDate>Mon, 01 Jun 2026 07:35:00 +0800</pubDate>
      <guid>https://blog.margrop.net/en/post/macos-gatekeeper-unsigned-app-fix/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;The short version&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;When a macOS utility refuses to launch after being downloaded, the first conclusion should not be “the app is broken.” A very common cause is a combination of two facts: the app still has the &lt;code&gt;com.apple.quarantine&lt;/code&gt; extended attribute that marks it as downloaded from the internet, and the app bundle does not have a usable signature that Gatekeeper can trust. For first launch, “downloaded from the web + no usable signature” is enough for macOS to block it.&lt;/p&gt;&#xA;&lt;p&gt;For a tool whose source you understand and trust, the smallest local repair is straightforward: inspect the quarantine attribute, verify the signing state, apply a local ad-hoc signature to that one app bundle, remove quarantine from that one app, and then verify the result. The point is not to turn off macOS security globally. The point is to fix one known local tool while keeping the safety boundary clear.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;This post is based on a real local troubleshooting session, but all private details have been removed. There are no real internal addresses, usernames, tokens, private download locations, hostnames, or machine-specific paths. The examples use placeholders such as &lt;code&gt;/Applications/&amp;lt;App&amp;gt;.app&lt;/code&gt; and &lt;code&gt;&amp;lt;App&amp;gt;&lt;/code&gt;. The goal is to share a repeatable diagnostic method, not to expose an environment.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Your Background Process Keeps Dying After Shell Exit? Stop Blaming nohup — Understand setsid and Unix Process Lifecycle</title>
      <link>https://blog.margrop.net/en/post/setsid-daemon-process-survival/</link>
      <pubDate>Mon, 01 Jun 2026 07:00:00 +0800</pubDate>
      <guid>https://blog.margrop.net/en/post/setsid-daemon-process-survival/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;The short version&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;You launched a background service. You used &lt;code&gt;nohup&lt;/code&gt;. You added &lt;code&gt;&amp;amp;&lt;/code&gt;. You redirected output. Everything looked fine. But when you closed the terminal, disconnected SSH, or—more insidiously—when an automation tool finished executing its script, the process silently vanished. You checked &lt;code&gt;ps&lt;/code&gt;—nothing. You checked the port—nothing. No error in the logs. If this has happened to you, you&amp;rsquo;re not alone.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;The root cause isn&amp;rsquo;t SIGHUP—at least, not entirely. The real issue is process group and session membership.&lt;/strong&gt; &lt;code&gt;nohup&lt;/code&gt; merely tells the process to ignore the SIGHUP signal, but the process still belongs to the same session and process group as the parent shell. When the shell exits and sends SIGHUP to the entire process group, &lt;code&gt;nohup&lt;/code&gt; helps—but only if the signal delivery chain stops there. If the controlling terminal is destroyed, if stdin/stdout pipes break, or if the parent&amp;rsquo;s process group is collectively reaped, the process can still die. The proper fix is &lt;code&gt;setsid&lt;/code&gt;: it creates a brand-new session, detaches from the controlling terminal, and places the process in its own process group—so SIGHUP from the parent shell never reaches it in the first place.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;This post is based on a real debugging session of the CLIProxyAPI service. The service was started with &lt;code&gt;nohup&lt;/code&gt; but kept disappearing every time an automation script finished. The management panel kept showing &amp;ldquo;connection failed.&amp;rdquo; The culprit turned out to be a subtle Unix process lifecycle issue. This class of problem is common in cloud dev machines, CI runners, SSH jump hosts, and containerized workflows, but it&amp;rsquo;s often misdiagnosed because the underlying process model is poorly understood.&lt;/p&gt;&#xA;&lt;p&gt;All private details have been removed. No real internal addresses, tokens, or paths appear in this article.&lt;/p&gt;</description>
    </item>
    <item>
      <title>You Can SSH Out, but Not Back In: A Fail2Ban False Positive That Broke Reverse Access</title>
      <link>https://blog.margrop.net/en/post/reverse-ssh-fail2ban-vpn-gateway-investigation/</link>
      <pubDate>Sat, 30 May 2026 15:45:00 +0800</pubDate>
      <guid>https://blog.margrop.net/en/post/reverse-ssh-fail2ban-vpn-gateway-investigation/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;Bottom line&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;The symptom looked simple: I could SSH from the local machine into a remote internal host, but the reverse SSH path failed when the remote host tried to connect back. &lt;code&gt;sshd&lt;/code&gt; was listening, the route table looked fine, and the failure still happened.&lt;/p&gt;&#xA;&lt;p&gt;The real root cause was not a broken SSH daemon. &lt;code&gt;Fail2Ban&lt;/code&gt; had banned the &lt;strong&gt;VPN gateway address&lt;/strong&gt; that represented the return path. From the local machine&amp;rsquo;s point of view, the incoming SSH session did not appear to come directly from the remote host. It appeared to come from the gateway, so the ban cut off the whole reverse path.&lt;/p&gt;&#xA;&lt;/blockquote&gt;</description>
    </item>
    <item>
      <title>Claude Code Upgrade Broke Every Model? Before You Rotate Keys, Check the Compatibility Gateway</title>
      <link>https://blog.margrop.net/en/post/claude-code-invalid-message-role-system/</link>
      <pubDate>Fri, 29 May 2026 15:30:00 +0800</pubDate>
      <guid>https://blog.margrop.net/en/post/claude-code-invalid-message-role-system/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;The short version&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;After a Claude Code upgrade, every model call failed with the same error: &lt;code&gt;API Error: 400 invalid params, chat content has invalid message role: system (2013)&lt;/code&gt;. At first glance this looks like a bad API key, a dead model, an exhausted account, a broken proxy, or a provider-wide outage. The root cause was different: a newer Claude Code request shape crossed an Anthropic-compatible model gateway that had not caught up with the client-side protocol behavior. The gateway, or a downstream chat validation layer behind it, rejected a &lt;code&gt;system&lt;/code&gt; role in a place where it did not expect one.&lt;/p&gt;&#xA;&lt;p&gt;The practical fix was not to randomly reinstall things. I reproduced the failure with the smallest possible prompt, tested adjacent Claude Code versions, and found the last known good version. &lt;code&gt;2.1.150&lt;/code&gt; returned &lt;code&gt;OK&lt;/code&gt;; &lt;code&gt;2.1.154&lt;/code&gt; and &lt;code&gt;2.1.156&lt;/code&gt; reproduced the 400. Pinning the global Claude Code installation back to &lt;code&gt;2.1.150&lt;/code&gt; restored the existing provider path.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;This post is a troubleshooting write-up for a local Claude Code environment. The fix itself was small, but the incident is useful because it shows a pattern that is becoming common in AI agent tooling: the CLI, environment settings, model gateway, API compatibility layer, and actual model backend are often separate moving pieces. When one layer changes its request structure, the terminal may only show a vague 400.&lt;/p&gt;&#xA;&lt;p&gt;All private details have been removed. This article does not include real internal addresses, usernames, tokens, private provider names, local absolute paths, or private service domains. Configuration examples use placeholders such as &lt;code&gt;&amp;lt;PLACEHOLDER&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;MODEL_GATEWAY&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;MODEL_NAME&amp;gt;&lt;/code&gt;. The goal is to document a reusable debugging method, not to expose a specific environment.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Avoiding the Trap: Why Fail2Ban Fails on Ubuntu 26.04 &amp; PVE 9.2 (And How to Fix It)</title>
      <link>https://blog.margrop.net/en/post/ubuntu-2604-pve-92-fail2ban-nftables-guide/</link>
      <pubDate>Tue, 26 May 2026 22:20:00 +0800</pubDate>
      <guid>https://blog.margrop.net/en/post/ubuntu-2604-pve-92-fail2ban-nftables-guide/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;The Bottom Line&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;With the releases of Ubuntu 26.04 LTS and Proxmox VE (PVE) 9.2, many system administrators, DevOps engineers, and self-hosting enthusiasts have eagerly upgraded or clean-installed these latest systems. However, during the post-installation security hardening phase, you may encounter a highly frustrating scenario: &lt;strong&gt;simply copying over your legacy Fail2Ban configuration files from older versions (such as Ubuntu 20.04/22.04 or PVE 7.x/8.x) will either cause the Fail2Ban service to crash during startup or, worse, run silently in the background while completely failing to block external brute-force attacks!&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;This issue is not caused by a bug in Fail2Ban itself. Instead, it is the result of three &amp;ldquo;invisible bombs&amp;rdquo; hidden beneath the surface of these modern Linux distributions, namely changes in the logging architecture, service activation mechanisms, and firewall backends. This article provides an in-depth analysis of these three architectural shifts and introduces a modern, bulletproof Fail2Ban configuration guide fully integrated with &lt;code&gt;systemd-journald&lt;/code&gt; and &lt;code&gt;nftables&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;</description>
    </item>
    <item>
      <title>Don&#39;t Rush After Installing Ubuntu 26.04 LTS! Swiftly Configure Domestic Mirrors (Huawei &amp; Alibaba Cloud APT Sources DEB822 Tutorial)</title>
      <link>https://blog.margrop.net/en/post/ubuntu-26-apt-source-mirror-setup-guide/</link>
      <pubDate>Tue, 26 May 2026 21:00:00 +0800</pubDate>
      <guid>https://blog.margrop.net/en/post/ubuntu-26-apt-source-mirror-setup-guide/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;Ubuntu 26.04 LTS (Resolute Raccoon) has officially been released. If you have just installed it, the very first thing you should do is replace the default APT sources with fast domestic mirror servers (such as Alibaba Cloud or Huawei Cloud). Note that starting from Ubuntu 24.04, the system has transitioned entirely to the new &lt;strong&gt;DEB822&lt;/strong&gt; format for APT source configurations (stored in &lt;code&gt;/etc/apt/sources.list.d/ubuntu.sources&lt;/code&gt;), making the traditional &lt;code&gt;/etc/apt/sources.list&lt;/code&gt; inactive by default.&lt;/p&gt;&#xA;&lt;p&gt;This tutorial provides a step-by-step guide on how to safely change to Alibaba Cloud and Huawei Cloud mirrors in Ubuntu 26.04 LTS, along with deep troubleshooting analysis for potential pitfalls like dependency conflicts and version codename mismatches.&lt;/p&gt;&#xA;&lt;/blockquote&gt;</description>
    </item>
    <item>
      <title>Your MCP Config Was Fine. The Server Died Before the Handshake.</title>
      <link>https://blog.margrop.net/en/post/codex-mcp-startup-swift-runtime-troubleshooting/</link>
      <pubDate>Sat, 23 May 2026 06:10:00 +0800</pubDate>
      <guid>https://blog.margrop.net/en/post/codex-mcp-startup-swift-runtime-troubleshooting/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;The short version&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;The misleading part of this incident was the surface error. Codex reported that an MCP client could not initialize and that the MCP server handshake failed. That naturally pushes you toward MCP configuration, JSON-RPC initialization, plugin settings, authentication, network transport, or stale tool caches. But the root cause was lower than the MCP protocol: one local MCP server binary was killed by the macOS dynamic linker before it could answer the &lt;code&gt;initialize&lt;/code&gt; request. The client only saw a closed connection. The useful evidence was in the server stderr: &lt;code&gt;dyld: Symbol not found&lt;/code&gt;, pointing at a missing Swift Concurrency runtime symbol.&lt;/p&gt;&#xA;&lt;p&gt;In other words, MCP did not really get a chance to fail. The server process died first.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;This article walks through a real troubleshooting pattern around Codex, MCP clients, and MCP servers. The failure looked like a protocol startup problem, but the root cause lived in a native macOS binary and its Swift runtime compatibility.&lt;/p&gt;&#xA;&lt;p&gt;All private details have been removed. This article does not include real internal addresses, usernames, session IDs, full local paths, tokens, private repository names, or business system names. Paths use placeholders such as &lt;code&gt;~&lt;/code&gt;, &lt;code&gt;&amp;lt;USER_HOME&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;PLUGIN_DIR&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;PROJECT&amp;gt;&lt;/code&gt;. Log snippets keep only the technical fields needed to explain the diagnosis.&lt;/p&gt;</description>
    </item>
    <item>
      <title>New Chrome Could Not Log In to Bitwarden, but Old Machines Still Worked: A Vaultwarden Compatibility Trap</title>
      <link>https://blog.margrop.net/en/post/chrome-bitwarden-vaultwarden-login-failure-prelogin/</link>
      <pubDate>Mon, 18 May 2026 10:05:00 +0800</pubDate>
      <guid>https://blog.margrop.net/en/post/chrome-bitwarden-vaultwarden-login-failure-prelogin/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;The failure looked like a Chrome or Bitwarden extension problem: newly installed computers could not log in to a self-hosted Vaultwarden instance, while existing computers continued to work with the same Bitwarden &lt;code&gt;2026.4.1&lt;/code&gt; browser extension. The actual root cause was server-side compatibility: the clients were using a newer prelogin endpoint, while the Vaultwarden server was still running &lt;code&gt;vaultwarden/server:1.35.4-alpine&lt;/code&gt;, which did not provide &lt;code&gt;/identity/accounts/prelogin/password&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Existing machines were likely protected by an already established login state and local cache. They did not prove that the full first-login API path was still compatible. The correct fix was to back up the Vaultwarden data, upgrade the server image to &lt;code&gt;1.36.0&lt;/code&gt; or newer, and confirm from logs that the prelogin endpoint no longer returned 404.&lt;/p&gt;&#xA;&lt;p&gt;All domains, paths, usernames, and deployment details in this article are sanitized. Examples use placeholders such as &lt;code&gt;vault.example.com&lt;/code&gt; and &lt;code&gt;/opt/vaultwarden&lt;/code&gt;; no internal network or private information is included.&lt;/p&gt;&#xA;&lt;/blockquote&gt;</description>
    </item>
    <item>
      <title>[Clickbait Alert] Switching to a Chinese Mirror Nearly Broke My Ubuntu: A Real Story of apt Dependency Hell</title>
      <link>https://blog.margrop.net/en/post/ubuntu-26-apt-source-mirror-dependency-conflict-fix/</link>
      <pubDate>Sun, 17 May 2026 10:00:00 +0800</pubDate>
      <guid>https://blog.margrop.net/en/post/ubuntu-26-apt-source-mirror-dependency-conflict-fix/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;I switched to a domestic Chinese mirror for Ubuntu and thought it would take 10 minutes. It took two hours. This article documents the entire journey from &amp;ldquo;mirror sync delay causing version mismatch&amp;rdquo; to &amp;ldquo;dpkg forced downgrade&amp;rdquo; to &amp;ldquo;apt fully restored.&amp;rdquo; The core lesson: switching a mirror is not a one-click fix—version consistency is everything. If you swap mirrors on a system that&amp;rsquo;s been running for months, you have a 99% chance of hitting dependency conflicts.&lt;/p&gt;&#xA;&lt;p&gt;All operations in this article are based on Ubuntu 26.04 (Plucky). All IPs, hostnames, and paths are sanitized examples and contain no internal network addresses or sensitive information.&lt;/p&gt;&#xA;&lt;/blockquote&gt;</description>
    </item>
    <item>
      <title>Debugging Chinese Text Turning into Underscores in tmux &#43; opencode: The Real Culprit Was client_utf8=0</title>
      <link>https://blog.margrop.net/en/post/tmux-opencode-chinese-underscore-fix/</link>
      <pubDate>Tue, 28 Apr 2026 18:40:00 +0800</pubDate>
      <guid>https://blog.margrop.net/en/post/tmux-opencode-chinese-underscore-fix/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;Short version&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;This was not an opencode Chinese rendering bug, and it was not a broken terminal font. The tmux client had been detected as not supporting UTF-8. The decisive evidence was &lt;code&gt;tmux display-message -p &#39;client_utf8=#{client_utf8}&#39;&lt;/code&gt;, which returned &lt;code&gt;client_utf8=0&lt;/code&gt;. In that state, tmux replaces non-ASCII output with underscores, so every Chinese character inside the opencode TUI appeared as &lt;code&gt;_&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;This post documents a small but very typical terminal debugging session. On the same Mac, running &lt;code&gt;opencode&lt;/code&gt; directly displayed Chinese text correctly. But after starting a tmux session first and then running &lt;code&gt;opencode&lt;/code&gt;, all Chinese text became underscores.&lt;/p&gt;&#xA;&lt;p&gt;This kind of problem is easy to misdiagnose. The first instinct is often to blame the font, the terminal emulator, the opencode version, a color theme, Nerd Font configuration, Unicode width handling, or a bug in the TUI framework. In this case, however, the key question was much narrower: &lt;strong&gt;does the current tmux client believe it can write UTF-8 back to the outer terminal?&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;To avoid exposing private environment details, all examples below are generalized. They do not include real hostnames, private addresses, session names, credentials, usernames, or project paths. The only things preserved are the technical facts needed to understand the failure and the fix.&lt;/p&gt;</description>
    </item>
    <item>
      <title>OpenClaw Backup Failure Investigation and Fix: From Two Broken Schedules to a Fully Restored Automation Pipeline</title>
      <link>https://blog.margrop.net/en/post/openclaw-backup-failure-investigation-and-fix/</link>
      <pubDate>Mon, 13 Apr 2026 09:30:00 +0800</pubDate>
      <guid>https://blog.margrop.net/en/post/openclaw-backup-failure-investigation-and-fix/</guid>
      <description>&lt;p&gt;This post is a full incident review, not because the failure was dramatic, but because it was a very typical automation problem.&lt;/p&gt;&#xA;&lt;p&gt;Two machines lost their automatic OpenClaw backups at the same time: a local macOS machine and a remote VPS. Manual runs worked on both sides, and the archives could still be synchronized to the NAS, but the scheduled jobs were unreliable. At first glance it looked like &amp;ldquo;the scheduler did not fire.&amp;rdquo; In reality, the root cause was spread across scheduling, permissions, locking, logging, and even the way I validated the schedule.&lt;/p&gt;&#xA;&lt;p&gt;That combination is what makes these incidents annoying: every individual piece looks almost fine, yet the full pipeline still fails.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Upgrading OpenClaw to 2026.3.23-2: A Real Compatibility Upgrade Playbook</title>
      <link>https://blog.margrop.net/en/post/openclaw-upgrade-to-2026-3-23-compatibility-playbook/</link>
      <pubDate>Tue, 24 Mar 2026 21:20:00 +0800</pubDate>
      <guid>https://blog.margrop.net/en/post/openclaw-upgrade-to-2026-3-23-compatibility-playbook/</guid>
      <description>&lt;p&gt;This post documents a real OpenClaw upgrade, not a laboratory demo. The goal was not simply to change a version string. The real goal was to move a small multi-node environment from &lt;code&gt;2026.3.8 / 2026.3.13&lt;/code&gt; to &lt;code&gt;2026.3.23-2&lt;/code&gt; while keeping message channels, external plugins, service startup behavior, and rollback paths under control.&lt;/p&gt;&#xA;&lt;p&gt;In practice, the most difficult part was not the OpenClaw core package. The hardest part was everything around it: plugin SDK compatibility, JIT cache reuse, mixed installation layouts, restricted outbound access on an edge node, and differences in bind mode that changed what health probes actually meant. If you do not identify those variables first, an upgrade can easily look successful while one or more production channels are already broken.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
