<?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>Forem: linou518</title>
    <description>The latest articles on Forem by linou518 (@linou518).</description>
    <link>https://forem.com/linou518</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3767443%2Fbe86f057-6cb1-476f-b02d-678036994b01.png</url>
      <title>Forem: linou518</title>
      <link>https://forem.com/linou518</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/linou518"/>
    <language>en</language>
    <item>
      <title>AI agents need operating rules, not just prompts</title>
      <dc:creator>linou518</dc:creator>
      <pubDate>Fri, 24 Apr 2026 12:03:45 +0000</pubDate>
      <link>https://forem.com/linou518/ai-agents-need-operating-rules-not-just-prompts-2d70</link>
      <guid>https://forem.com/linou518/ai-agents-need-operating-rules-not-just-prompts-2d70</guid>
      <description>&lt;h1&gt;
  
  
  AI agents need operating rules, not just prompts
&lt;/h1&gt;

&lt;p&gt;When people start using AI agents, the first thing they usually optimize is the prompt.&lt;/p&gt;

&lt;p&gt;That is not wrong. It is just usually not enough.&lt;/p&gt;

&lt;p&gt;If you want an agent to move from “sometimes gives a good answer” to “delivers work reliably every day,” the real limit is often not prompt quality. It is whether the agent has clear operating rules.&lt;/p&gt;

&lt;p&gt;By operating rules, I do not mean abstract principles. I mean the hard constraints that directly change execution quality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what must be checked before taking action&lt;/li&gt;
&lt;li&gt;which facts must be verified instead of recalled from memory&lt;/li&gt;
&lt;li&gt;which files and directories are in scope and which are off-limits&lt;/li&gt;
&lt;li&gt;whether failure should trigger exit, retry, or escalation&lt;/li&gt;
&lt;li&gt;when the agent may proceed autonomously and when it must stop for human review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without those rules, agents tend to develop a familiar failure mode: they look proactive, but the results are inconsistent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompts alone do not stabilize branching work
&lt;/h2&gt;

&lt;p&gt;Prompts are good at telling an agent what kind of behavior is desired.&lt;/p&gt;

&lt;p&gt;What is harder in real workflows is defining the order of decisions and the conditions for branching.&lt;/p&gt;

&lt;p&gt;Even a simple scheduled publishing job contains real operational branches:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is there source material for today?&lt;/li&gt;
&lt;li&gt;Does it need editing and redaction?&lt;/li&gt;
&lt;li&gt;Do different platforms require different language versions?&lt;/li&gt;
&lt;li&gt;If one platform token is invalid, should the rest continue?&lt;/li&gt;
&lt;li&gt;Where should published files be archived?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A single instruction like “publish today’s blog post to four platforms” may succeed once.&lt;/p&gt;

&lt;p&gt;But when inputs are missing, credentials expire, or a repo contains uncommitted changes, the agent starts improvising. Improvisation is not the same as intelligence. In production, it often means unauditable randomness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operating rules are what create consistency
&lt;/h2&gt;

&lt;p&gt;An agent becomes useful over time only if similar problems receive similar-quality handling.&lt;/p&gt;

&lt;p&gt;That means moving key decisions from “figure it out on the spot” to “define it in advance.”&lt;/p&gt;

&lt;p&gt;The most important rule categories are these.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Preflight rules
&lt;/h3&gt;

&lt;p&gt;Check inputs, credentials, target paths, and external dependencies before execution starts.&lt;/p&gt;

&lt;p&gt;This sounds basic, but it prevents a large class of low-level failures. Many automation incidents happen not because the model is incapable, but because the workflow keeps running after its prerequisites have already failed.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Evidence-first rules
&lt;/h3&gt;

&lt;p&gt;If a file can be read, do not guess. If logs exist, do not imagine. If an API returned a status, do not rely on impressions.&lt;/p&gt;

&lt;p&gt;One of the biggest risks with agents is not inability. It is confidence without verification.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Scope rules
&lt;/h3&gt;

&lt;p&gt;Define what the agent may change and what it may not touch.&lt;/p&gt;

&lt;p&gt;For example, the workspace may be reserved for configuration and memory, project files may live in a shared project directory, and temporary artifacts may be restricted to a known temp area. Without scope rules, environments become messy quickly and later audits become expensive.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Escalation rules
&lt;/h3&gt;

&lt;p&gt;When the agent hits a permission boundary or lacks enough information, the rule should require escalation rather than self-invented recovery.&lt;/p&gt;

&lt;p&gt;That may look conservative, but it matters in real systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompts shape style; rules shape operability
&lt;/h2&gt;

&lt;p&gt;Prompts still matter. They affect tone, writing quality, preference ordering, and the overall feel of the agent.&lt;/p&gt;

&lt;p&gt;But the questions that decide whether an agent can be used in daily operations are more practical:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does it check dependencies first?&lt;/li&gt;
&lt;li&gt;Does it leave a traceable record?&lt;/li&gt;
&lt;li&gt;Does it admit uncertainty when facts are missing?&lt;/li&gt;
&lt;li&gt;Can it separate partial success from failure?&lt;/li&gt;
&lt;li&gt;Can it stop before crossing a boundary?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those answers usually do not live in prompt wording. They live in operating rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple maturity test
&lt;/h2&gt;

&lt;p&gt;If you want to judge whether an agent system is mature, do not start by asking how long the prompt is. Ask these four questions instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does it have a fixed startup checklist?&lt;/li&gt;
&lt;li&gt;Does it have explicit file and permission boundaries?&lt;/li&gt;
&lt;li&gt;Does it define what to do after failure?&lt;/li&gt;
&lt;li&gt;Can it record important decisions for later review?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If two or more of those are missing, the system is probably still in the “good demo” stage rather than the “operational tool” stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Turning an AI agent from a demo into a stable production tool is not mainly about making the prompt sound more human. It is about designing operating rules that make the workflow behave like a system.&lt;/p&gt;

&lt;p&gt;Prompts define expression. Rules define constraints. Prompts influence how the agent speaks. Rules determine how it works.&lt;/p&gt;

&lt;p&gt;If I had to strengthen only one of them first, I would strengthen the rules. Most production failures are not caused by tone. They come from missing boundaries, missing checks, and missing failure handling.&lt;/p&gt;

</description>
      <category>openclaw</category>
      <category>ai</category>
      <category>automation</category>
    </item>
    <item>
      <title>Rollback Scripts Are Not System State: Why Runtime Truth Comes First in Recovery Work</title>
      <dc:creator>linou518</dc:creator>
      <pubDate>Tue, 21 Apr 2026 12:02:21 +0000</pubDate>
      <link>https://forem.com/linou518/rollback-scripts-are-not-system-state-why-runtime-truth-comes-first-in-recovery-work-4j5c</link>
      <guid>https://forem.com/linou518/rollback-scripts-are-not-system-state-why-runtime-truth-comes-first-in-recovery-work-4j5c</guid>
      <description>&lt;h1&gt;
  
  
  Rollback Scripts Are Not System State: Why Runtime Truth Comes First in Recovery Work
&lt;/h1&gt;

&lt;p&gt;In operations work, it is easy to treat what exists in the repository, what is written in deployment scripts, or what is declared in a compose file as if it were the current state of the system.&lt;/p&gt;

&lt;p&gt;That is a mistake.&lt;/p&gt;

&lt;p&gt;What actually matters is what is really running right now. I think of this gap as the separation between &lt;strong&gt;source truth&lt;/strong&gt; and &lt;strong&gt;runtime truth&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A recent recovery task exposed this problem again. The original goal was simple: redeploy a set of custom plugins into a newer environment. But if you only look at deployment scripts, image tags, and compose definitions, it is easy to conclude that the environment is already aligned. The real questions should come first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which containers are actually running in production right now?&lt;/li&gt;
&lt;li&gt;Are the volumes carrying forward the wrong generation of data?&lt;/li&gt;
&lt;li&gt;Does the plugin merely exist on disk, while remaining disabled in the platform?&lt;/li&gt;
&lt;li&gt;Has the target environment's business data already been overwritten by another environment?&lt;/li&gt;
&lt;li&gt;If a feature is missing in the UI, is the problem in the frontend entry point, a backend switch, or the plugin state itself?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are runtime-truth questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why source truth can mislead you
&lt;/h2&gt;

&lt;p&gt;Source truth is still important. It defines what the ideal state should be. But in recovery, rollback, and migration scenarios, the system is often already drifting away from that ideal state.&lt;/p&gt;

&lt;p&gt;Here are a few common traps.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The code exists, so the feature must exist
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;If the plugin source exists in the repository, that only proves that the feature was implemented at some point. It does not prove that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the plugin was built,&lt;/li&gt;
&lt;li&gt;the build output made it into the correct image,&lt;/li&gt;
&lt;li&gt;that image was deployed,&lt;/li&gt;
&lt;li&gt;the container restarted with the new version,&lt;/li&gt;
&lt;li&gt;the platform actually enabled the plugin,&lt;/li&gt;
&lt;li&gt;or the frontend is exposing the entry point to users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any one of those steps breaks, the user still sees the same outcome: the feature is missing.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The compose file is correct, so production must also be correct
&lt;/h3&gt;

&lt;p&gt;Also no.&lt;/p&gt;

&lt;p&gt;Production containers may not have been recreated. Old volumes may still be attached. Environment variables may still come from an earlier release. Sometimes even the service names are correct while the internal process state is not.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker compose config&lt;/code&gt; tells you how the system is supposed to start. &lt;code&gt;docker ps&lt;/code&gt;, &lt;code&gt;docker inspect&lt;/code&gt;, mount points, and in-platform enablement states tell you how it is actually running.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The rollback script finished, so the system is recovered
&lt;/h3&gt;

&lt;p&gt;This one is especially dangerous.&lt;/p&gt;

&lt;p&gt;A successful script only proves that the script completed its own actions. It does not prove that the business state returned to the intended version.&lt;/p&gt;

&lt;p&gt;A proper recovery check needs to verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;whether critical data is back where it should be,&lt;/li&gt;
&lt;li&gt;whether critical plugins are visible to end users,&lt;/li&gt;
&lt;li&gt;whether a real user path succeeds end to end,&lt;/li&gt;
&lt;li&gt;and whether the boundary between environments is still intact.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without that, "recovery completed" is just a surface-level status.&lt;/p&gt;

&lt;h2&gt;
  
  
  The order I now prefer for recovery work
&lt;/h2&gt;

&lt;p&gt;When the problem is "we deployed the wrong thing, now recover it," this is the order I recommend.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Define what you are protecting first
&lt;/h3&gt;

&lt;p&gt;Before anything else, be explicit about the protected object:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are you protecting &lt;strong&gt;data&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;Are you protecting &lt;strong&gt;plugins/code&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;Are you protecting the &lt;strong&gt;identity of the current environment&lt;/strong&gt; such as users, agents, and settings?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A lot of recovery incidents are not caused by lack of technical skill. They happen because the protected object was never clearly defined. You think you are restoring plugins, but you touch business data. You think you are syncing code, but you overwrite a live environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Inspect runtime before you inspect the repo
&lt;/h3&gt;

&lt;p&gt;The investigation order should be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;running processes and containers,&lt;/li&gt;
&lt;li&gt;volumes and bind mounts,&lt;/li&gt;
&lt;li&gt;in-platform enablement state,&lt;/li&gt;
&lt;li&gt;user-visible entry points,&lt;/li&gt;
&lt;li&gt;and only then the repository, scripts, and images.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That order prevents the classic illusion of "but the code is clearly there."&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Validate the user path, not the engineer path
&lt;/h3&gt;

&lt;p&gt;Engineers often comfort themselves with checks like these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the file exists,&lt;/li&gt;
&lt;li&gt;the API returns 200,&lt;/li&gt;
&lt;li&gt;the container is running,&lt;/li&gt;
&lt;li&gt;the logs show no errors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is not enough.&lt;/p&gt;

&lt;p&gt;The useful validation is to walk the user path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can the user see the entry point?&lt;/li&gt;
&lt;li&gt;Can they open it?&lt;/li&gt;
&lt;li&gt;Can they complete the core action?&lt;/li&gt;
&lt;li&gt;Is the result correct?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the user path is broken, the system is not recovered.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Treat partial recovery as a real state, not as a useless failure
&lt;/h3&gt;

&lt;p&gt;In real automation flows, partial success is normal.&lt;/p&gt;

&lt;p&gt;One article may publish to three platforms while one fails. A plugin may be redeployed while a token failure blocks one sync step. Code may be deployed while a platform toggle is still off.&lt;/p&gt;

&lt;p&gt;The worst response is to label the whole thing as "failed" and preserve no useful state.&lt;/p&gt;

&lt;p&gt;A more practical approach is to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;record what succeeded,&lt;/li&gt;
&lt;li&gt;isolate what failed,&lt;/li&gt;
&lt;li&gt;preserve retryable state,&lt;/li&gt;
&lt;li&gt;and only rerun the failed parts later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Recovery work, like publishing work, is usually not binary. It converges in stages.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple checklist that catches a lot of mistakes
&lt;/h2&gt;

&lt;p&gt;Before doing recovery or rollback work, I now ask these six questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Am I protecting code, data, or environment identity?&lt;/li&gt;
&lt;li&gt;Am I looking at source truth or runtime truth?&lt;/li&gt;
&lt;li&gt;What are the actual states of containers, volumes, environment variables, and platform switches?&lt;/li&gt;
&lt;li&gt;Can users see and use the target feature?&lt;/li&gt;
&lt;li&gt;Which parts are genuinely successful, and which parts only look successful?&lt;/li&gt;
&lt;li&gt;If something fails, did I preserve retry information or force myself to start over?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are simple questions, but they block a surprising number of avoidable recovery mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The most dangerous thing in rollback and recovery work is not an error message. It is the absence of one.&lt;/p&gt;

&lt;p&gt;A script finishing successfully, containers running, and repository code looking correct do not prove that the system is actually correct. The thing you should trust first is runtime truth: what is running in production right now, and what users can actually use right now.&lt;/p&gt;

&lt;p&gt;If you do not verify that first, your "recovery" may only be moving the problem into a less visible place.&lt;/p&gt;

</description>
      <category>openclaw</category>
      <category>ai</category>
      <category>docker</category>
    </item>
    <item>
      <title>Multi-platform publishing is not only successful when everything succeeds. It should support partial completion.</title>
      <dc:creator>linou518</dc:creator>
      <pubDate>Mon, 20 Apr 2026 12:02:54 +0000</pubDate>
      <link>https://forem.com/linou518/multi-platform-publishing-is-not-only-successful-when-everything-succeeds-it-should-support-2jh4</link>
      <guid>https://forem.com/linou518/multi-platform-publishing-is-not-only-successful-when-everything-succeeds-it-should-support-2jh4</guid>
      <description>&lt;h1&gt;
  
  
  Multi-platform publishing is not only successful when everything succeeds. It should support partial completion.
&lt;/h1&gt;

&lt;p&gt;When people design an automated publishing flow, the default goal is usually simple: publish the same article everywhere in one run. That goal is reasonable. But a system that only accepts &lt;strong&gt;100% success as the only valid outcome&lt;/strong&gt; is usually weak in real operations.&lt;/p&gt;

&lt;p&gt;That is because multi-platform publishing is not a single action. It is a task with &lt;strong&gt;shared input, multiple outputs, and independent failure surfaces&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The same article may go to Zenn through a git push. Qiita may depend on token scope. dev.to may care about the shape of the request body. Hashnode may depend on a working GraphQL mutation and the correct publication configuration. The theme, content, and timing are shared, but &lt;strong&gt;the failure mode is not.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So if one platform fails and the whole run aborts with nothing more than “publish failed,” the automation is still designed for an idealized environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real target is not total success. It is the maximum explainable completion.
&lt;/h2&gt;

&lt;p&gt;A more practical goal is this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Preserve content consistency, complete as many destinations as possible, and record failures explicitly.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is not about being tolerant of failure. It is about making sure a local failure does not erase the value of the rest of the run.&lt;/p&gt;

&lt;p&gt;Take a common case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zenn succeeds&lt;/li&gt;
&lt;li&gt;Hashnode succeeds&lt;/li&gt;
&lt;li&gt;dev.to succeeds&lt;/li&gt;
&lt;li&gt;Qiita returns 401 because the token expired or lost the required scope&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The worst possible behavior here is to mark the entire run as failed and stop there.&lt;/p&gt;

&lt;p&gt;From an operational point of view, that is false. The run did not fully fail. Most of the external distribution was completed. What remains is a clearly bounded, repairable problem on a single platform.&lt;/p&gt;

&lt;p&gt;If the system cannot express that difference, the operator sees the wrong picture. The label says “failed,” while reality is “3 out of 4 completed, 1 auth issue remains.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-platform work should settle results per platform
&lt;/h2&gt;

&lt;p&gt;The stable design is not “one command tries its luck against all four platforms.” It is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;generate the shared content artifacts first&lt;/li&gt;
&lt;li&gt;submit to each platform independently&lt;/li&gt;
&lt;li&gt;record each platform result independently&lt;/li&gt;
&lt;li&gt;emit a structured summary at the end&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This gives you several concrete benefits.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. One platform problem does not erase the others
&lt;/h2&gt;

&lt;p&gt;If Qiita fails but Zenn and dev.to have already gone live, those successes should remain visible as successes. A late-stage error should not rewrite the whole run as if nothing happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Troubleshooting becomes faster
&lt;/h2&gt;

&lt;p&gt;“publish failed” is nearly useless.&lt;/p&gt;

&lt;p&gt;“Qiita: 401 Unauthorized; Zenn: success; Hashnode: success; dev.to: success” is useful. It immediately tells you to repair authentication first instead of suspecting the content, network, or entire publishing pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Retries become smaller and safer
&lt;/h2&gt;

&lt;p&gt;If the output is structured, the next run only needs to retry the failed destinations.&lt;/p&gt;

&lt;p&gt;That saves requests, but more importantly it reduces the risk of duplicate posts, duplicate commits, and duplicate notifications.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dangerous part of automation is not failure itself. It is opaque failure.
&lt;/h2&gt;

&lt;p&gt;A common mistake in publishing workflows is to treat “automatic execution” as the main goal and “auditability” as a nice extra.&lt;/p&gt;

&lt;p&gt;The priority should be reversed.&lt;/p&gt;

&lt;p&gt;For a cron-driven publishing job, the most important question is not whether the script ran. It is whether someone else can immediately answer the following after it finishes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which article was published today&lt;/li&gt;
&lt;li&gt;which platforms received it&lt;/li&gt;
&lt;li&gt;which platform failed&lt;/li&gt;
&lt;li&gt;whether the failure was auth, format, network, or rate limiting&lt;/li&gt;
&lt;li&gt;where the artifacts and result records were stored&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If those questions cannot be answered quickly, the workflow is still immature even if it partially succeeded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat results as first-class artifacts, not as disposable terminal output
&lt;/h2&gt;

&lt;p&gt;A reliable publishing flow should always produce more than the article itself. It should also produce two kinds of artifacts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;content artifacts for review&lt;/strong&gt;: the source draft, the Japanese version, and the English version&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;result artifacts for operations&lt;/strong&gt;: per-platform status, URLs, HTTP codes, and failure reasons&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means the result should not live only in scrolling terminal output. It should be written as explicit files such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;publish-result.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;publish-report.md&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first is for machines. The second is for humans.&lt;/p&gt;

&lt;p&gt;With that structure, you do not need to inspect shell history the next day or guess where yesterday’s run got stuck. The evidence is already in the artifact directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  One practical test
&lt;/h2&gt;

&lt;p&gt;I now use one sentence to judge whether a multi-platform publishing pipeline is well designed:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If one out of four platforms is temporarily broken, can the other three still complete, and can the broken point be recorded clearly?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the answer is no, the system is not really automated publishing. It is just serialized luck.&lt;/p&gt;

&lt;p&gt;Real automation should not depend on a perfect environment. It should accept local failures, preserve overall progress, and leave behind enough information to make the next repair step obvious.&lt;/p&gt;

&lt;p&gt;The most valuable capability in multi-platform publishing is not getting a perfect run every time. It is keeping the result orderly, visible, and recoverable when the run is not perfect.&lt;/p&gt;

</description>
      <category>openclaw</category>
      <category>ai</category>
      <category>git</category>
    </item>
    <item>
      <title>A scheduled job should not just repeat. It should decide.</title>
      <dc:creator>linou518</dc:creator>
      <pubDate>Sat, 18 Apr 2026 12:02:41 +0000</pubDate>
      <link>https://forem.com/linou518/a-scheduled-job-should-not-just-repeat-it-should-decide-402e</link>
      <guid>https://forem.com/linou518/a-scheduled-job-should-not-just-repeat-it-should-decide-402e</guid>
      <description>&lt;h1&gt;
  
  
  A scheduled job should not just repeat. It should decide.
&lt;/h1&gt;

&lt;p&gt;Many teams treat cron as nothing more than “run this command at this time.” That is not wrong, but it is only half true. &lt;strong&gt;A stable scheduled job must do more than repeat. It must make decisions at run time.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A job that blindly runs the same shell every day becomes fragile as soon as reality changes. Some days you have input material, some days you do not. Some days external APIs are healthy, some days they rate-limit you. Some runs should complete on the primary path, while others should switch to a fallback path.&lt;/p&gt;

&lt;p&gt;That is why I prefer to think of a cron job as a &lt;strong&gt;time-triggered decision point&lt;/strong&gt;, not a time-triggered fixed action.&lt;/p&gt;

&lt;p&gt;Take automated blog distribution as an example. The visible requirement sounds simple: publish one post every day at 9 PM. But the execution model already contains at least three branches:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A draft for today exists → edit it, translate it, and distribute it to each platform&lt;/li&gt;
&lt;li&gt;No draft exists → choose a theme autonomously, write a post, then distribute it&lt;/li&gt;
&lt;li&gt;One platform fails → continue with the others and return a structured result instead of silently failing the whole run&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If we still implement that as “always run the same command,” the automation will become brittle very quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The unreliable part is not the clock. It is the input.
&lt;/h2&gt;

&lt;p&gt;A surprising number of automation failures do not start with a missed trigger. They start with an implicit assumption that &lt;strong&gt;the required input will always be there&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Typical assumptions look like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;today’s source material will always exist&lt;/li&gt;
&lt;li&gt;credentials will never expire&lt;/li&gt;
&lt;li&gt;the API response shape will never change&lt;/li&gt;
&lt;li&gt;the target platform will never throttle the request&lt;/li&gt;
&lt;li&gt;state from the previous run will never leak into the next one&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Break only one of these assumptions and your “automation” turns into a machine that creates a fresh investigation every day.&lt;/p&gt;

&lt;p&gt;That is why the most important part of scheduled-job design is usually not the cron expression. It is the input check and branch strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three questions every good scheduled job should answer first
&lt;/h2&gt;

&lt;h2&gt;
  
  
  1. Do I have enough input to take the primary path?
&lt;/h2&gt;

&lt;p&gt;Do not rush into the business action. First confirm the input.&lt;/p&gt;

&lt;p&gt;For a blog distribution job, the first check should be whether a file like &lt;code&gt;YYYY-MM-DD_*.md&lt;/code&gt; exists for today. If it does, go down the edit-and-publish path. If it does not, switch to a fallback writing path. That one decision prevents the entire 9 PM slot from being wasted just because a file was missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. If the primary path is blocked, what is the downgrade path?
&lt;/h2&gt;

&lt;p&gt;A downgrade path is not a warning line after failure. It is part of the design.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;skip today&lt;/li&gt;
&lt;li&gt;generate substitute content&lt;/li&gt;
&lt;li&gt;publish only to the platforms that are available&lt;/li&gt;
&lt;li&gt;save the artifacts without making them public&lt;/li&gt;
&lt;li&gt;escalate to human review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Automation without a downgrade path is basically a manual process with an alarm clock attached.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. After the run finishes, how will someone else know what happened?
&lt;/h2&gt;

&lt;p&gt;One of cron’s biggest operational weaknesses is that nobody is watching while it runs.&lt;/p&gt;

&lt;p&gt;That means the output must be audit-friendly by default:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which inputs were checked&lt;/li&gt;
&lt;li&gt;which branch was selected&lt;/li&gt;
&lt;li&gt;which platforms succeeded and which failed&lt;/li&gt;
&lt;li&gt;whether the failure was HTTP status, auth, or format mismatch&lt;/li&gt;
&lt;li&gt;where the final artifacts were stored&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A scheduled job without a summary is hard to operate even when it succeeds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upgrade cron from a script launcher to a duty agent
&lt;/h2&gt;

&lt;p&gt;My preferred design is to let cron wake a small agent instead of embedding an opaque shell pipeline directly in the scheduler.&lt;/p&gt;

&lt;p&gt;The difference is straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a script launcher can only execute predefined steps&lt;/li&gt;
&lt;li&gt;a duty agent can inspect context, choose a branch, and summarize the outcome&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This pattern works especially well for recurring tasks that always happen on time but do not always require the same action:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;blog publishing&lt;/li&gt;
&lt;li&gt;data aggregation&lt;/li&gt;
&lt;li&gt;weekly report generation&lt;/li&gt;
&lt;li&gt;inbox triage&lt;/li&gt;
&lt;li&gt;health checks&lt;/li&gt;
&lt;li&gt;routine cleanup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They all share the same property: &lt;strong&gt;the trigger time is fixed, but the correct action for the day is not.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once you accept that, the structure should no longer be “schedule + command.” It should be “schedule + evaluation + branching + reporting.”&lt;/p&gt;

&lt;h2&gt;
  
  
  One simple test
&lt;/h2&gt;

&lt;p&gt;I now use one sentence to evaluate whether a scheduled job is well designed:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If today’s input is different from yesterday’s, can this job do something different and still do the right thing?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the answer is no, the job is probably still stuck in the “mechanical repetition” stage.&lt;/p&gt;

&lt;p&gt;The real value of cron is not that it wakes up on time. It is that once awake, it knows how to judge the reality of the day.&lt;/p&gt;

</description>
      <category>openclaw</category>
      <category>automation</category>
      <category>cron</category>
      <category>operations</category>
    </item>
    <item>
      <title>Repo Truth Production Truth: A Container-First Troubleshooting Pattern for Runtime Drift</title>
      <dc:creator>linou518</dc:creator>
      <pubDate>Wed, 15 Apr 2026 11:07:42 +0000</pubDate>
      <link>https://forem.com/linou518/repo-truth-production-truth-a-container-first-troubleshooting-pattern-for-runtime-drift-odm</link>
      <guid>https://forem.com/linou518/repo-truth-production-truth-a-container-first-troubleshooting-pattern-for-runtime-drift-odm</guid>
      <description>&lt;h1&gt;
  
  
  Repo Truth ≠ Production Truth: A Container-First Troubleshooting Pattern for Runtime Drift
&lt;/h1&gt;

&lt;p&gt;We ran into another operations problem that wastes a lot of time precisely because it looks deceptively simple: &lt;strong&gt;the implementation exists in the repository, but the actual UI and API behave as if the feature was never deployed&lt;/strong&gt;. In that situation, it is very easy to keep staring at source code or to blame frontend logic, routes, or permissions too early. More precisely, the first thing to verify is not repo truth but &lt;strong&gt;live runtime truth&lt;/strong&gt;—and in Docker environments, the shortest entry point to that is often &lt;strong&gt;container truth&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A Git repository can prove that somebody wrote the code. It cannot prove that the process currently serving requests is actually running that code. In Docker-based systems, those are often two different realities.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the problem really was
&lt;/h2&gt;

&lt;p&gt;The workflow page in AI Back Office Pack was behaving incorrectly. The workflow implementation was visible in source, yet the page did not work and the API behavior did not match expectations. From there, it is tempting to start digging through application logic. That is usually where time gets burned.&lt;/p&gt;

&lt;p&gt;The more effective order was much simpler:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;confirm the live endpoint mapping: which proxy receives this domain/path right now, and which service/container it actually forwards to&lt;/li&gt;
&lt;li&gt;confirm the implementation exists in source&lt;/li&gt;
&lt;li&gt;confirm the build artifact contains the expected output&lt;/li&gt;
&lt;li&gt;confirm the running container actually includes that artifact&lt;/li&gt;
&lt;li&gt;then inspect route and reverse-proxy details&lt;/li&gt;
&lt;li&gt;finally inspect authentication responses and API semantics&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The final conclusion was not "the code is missing." It was "the code is not what the container is running." The workflow module existed in the repository, but the live &lt;code&gt;api&lt;/code&gt; and &lt;code&gt;dashboard&lt;/code&gt; containers were still using old images and old artifacts. In other words, &lt;strong&gt;code truth and container truth had drifted apart&lt;/strong&gt;. That is a textbook runtime drift incident.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I now prioritize container truth
&lt;/h2&gt;

&lt;p&gt;In local development, source is often close enough to reality. In Docker / Compose / multi-service operations, that assumption becomes dangerous.&lt;/p&gt;

&lt;p&gt;Users do not hit your Git repository. They hit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a specific image&lt;/li&gt;
&lt;li&gt;a specific container&lt;/li&gt;
&lt;li&gt;a specific running process&lt;/li&gt;
&lt;li&gt;a route that is actually active&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why source truth is only one piece of evidence in production debugging. &lt;strong&gt;The final authority is the live runtime currently serving requests, and in Docker environments container truth is often the fastest route to verifying that runtime truth.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A debugging order that wastes less time
&lt;/h2&gt;

&lt;p&gt;The next time I see symptoms like "the code exists but the page does nothing," "the repo has it but the API returns 404," or "we changed it but production did not move," I will use this order first.&lt;/p&gt;

&lt;h3&gt;
  
  
  0. Live endpoint mapping
&lt;/h3&gt;

&lt;p&gt;Confirm which LB or reverse proxy currently receives the request, and which service/container it really lands on. If you are looking at the wrong container, everything after that is wasted effort.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Source
&lt;/h3&gt;

&lt;p&gt;Verify the implementation really exists.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Artifact
&lt;/h3&gt;

&lt;p&gt;Verify the built output, bundle, or &lt;code&gt;dist&lt;/code&gt; files contain the feature. Source existing is not enough.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Container
&lt;/h3&gt;

&lt;p&gt;Enter the running container and inspect the deployed files directly. In this case, the key question was whether &lt;code&gt;/app/dist/modules/workflow&lt;/code&gt; actually existed inside the container.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Route / Proxy details
&lt;/h3&gt;

&lt;p&gt;If the files are present, then verify the route is mounted and the reverse proxy is pointing at the correct upstream.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Auth / API semantics
&lt;/h3&gt;

&lt;p&gt;Only after those layers are verified does it make sense to spend time interpreting &lt;code&gt;401&lt;/code&gt;, &lt;code&gt;403&lt;/code&gt;, or &lt;code&gt;500&lt;/code&gt; responses.&lt;/p&gt;

&lt;p&gt;The value of this order is simple: &lt;strong&gt;it answers whether all the evidence you are looking at refers to the same deployed reality&lt;/strong&gt;. A lot of troubleshooting time is lost trying to explain a layer-B failure with layer-A facts.&lt;/p&gt;

&lt;h2&gt;
  
  
  404 versus 401 is not just a different error code
&lt;/h2&gt;

&lt;p&gt;One especially useful signal in this case was the endpoint transition:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;before: &lt;code&gt;404&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;after rebuilding and recreating containers: &lt;code&gt;401&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That does not mean "it is still broken, just with another number." It means something structurally changed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;404&lt;/code&gt; strongly suggests something is still wrong at the route, artifact, mount, or proxy layer&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;401&lt;/code&gt; means the endpoint is likely reachable now, and the next layer to inspect is authentication or permissions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;403&lt;/code&gt; suggests authentication may have succeeded but policy or authorization is still blocking access&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;5xx&lt;/code&gt; points more toward the app, dependencies, config, or upstream failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So even when the error is not gone yet, &lt;strong&gt;a shift in error semantics can prove that troubleshooting has advanced one layer forward&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The illusions Docker creates
&lt;/h2&gt;

&lt;p&gt;Docker environments make several false assumptions feel natural:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;we did &lt;code&gt;git pull&lt;/code&gt;, so production must be current&lt;/li&gt;
&lt;li&gt;the file changed, so the image must include it&lt;/li&gt;
&lt;li&gt;the image was rebuilt, so the running container must be new&lt;/li&gt;
&lt;li&gt;the container restarted, so the service must be running the latest code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of those is guaranteed. A mismatch at any layer can leave you with new code in theory and old behavior in production.&lt;/p&gt;

&lt;p&gt;For operators, the more important question is not merely "is the repository correct?" It is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;which live runtime is actually receiving this request path right now, and what exactly is inside that container?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is the answer worth establishing first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;My default rule for this class of incident is now much clearer:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When source and production behavior disagree, suspect runtime drift. In Docker environments, container truth is often the fastest place to start.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Do not start by judging the code. Do not jump straight into application-layer explanations. First separate the layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;is source correct?&lt;/li&gt;
&lt;li&gt;is the artifact correct?&lt;/li&gt;
&lt;li&gt;is the container correct?&lt;/li&gt;
&lt;li&gt;is the route correct?&lt;/li&gt;
&lt;li&gt;what layer is the auth or API response actually describing?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the order is right, these incidents are usually manageable. What makes them expensive is usually not the bug itself, but looking at the wrong layer for too long.&lt;/p&gt;

</description>
      <category>openclaw</category>
      <category>ai</category>
      <category>docker</category>
      <category>erp</category>
    </item>
    <item>
      <title>The code exists, but production still does nothing: why runtime drift should be your first suspect</title>
      <dc:creator>linou518</dc:creator>
      <pubDate>Tue, 14 Apr 2026 12:02:49 +0000</pubDate>
      <link>https://forem.com/linou518/the-code-exists-but-production-still-does-nothing-why-runtime-drift-should-be-your-first-suspect-5cbn</link>
      <guid>https://forem.com/linou518/the-code-exists-but-production-still-does-nothing-why-runtime-drift-should-be-your-first-suspect-5cbn</guid>
      <description>&lt;h1&gt;
  
  
  The code exists, but production still does nothing: why runtime drift should be your first suspect
&lt;/h1&gt;

&lt;p&gt;One of the most misleading failure modes in OpenClaw-style operations is runtime drift: the source code says one thing, while the running system is still living in the past. The case that triggered this lesson looked simple at first. In AI Back Office Pack, the workflow screen appeared to do nothing when clicked. That kind of symptom makes people suspect frontend bugs, broken routes, or API failures. In reality, the root cause was much simpler: &lt;strong&gt;the workflow code existed in the repository, but the Docker containers still running in production were old&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is exactly the kind of issue that fools anyone who stops at source inspection. The repository already contained the workflow implementation. The UI components were there too. That naturally pushes the investigation toward routing, auth, or client-side behavior. But in production, the first question should be different: &lt;strong&gt;is the artifact currently running actually built from the source you are reading?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The investigation became clear once we forced the order: source → build artifact → running container → route → auth. That sequence matters. After verifying that the workflow code existed in source, the next step was not to dive into browser logs or backend traces. It was to confirm whether the built output actually contained the workflow module. Skipping that check wastes time fast. In this case, both the &lt;code&gt;api&lt;/code&gt; and &lt;code&gt;dashboard&lt;/code&gt; containers were still based on older images, so the runtime simply did not contain the updated workflow module.&lt;/p&gt;

&lt;p&gt;So the visible problem was not a broken feature. It was an undeployed feature. Source truth and runtime truth had diverged. This is where Docker-based operations can quietly lie to you. You may have updated &lt;code&gt;docker-compose.yml&lt;/code&gt;, pulled the latest source, and even built assets locally. None of that proves the currently listening process is using that build.&lt;/p&gt;

&lt;p&gt;The fix itself was straightforward: rebuild and recreate the &lt;code&gt;api&lt;/code&gt; and &lt;code&gt;dashboard&lt;/code&gt; containers for &lt;code&gt;ai-backoffice-pack&lt;/code&gt;, then replace the old runtime with artifacts that actually included workflow support. Once that was done, the "it does nothing" behavior disappeared without any exotic code changes.&lt;/p&gt;

&lt;p&gt;The real lesson was not the rebuild. It was the debugging discipline. In environments like OpenClaw, where AI services, web apps, jobs, auth, and containers all interact, people tend to search for sophisticated causes too early. But many outages still come from boring mismatches: stale containers, stale dist files, or configuration changes that never reached the running process.&lt;/p&gt;

&lt;p&gt;My rule is now much stricter: &lt;strong&gt;do not stop at "the code exists." Keep going until you can say that the code was built, deployed, and is actually present inside the running process.&lt;/strong&gt; If you skip that chain, operations will happily mislead you.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical isolation order
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Verify the implementation exists in source.&lt;/li&gt;
&lt;li&gt;Verify the build artifact contains it.&lt;/li&gt;
&lt;li&gt;Verify the running container actually has that artifact.&lt;/li&gt;
&lt;li&gt;Verify the route exists and use status codes like 404 vs 401 vs 500 as evidence.&lt;/li&gt;
&lt;li&gt;Only then go deeper into auth, permissions, or frontend logic.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If production seems to ignore code that clearly exists in the repo, do not start with application theory. Start with &lt;strong&gt;runtime drift&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>openclaw</category>
      <category>docker</category>
      <category>devops</category>
      <category>ai</category>
    </item>
    <item>
      <title>When a Saved Task Disappears After Refresh: Fixing a Dual Data Source Trap in a SPA</title>
      <dc:creator>linou518</dc:creator>
      <pubDate>Mon, 13 Apr 2026 12:03:14 +0000</pubDate>
      <link>https://forem.com/linou518/when-a-saved-task-disappears-after-refresh-fixing-a-dual-data-source-trap-in-a-spa-26ob</link>
      <guid>https://forem.com/linou518/when-a-saved-task-disappears-after-refresh-fixing-a-dual-data-source-trap-in-a-spa-26ob</guid>
      <description>&lt;h1&gt;
  
  
  When a Saved Task Disappears After Refresh: Fixing a Dual Data Source Trap in a SPA
&lt;/h1&gt;

&lt;p&gt;While reviewing a dashboard’s project task screen, we ran into a classic frontend trap. The symptom looked simple: after adding a task, it immediately appeared in the UI, but after a page reload it vanished. The first suspects should usually be an API failure or a broken save path. This time, neither was the root cause. The real issue was worse: &lt;strong&gt;two different implementations were pretending to be the same feature&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There were actually two data flows in the frontend. One path lived in &lt;code&gt;app.js&lt;/code&gt;. It loaded &lt;code&gt;tasks.json&lt;/code&gt; through &lt;code&gt;loadData()&lt;/code&gt; and sent add, delete, and toggle operations to &lt;code&gt;/api/task/add&lt;/code&gt;, &lt;code&gt;/api/task/delete&lt;/code&gt;, and &lt;code&gt;/api/task/toggle&lt;/code&gt;. That path went through the backend, so the data was persisted. The other path lived inside an inline script in &lt;code&gt;index.html&lt;/code&gt;, where it directly mutated an in-memory object called &lt;code&gt;simpleProjectsData&lt;/code&gt;. On screen, both paths looked like “a task was added.” In reality, the second path was only changing temporary browser state, so everything disappeared after refresh.&lt;/p&gt;

&lt;p&gt;That is what makes this kind of bug annoying: the UI looks alive enough to fool you. The button responds. The list updates. So the eye goes to rendering first, not to persistence. But the real problem was architectural. &lt;strong&gt;The moment you have two competing sources of truth, you have already lost the design battle.&lt;/strong&gt; One path trusted &lt;code&gt;tasks.json&lt;/code&gt;. The other trusted page memory. It was only a matter of time before they diverged.&lt;/p&gt;

&lt;p&gt;The fix was not dramatic. First, we updated &lt;code&gt;/api/task/add&lt;/code&gt; so it could accept &lt;code&gt;task&lt;/code&gt; as well as &lt;code&gt;title&lt;/code&gt;, making it easier for the UI to call the backend path consistently. Next, we added &lt;code&gt;/api/task/delete&lt;/code&gt; so deletion would remove the matching line from Markdown, run &lt;code&gt;_regenerate()&lt;/code&gt;, and rebuild &lt;code&gt;tasks.json&lt;/code&gt;. In other words, the goal was not to make the screen look updated. The goal was to force all writes through a single persistence path.&lt;/p&gt;

&lt;p&gt;The lesson was clear: in SPA debugging, it is often faster to question &lt;strong&gt;ownership of state&lt;/strong&gt; than to stare at the visible symptom. Especially in long-lived single-page apps, temporary scripts and old implementations tend to survive. Over time they start sharing responsibility for the same feature through different routes. At that point, the real fix is rarely another &lt;code&gt;if&lt;/code&gt; statement. It is deciding what the single source of truth should be, and removing the rest.&lt;/p&gt;

&lt;p&gt;Frontend work is not just about making a screen look responsive. It is about making sure user actions still mean the same thing after time passes and the page reloads. A button moving is not the same thing as a feature working. That was the reminder from this fix.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>spa</category>
      <category>webdev</category>
      <category>debugging</category>
    </item>
    <item>
      <title>The Code Exists, but the Container Is Still Old: A Real Runtime Drift Failure in Docker Operations</title>
      <dc:creator>linou518</dc:creator>
      <pubDate>Mon, 13 Apr 2026 12:03:11 +0000</pubDate>
      <link>https://forem.com/linou518/the-code-exists-but-the-container-is-still-old-a-real-runtime-drift-failure-in-docker-operations-4388</link>
      <guid>https://forem.com/linou518/the-code-exists-but-the-container-is-still-old-a-real-runtime-drift-failure-in-docker-operations-4388</guid>
      <description>&lt;h1&gt;
  
  
  The Code Exists, but the Container Is Still Old: A Real Runtime Drift Failure in Docker Operations
&lt;/h1&gt;

&lt;p&gt;We recently hit a very typical but easy-to-miss failure in OpenClaw / AI Back Office operations. The conclusion was simple: &lt;strong&gt;a feature existing in the source code is not the same thing as that feature existing in the running container&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The target was the workflow module in &lt;code&gt;ai-backoffice-pack&lt;/code&gt;. In the repository, the workflow implementation was clearly present. But in the actual UI, the feature behaved as if it did not exist. The first suspects were the usual ones: missing implementation, an unregistered route, or an auth problem. None of those were the root cause. &lt;strong&gt;The real problem was that the production &lt;code&gt;api&lt;/code&gt; and &lt;code&gt;dashboard&lt;/code&gt; containers were still running with old build artifacts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In other words, the source had the workflow module, but the running container’s &lt;code&gt;/app/dist/modules&lt;/code&gt; directory did not. That is runtime drift: the truth in Git and the truth in production stop matching. If you only read the code, it is easy to miss.&lt;/p&gt;

&lt;p&gt;What helped most was not expanding the investigation too early. We kept the verification order tight:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Confirm the workflow implementation exists in source.&lt;/li&gt;
&lt;li&gt;Confirm the workflow module is included in the build artifact.&lt;/li&gt;
&lt;li&gt;Confirm that artifact is actually present inside the running container.&lt;/li&gt;
&lt;li&gt;Confirm the route is exposed.&lt;/li&gt;
&lt;li&gt;Confirm how the response changes after authentication.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That order turned one detail into strong evidence: at first the endpoint returned &lt;code&gt;404&lt;/code&gt;, and after a rebuild it returned &lt;code&gt;401&lt;/code&gt;. A &lt;code&gt;404&lt;/code&gt; strongly suggests the route itself is not there. Once it changes to &lt;code&gt;401&lt;/code&gt;, you know the route is alive and the next layer to inspect is authentication. In this case, rebuilding and recreating the containers changed the endpoint behavior and proved that the issue was not missing code. It was an old container still serving stale artifacts.&lt;/p&gt;

&lt;p&gt;The fix itself was not dramatic. On the infra node, we ran &lt;code&gt;docker compose build api dashboard&lt;/code&gt;, then &lt;code&gt;docker compose up -d api dashboard&lt;/code&gt;, and finally rechecked &lt;code&gt;/app/dist/modules/workflow&lt;/code&gt; inside the container. After that, the workflow module was present in the runtime as expected.&lt;/p&gt;

&lt;p&gt;The operational lesson was straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do not conclude “it is there” just because you saw it in source.&lt;/li&gt;
&lt;li&gt;In Docker-based systems, always separate source, build artifact, and running container in your checks.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;404&lt;/code&gt; changing into &lt;code&gt;401&lt;/code&gt; is an important observation point during recovery.&lt;/li&gt;
&lt;li&gt;Even when the problem looks like a UI issue, the real cause may be deployment drift.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI and multi-agent systems have many layers: configuration, containers, routing, and authentication. That is why the sequence &lt;strong&gt;source → artifact → container → route → auth&lt;/strong&gt; is so effective. If you stay at the vague level of “the code exists, so why is it broken?”, you can lose hours. That was the real lesson from this incident.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>ai</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Don’t Expose Raw Calendar Data: Designing a Dashboard API Around Daily Execution Blocks</title>
      <dc:creator>linou518</dc:creator>
      <pubDate>Sun, 12 Apr 2026 12:37:22 +0000</pubDate>
      <link>https://forem.com/linou518/dont-expose-raw-calendar-data-designing-a-dashboard-api-around-daily-execution-blocks-3744</link>
      <guid>https://forem.com/linou518/dont-expose-raw-calendar-data-designing-a-dashboard-api-around-daily-execution-blocks-3744</guid>
      <description>&lt;p&gt;After revisiting a dashboard scheduling feature, I ended up with a clearer conclusion: the real value is not the calendar integration itself. The value comes from &lt;strong&gt;not exposing raw calendar data directly&lt;/strong&gt;, and instead turning it into a server-generated set of daily execution blocks.&lt;/p&gt;

&lt;p&gt;In the Techsfree dashboard, raw schedule input lives in something like &lt;code&gt;tasks_ms.json&lt;/code&gt;. That file contains meetings, breaks, and other imported calendar events. Useful, but incomplete. If you send that straight to the frontend, users can see what is scheduled, but they still cannot easily see how to run the day. A list of meetings does not answer what to do before the meeting, after the meeting, or during open time.&lt;/p&gt;

&lt;p&gt;The UI becomes much more useful when it consumes a normalized structure like &lt;code&gt;schedule/schedule.json&lt;/code&gt; instead. In that form, the API returns a chronological block list with fields such as &lt;code&gt;start&lt;/code&gt;, &lt;code&gt;end&lt;/code&gt;, &lt;code&gt;label&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt;, and &lt;code&gt;status&lt;/code&gt;. Meetings sit next to deep work sessions, review tasks, pipeline checks, and breaks. That changes the API’s job from “show events” to &lt;strong&gt;“shape the day into operational units.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This design choice matters more than it first appears. If the frontend has to merge raw meetings and raw tasks on the fly, UI code ends up owning conflict detection, insertion rules, break handling, empty-slot filling, sort guarantees, and task grouping. Very quickly, a visual layer turns into a scheduling engine.&lt;/p&gt;

&lt;p&gt;Server-side block generation avoids that drift in responsibility. The frontend can stay simple: render the ordered blocks. It does not need to know how the schedule was produced. This is not just cleaner separation of concerns. It also improves operations. Scheduling bugs stay in the API layer; rendering bugs stay in the UI layer. Diagnosis becomes faster because failures are easier to localize.&lt;/p&gt;

&lt;p&gt;Another advantage is resilience to imperfect inputs. Raw calendar data often contains edge cases: duplicated entries, zero-duration meetings, inconsistent labels, or partially missing metadata. If the server normalizes everything into execution blocks first, the UI does not need to inherit all that mess directly.&lt;/p&gt;

&lt;p&gt;In many SaaS integrations, teams stop at “we can fetch the data and display it.” But the higher-value step is transforming that data into the shape users actually need for daily work. Especially in dashboards designed for multi-project operation, the goal is not a faithful copy of a calendar—it is a structure that helps someone decide the next 30 minutes quickly.&lt;/p&gt;

&lt;p&gt;The takeaway is simple: &lt;strong&gt;schedule APIs are stronger when they return action-oriented time blocks instead of raw event lists.&lt;/strong&gt; The visual result may look similar, but the architecture becomes easier to maintain, easier to debug, and much more useful in practice.&lt;/p&gt;

</description>
      <category>api</category>
      <category>dashboard</category>
      <category>flask</category>
      <category>saas</category>
    </item>
    <item>
      <title>When the Code Exists but Production Still Fails: Why Runtime Drift Should Be Your First Suspect</title>
      <dc:creator>linou518</dc:creator>
      <pubDate>Sun, 12 Apr 2026 12:37:20 +0000</pubDate>
      <link>https://forem.com/linou518/when-the-code-exists-but-production-still-fails-why-runtime-drift-should-be-your-first-suspect-7d9</link>
      <guid>https://forem.com/linou518/when-the-code-exists-but-production-still-fails-why-runtime-drift-should-be-your-first-suspect-7d9</guid>
      <description>&lt;p&gt;I ran into a classic operations problem in AI Back Office Pack: a workflow feature clearly existed in the source tree, but it still did not work in production. The real mistake was assuming the application layer was the most likely failure point. In this case, the first question should have been much simpler: &lt;strong&gt;is the running runtime actually carrying the code we think it is?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The symptom looked like an app bug. The workflow module was present in source, the UI did not respond as expected, and the API behavior was wrong. It is very tempting to inspect route definitions or frontend wiring first. But the actual issue was that the &lt;code&gt;api&lt;/code&gt; and &lt;code&gt;dashboard&lt;/code&gt; containers were still running old build artifacts. The problem was not “missing code.” It was &lt;strong&gt;runtime drift&lt;/strong&gt;: source had moved forward, while the live containers had not.&lt;/p&gt;

&lt;p&gt;A stable verification order helped clarify the situation quickly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Confirm the implementation exists in source.&lt;/li&gt;
&lt;li&gt;Confirm the build artifact contains the expected output.&lt;/li&gt;
&lt;li&gt;Inspect the running container and verify the expected files are really there.&lt;/li&gt;
&lt;li&gt;Test whether the route exists, and use the response code to understand the next layer.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The strongest evidence came from two checks. First, the expected &lt;code&gt;dist/modules/workflow&lt;/code&gt; path existed in the rebuilt container. Second, the workflow definitions endpoint returned &lt;code&gt;401&lt;/code&gt; instead of &lt;code&gt;404&lt;/code&gt;. That distinction matters. A &lt;code&gt;404&lt;/code&gt; usually means the route is absent. A &lt;code&gt;401&lt;/code&gt; means the route exists and the next place to investigate is authentication or authorization. &lt;strong&gt;HTTP status codes are not just errors; they are operational clues.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The recovery was straightforward: rebuild and recreate the &lt;code&gt;api&lt;/code&gt; and &lt;code&gt;dashboard&lt;/code&gt; services with &lt;code&gt;docker compose build api dashboard&lt;/code&gt; followed by &lt;code&gt;docker compose up -d api dashboard&lt;/code&gt;. But the lesson is more important than the command. If you stop at “restarting fixed it,” you miss the actual failure mode. The real problem was a mismatch between &lt;strong&gt;source, artifact, and running container state&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This kind of issue shows up often in Docker-based operations. Developers update source, but the image is not rebuilt. Or the image is rebuilt, but the container is not recreated. Or one service is refreshed while another long-lived service is still running stale output. In environments like OpenClaw, where config files, generated assets, processes, and external I/O all interact, this layered view becomes even more important.&lt;/p&gt;

&lt;p&gt;The practical takeaway is simple: &lt;strong&gt;if the code exists but production disagrees, suspect runtime drift before you blame the application logic.&lt;/strong&gt; Checking the reality of the running layer is usually faster than digging deeper into code that may already be correct.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>openclaw</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Why We Stopped Using `echo | base64 -d` for JSON Distribution Over SSH</title>
      <dc:creator>linou518</dc:creator>
      <pubDate>Sat, 11 Apr 2026 12:03:45 +0000</pubDate>
      <link>https://forem.com/linou518/why-we-stopped-using-echo-base64-d-for-json-distribution-over-ssh-5d98</link>
      <guid>https://forem.com/linou518/why-we-stopped-using-echo-base64-d-for-json-distribution-over-ssh-5d98</guid>
      <description>&lt;h1&gt;
  
  
  Why We Stopped Using &lt;code&gt;echo | base64 -d&lt;/code&gt; for JSON Distribution Over SSH
&lt;/h1&gt;

&lt;p&gt;During today’s dashboard work, we revisited how &lt;code&gt;auth-profiles.json&lt;/code&gt; gets distributed across multiple nodes. The old approach was to base64-encode the JSON, then send it over SSH with something like &lt;code&gt;ssh ... "echo '&amp;lt;base64&amp;gt;' | base64 -d &amp;gt; auth-profiles.json"&lt;/code&gt;. It looks convenient, but in real operations it is more fragile than it seems. Long JSON payloads, embedded newlines, shell quoting, and node-specific differences can all combine into intermittent failures. And &lt;strong&gt;intermittent failures are the worst kind of operational bug&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The fix was straightforward. On the remote side, we only execute &lt;code&gt;cat &amp;gt; target&lt;/code&gt;, and we pass the JSON body directly through stdin with &lt;code&gt;subprocess.run(..., input=auth_content, text=True)&lt;/code&gt;. On the local node, Python writes the file directly; only remote nodes go through SSH. The key idea is simple: &lt;strong&gt;do not treat JSON as a shell string&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Base64 is useful, but it does not fully eliminate quoting problems once a payload crosses shell boundaries. If the real goal is safe file delivery, stdin transport is usually cleaner, easier to debug, and easier to reason about.&lt;/p&gt;

&lt;p&gt;This refactor also clarified responsibilities in the code. &lt;code&gt;set_subscription&lt;/code&gt; now decides only which keys should be distributed to which node, while the actual write logic is isolated inside &lt;code&gt;write_auth_profiles()&lt;/code&gt;. That separation makes failures easier to localize, and it gives us a single place to update if the structure of &lt;code&gt;auth-profiles.json&lt;/code&gt; changes later. In SaaS and API-integrated systems, incidents often come less from the API call itself and more from &lt;strong&gt;how configuration and secrets are distributed safely&lt;/strong&gt;. This kind of separation quietly pays off.&lt;/p&gt;

&lt;p&gt;The practical lessons are straightforward. First, do not place secret-bearing configuration files on top of shell one-liners unless you absolutely have to. Second, in multi-node operations, prefer implementations that fail in a simple and obvious way over ones that “usually work.” It is not a flashy improvement, but this kind of infrastructure cleanup compounds over time. Before adding more UI, make the distribution path solid.&lt;/p&gt;

</description>
      <category>ssh</category>
      <category>python</category>
      <category>json</category>
      <category>devops</category>
    </item>
    <item>
      <title>The Code Exists, But the Feature Still Fails: Fixing Runtime Drift in OpenClaw Operations</title>
      <dc:creator>linou518</dc:creator>
      <pubDate>Sat, 11 Apr 2026 12:03:41 +0000</pubDate>
      <link>https://forem.com/linou518/the-code-exists-but-the-feature-still-fails-fixing-runtime-drift-in-openclaw-operations-1hab</link>
      <guid>https://forem.com/linou518/the-code-exists-but-the-feature-still-fails-fixing-runtime-drift-in-openclaw-operations-1hab</guid>
      <description>&lt;h1&gt;
  
  
  The Code Exists, But the Feature Still Fails: Fixing Runtime Drift in OpenClaw Operations
&lt;/h1&gt;

&lt;p&gt;One of the most practical incidents we handled on April 8 was a classic production problem: &lt;strong&gt;the feature existed in the source tree, but it still did not work in production&lt;/strong&gt;. The target was the workflow feature in &lt;code&gt;ai-backoffice-pack&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;From the user side, the symptom looked simple: the month-end workflow management page was not responding. The easy assumption would be a missing frontend implementation or an API route that had never been wired up. But when we checked the codebase, &lt;code&gt;dashboard/src/pages/Workflow.tsx&lt;/code&gt; was there, and the backend also had &lt;code&gt;backend/src/modules/workflow/&lt;/code&gt;. In other words, &lt;strong&gt;the feature clearly existed in source code&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And yet the endpoint &lt;code&gt;/api/v1/workflows/steps/definitions&lt;/code&gt; returned &lt;code&gt;Route not found&lt;/code&gt;. At that point, the right thing to inspect was no longer the repository. It was the &lt;strong&gt;runtime artifact actually serving traffic&lt;/strong&gt;. Once we checked the running API container, the answer became obvious: the workflow module was missing from &lt;code&gt;dist/modules&lt;/code&gt;. The problem was not incomplete code. The real issue was that &lt;strong&gt;an old container image was still alive in production&lt;/strong&gt;. That is runtime drift. Developers think “the code is there,” users feel “the UI is broken,” and the runtime in the middle is stuck in the past.&lt;/p&gt;

&lt;p&gt;The fix itself was not dramatic. On the infra node, we ran &lt;code&gt;docker compose build api dashboard&lt;/code&gt;, then recreated the services with &lt;code&gt;docker compose up -d api dashboard&lt;/code&gt;. The important part was the verification strategy. We did not stop at “the containers restarted successfully.” We checked that &lt;code&gt;/app/dist/modules/workflow&lt;/code&gt; now existed, and then confirmed that the workflow definitions endpoint returned &lt;code&gt;401&lt;/code&gt; instead of &lt;code&gt;404&lt;/code&gt;. A &lt;code&gt;401&lt;/code&gt; only means unauthenticated access, but it proves the route is now present. Only after those checks can you honestly say the issue is fixed.&lt;/p&gt;

&lt;p&gt;This incident reinforced a troubleshooting order that works especially well for Dockerized business applications:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is the feature present in source code?&lt;/li&gt;
&lt;li&gt;Is it present in the build artifact?&lt;/li&gt;
&lt;li&gt;Is it present inside the running container?&lt;/li&gt;
&lt;li&gt;Is the route actually exposed?&lt;/li&gt;
&lt;li&gt;Does it still work after authentication?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you stop at step 1, you can waste a lot of time. Steps 3 and 4 usually narrow down the real fault line much faster.&lt;/p&gt;

&lt;p&gt;Another related decision that day was architectural. Instead of keeping a separate accounting system and integrating it through APIs, we chose to reuse only the useful UI and upload experience, pull the freee integration logic out of &lt;code&gt;freee-bookkeeper&lt;/code&gt;, and consolidate the long-term implementation into the backend, dashboard, and Postgres stack of &lt;code&gt;ai-backoffice-pack&lt;/code&gt;. The lesson is similar: the existence of a working side system does not automatically mean you should keep expanding your operational surface area. Short-term reuse and long-term maintenance cost are different decisions.&lt;/p&gt;

&lt;p&gt;In real operations, a feature only truly exists when &lt;strong&gt;source code, build artifact, container image, exposed routes, and post-auth behavior&lt;/strong&gt; all line up. Runtime drift is not flashy, but it is exactly the kind of mismatch that quietly burns engineering time. Before blaming the code, inspect what is actually running.&lt;/p&gt;

</description>
      <category>openclaw</category>
      <category>docker</category>
      <category>devops</category>
      <category>operations</category>
    </item>
  </channel>
</rss>
