<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>MulmoClaude on blog.taikiito.com</title><link>https://blog.taikiito.com/en/tags/mulmoclaude/</link><description>Recent content in MulmoClaude on blog.taikiito.com</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Mon, 17 Aug 2026 09:00:00 +0800</lastBuildDate><atom:link href="https://blog.taikiito.com/en/tags/mulmoclaude/index.xml" rel="self" type="application/rss+xml"/><item><title>After Making MulmoClaude Run 24/7, Its Background Automation Started Looping Forever</title><link>https://blog.taikiito.com/en/posts/2026-08-17-journal-loop-bug/</link><pubDate>Mon, 17 Aug 2026 09:00:00 +0800</pubDate><guid>https://blog.taikiito.com/en/posts/2026-08-17-journal-loop-bug/</guid><description>The day after moving to an always-on server, a background automation task (the journal feature) started endlessly retrying the same sessions. The root cause: a millisecond-precision mismatch between Python and Node.js timestamps.</description><content:encoded><![CDATA[<p>Last time I wrote about moving MulmoClaude to an always-on AWS Lightsail server. The migration itself went fine - but the next day, I noticed something was off with the automation running behind the scenes.</p>
<h2 id="what-was-happening">What was happening</h2>
<p>MulmoClaude has a background feature called <code>journal</code> that auto-generates daily summaries from chat logs. It started taking an abnormally long time every time it ran hourly - some runs took 25 to 90 seconds. It felt like something was retrying over and over, and token usage was climbing noticeably.</p>
<h2 id="tracking-it-down">Tracking it down</h2>
<p>Digging in, I found that a large batch of sessions (625 of them) generated during the migration work (see last post) had been left unprocessed. The hourly job kept trying to process the entire backlog every single run, and kept failing to finish - so the next hour it would try the exact same backlog again. Close to an infinite loop.</p>
<p>Digging further, there was a deeper root cause underneath. The logic that checks whether a session has &ldquo;already been processed&rdquo; compares timestamps, and the Python side and the Node.js side handled milliseconds slightly differently - one truncating, the other rounding. That mismatch meant sessions that should have counted as &ldquo;already processed&rdquo; kept looking &ldquo;unprocessed&rdquo; every single time. A few attempted fixes didn&rsquo;t stick, because none of them addressed the mismatch itself.</p>
<h2 id="stopgap-then-the-real-fix">Stopgap, then the real fix</h2>
<p>First I turned the <code>journal</code> feature off entirely to stop the bleeding. Then I fixed the timestamp handling so both languages treat precision the same way. After that, I confirmed <code>daysSkipped: 0</code> and the hourly check dropped to 2-3 milliseconds.</p>
<p>I also revisited the schedule: <code>journal</code> is now pinned to run once a day (<code>dailyIntervalHours: 24</code>), and the hourly scheduler check itself stays lightweight.</p>
<h2 id="what-i-took-from-this">What I took from this</h2>
<p>&ldquo;Always-on&rdquo; turned out to mean more than just the app staying up - the quiet automation running behind it has to not break either. The most expensive thing was happening somewhere completely invisible from the front end.</p>
<p>To be continued.</p>
]]></content:encoded></item><item><title>Moving to a Server That Never Turns Off</title><link>https://blog.taikiito.com/en/posts/2026-08-16-moving-to-a-server/</link><pubDate>Sun, 16 Aug 2026 09:00:00 +0800</pubDate><guid>https://blog.taikiito.com/en/posts/2026-08-16-moving-to-a-server/</guid><description>Migrating MulmoClaude to AWS Lightsail. A silent CSRF trusted-origin failure, a mystery &amp;lsquo;Network error calling &amp;hellip;&amp;rsquo; traced to a Docker Desktop vs Docker Engine networking difference, and a memory-plan saga.</description><content:encoded><![CDATA[<p>Continuing from before. I&rsquo;d been running MulmoClaude on my local MacBook, which meant it stopped working whenever I closed the lid or took it with me. I increasingly wanted proper web UI access while out and about, away from home. The Telegram bot couldn&rsquo;t handle multiple threads well and its Markdown rendering was weak, so I was starting to feel its limits.</p>
<h2 id="weighing-the-options">Weighing the options</h2>
<p>Leaving my laptop on 24/7 was ruled out on electricity and portability grounds. Connecting directly via SSH from my own machine was ruled out early too - corporate networks commonly block outbound port 22 (SSH). Instead I went with HTTPS over a domain, fronted by an authentication gate (Cloudflare Access). Login is via Google account, plus the app&rsquo;s own shared secret (<code>MULMOCLAUDE_AUTH_TOKEN</code>) as a second layer.</p>
<p>I went back and forth with Sakura Cloud, but chose AWS Lightsail for the lower latency from its Singapore region. Started on the $7/mo bundle (1GB RAM, 2 vCPUs, 40GB SSD), planning to upgrade later if it wasn&rsquo;t enough.</p>
<h2 id="a-silent-failure-the-csrf-trusted-origin-gap">A silent failure: the CSRF trusted-origin gap</h2>
<p>The migration itself went through, but sending a message from the browser just silently failed - no error, no response.</p>
<p>The cause: the app&rsquo;s built-in CSRF guard checks the <code>Origin</code> header, and the new domain hadn&rsquo;t been added to <code>MULMOCLAUDE_TRUSTED_ORIGINS</code> in <code>.env</code>. Adding <code>https://mulmoclaude-taiki.com</code> there and running <code>systemctl restart mulmoclaude.service</code> fixed it. If you&rsquo;re self-hosting MulmoClaude and sends silently fail only from your public domain, check this first.</p>
<h2 id="chasing-down-network-error-calling-">Chasing down &ldquo;Network error calling &hellip;&rdquo;</h2>
<p>A few days later, internal MCP-bridge calls like <code>presentForm</code> and <code>manageCollection</code> started intermittently failing with &ldquo;Network error calling &hellip;: fetch failed.&rdquo;</p>
<p>My first suspicion was memory pressure. <code>/proc/meminfo</code> did show low <code>MemAvailable</code>, with swap mostly used. I installed <code>zram-tools</code> for compressed swap (<code>/dev/zram0</code>) and dropped <code>vm.swappiness</code> to 10 - no change. I even bumped the Lightsail plan from $7/mo (1GB) to $12/mo (2GB) just in case, and the error still reproduced 100% of the time.</p>
<p>The real cause turned up when I read the app&rsquo;s source (<code>server/index.ts</code>): it&rsquo;s designed to <strong>bind only to loopback</strong> - <code>app.listen(port, &quot;127.0.0.1&quot;, ...)</code>. The sandboxed agent reaches the host via <code>host.docker.internal</code>. On <strong>Docker Desktop (Mac)</strong>, that resolves through a hypervisor proxy that does reach loopback. On <strong>Docker Engine (Linux)</strong>, it just resolves to the real IP of the <code>docker0</code> bridge (<code>172.17.0.1</code> in this case) - a genuinely different interface, which a loopback-only bind will never accept a connection from. A latent bug that never surfaced on the Mac, exposed for the first time on Linux.</p>
<p>The fix was a single <code>socat</code> relay bridging the docker-bridge address to loopback:</p>
<pre tabindex="0"><code>ExecStart=/usr/bin/socat TCP-LISTEN:3001,bind=172.17.0.1,fork,reuseaddr TCP:127.0.0.1:3001
</code></pre><p>Registered as a systemd unit, and <code>curl http://host.docker.internal:3001/</code> immediately started returning 200, with <code>presentForm</code>/<code>manageCollection</code> working right away. The memory/zram work wasn&rsquo;t entirely wasted (headroom genuinely improved), but it was never the actual cause of this error.</p>
<h2 id="why-i-ended-back-on-12mo-2gb">Why I ended back on $12/mo (2GB)</h2>
<p>Once the real cause was fixed, I tried downgrading back to $7/mo (1GB) to save money. Lightsail, though, <strong>doesn&rsquo;t support resizing an instance down via snapshot</strong> - it meant rebuilding a 1GB instance from scratch. That rebuilt instance repeatedly hung under load (like building the Docker sandbox image), with SSH and even Lightsail&rsquo;s own browser console becoming unresponsive, more than once.</p>
<p>I concluded 1GB can&rsquo;t reliably sustain this workload (MulmoClaude itself, the Docker sandbox, and the Telegram bridge together) and settled on $12/mo (2GB) as the final plan. Stability won over saving a few dollars.</p>
<h2 id="running-247">Running 24/7</h2>
<p>A few stumbles along the way, but this server has been running around the clock ever since. I can use it anytime, from anywhere, regardless of what state my own laptop is in.</p>
<p>To be continued.</p>
]]></content:encoded></item><item><title>How I Ran Into MulmoClaude, and Got It Actually Usable</title><link>https://blog.taikiito.com/en/posts/2026-07-25-mulmoclaude-genesis/</link><pubDate>Sat, 25 Jul 2026 09:00:00 +0800</pubDate><guid>https://blog.taikiito.com/en/posts/2026-07-25-mulmoclaude-genesis/</guid><description>A regular office worker who tinkers with computers on the side, building a personal AI assistant setup from scratch. Includes the ERR_MODULE_NOT_FOUND stumble.</description><content:encoded><![CDATA[<p>I have a regular day job, and tinker with computers on the side. Not an engineer. This blog is a record of what I did, in the order it happened. If it makes you think &ldquo;I could probably do this too,&rdquo; that&rsquo;s exactly the point.</p>
<p>As a first step, here&rsquo;s how it all started.</p>
<h2 id="how-it-started">How it started</h2>
<p>I came across &ldquo;MulmoClaude&rdquo; through Satoshi Nakajima&rsquo;s newsletter - an open-source project that lets you self-host an AI assistant like ChatGPT or Claude on your own machine instead of as a SaaS, with conversation history and memory persisted as local files you control. I decided to set it up on my MacBook. Environment: Node.js v24.18.0, git 2.50.1, Claude Code CLI v2.1.220.</p>
<h2 id="first-stumble-err_module_not_found">First stumble: <code>ERR_MODULE_NOT_FOUND</code></h2>
<p>There are a few install methods. I first tried the developer route: <code>git clone</code>-ing <code>receptron/mulmoclaude</code>. Running <code>yarn install</code> then <code>yarn dev</code> threw <code>ERR_MODULE_NOT_FOUND</code> and the server wouldn&rsquo;t start. The cause: I was trying to start <code>dev</code> before the package workspace had actually been built. Running <code>yarn build:packages:dev</code> first fixed it immediately. If you hit this exact error, check that build step first.</p>
<p>Once it was working, I found out the officially recommended path is <code>npx mulmoclaude@latest</code> - no build step, no repo to manage. I switched over the same day. The port also changed, from <code>5173</code> (the git-clone/<code>yarn dev</code> setup) to <code>3001</code> (npx). I still run the npx version today. Had I started there, I&rsquo;d have skipped the <code>ERR_MODULE_NOT_FOUND</code> stumble entirely.</p>
<h2 id="getting-it-into-actual-daily-use">Getting it into actual daily use</h2>
<p>Once it was running, I made a few changes to turn it into something usable day to day. First, I wanted access when I wasn&rsquo;t at my computer, so I created a bot (<code>taiki_mulmoclaude_bot</code>) via BotFather and wired it up following the official Telegram Bot API steps. Being able to chat with it on the go turned out to be genuinely useful.</p>
<p>Alongside that, everything I&rsquo;d built up talking to the official Claude app (my work, where I live, my interests, day-to-day notes) got carried over as &ldquo;memory&rdquo; (a system that accumulates as local Markdown files) into this new setup. Picking up from that existing history, instead of starting from zero, made a real difference.</p>
<p>Finally, I didn&rsquo;t want a broken local environment to wipe everything out, so early on I created a private GitHub repository and set up regular automated backups via <code>git push</code>. That habit is still going, and it later gave me the confidence to move everything to a server.</p>
<p>By this point, I had MulmoClaude running on my machine, reachable via Telegram, with its data backed up on GitHub. Next: moving it to an always-on cloud server.</p>
]]></content:encoded></item></channel></rss>