<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sebastian Lim</title>
    <description>The latest articles on DEV Community by Sebastian Lim (@microseyuyu).</description>
    <link>https://dev.to/microseyuyu</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%2F3774195%2F422dd853-f143-4402-91db-e65dbc83ebc9.png</url>
      <title>DEV Community: Sebastian Lim</title>
      <link>https://dev.to/microseyuyu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/microseyuyu"/>
    <language>en</language>
    <item>
      <title>How do you guys manage reusable info from AI conversation history</title>
      <dc:creator>Sebastian Lim</dc:creator>
      <pubDate>Wed, 18 Feb 2026 17:12:25 +0000</pubDate>
      <link>https://dev.to/microseyuyu/how-do-you-guys-manage-reusable-info-from-ai-conversation-history-13m9</link>
      <guid>https://dev.to/microseyuyu/how-do-you-guys-manage-reusable-info-from-ai-conversation-history-13m9</guid>
      <description>&lt;p&gt;As what title said&lt;br&gt;
there are lots of infos from AI could be reused or record as ADR.&lt;/p&gt;

&lt;p&gt;But as the task become more and more complex, more chat history remains.&lt;/p&gt;

&lt;p&gt;I found a way to save these infos and make sure they can be save immediately and find them quickly.&lt;/p&gt;

&lt;p&gt;I use codex to save my conversation history with AI or anything about the task. Ask it to save as a json into my daily note.&lt;br&gt;
If I'm gonna find anything I could also ask codex read the json and find them.&lt;/p&gt;

&lt;p&gt;I wanna write about my solution, but before that I curious about how you guy manage your infos. Whatever it is a chat history from AI, or from project you handle or maybe your interest.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>"Just Patch the Output" — Why Your Clean Architecture Is More Fragile Than My Band-Aids.</title>
      <dc:creator>Sebastian Lim</dc:creator>
      <pubDate>Tue, 17 Feb 2026 04:59:38 +0000</pubDate>
      <link>https://dev.to/microseyuyu/just-patch-the-output-why-your-clean-architecture-is-more-fragile-than-my-band-aids-1nib</link>
      <guid>https://dev.to/microseyuyu/just-patch-the-output-why-your-clean-architecture-is-more-fragile-than-my-band-aids-1nib</guid>
      <description>&lt;p&gt;You spent three months designing the "proper" solution. You contributed upstream. You negotiated with maintainers. You got your pull request reviewed, revised, re-reviewed, and finally merged — into a release that won't ship until next quarter. Meanwhile, your deadline was last Friday.&lt;/p&gt;

&lt;p&gt;I spent an afternoon writing a script that takes the wrong output and makes it right. It's ugly. It's a band-aid. It has seven workarounds that each begin with a comment explaining what upstream should have done differently. And it shipped on Friday.&lt;/p&gt;

&lt;p&gt;Here's the part that will make you uncomfortable: six months later, my band-aid script is still running, still auditable, still does exactly what it says. Your "proper" solution is stuck in a merge conflict with a refactor that a new contributor pushed to the upstream repo you thought was stable.&lt;/p&gt;

&lt;p&gt;I know this because I've been on both sides. I spent v1 doing the "proper" thing — forking four upstream tools, adding real platform support, building "real" fixes. It nearly killed my project. v3 is the recovery: stop fixing the tools. Fix what they produce. &lt;strong&gt;The band-aid &lt;em&gt;is&lt;/em&gt; the strategy.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Lie That Works
&lt;/h2&gt;

&lt;p&gt;I'm porting &lt;a href="https://docs.ros.org/en/jazzy/" rel="noopener noreferrer"&gt;ROS 2 Jazzy&lt;/a&gt; to &lt;a href="https://www.openeuler.org/en/" rel="noopener noreferrer"&gt;openEuler&lt;/a&gt; 24.03 LTS. The ROS build toolchain — bloom, rosdep, rospkg, rosdistro — does not recognize openEuler. It will not generate packages for it. If you ask bloom to build an RPM, it asks the OS detector what system you're on, gets "openEuler," and gives up.&lt;/p&gt;

&lt;p&gt;v1's answer: fork the OS detector, add an &lt;code&gt;OpenEuler(OsDetector)&lt;/code&gt; class, register it as an RHEL clone, maintain four forked repositories. Noble. Principled. And structurally suicidal — any &lt;code&gt;pip install&lt;/code&gt; could overwrite my forks with the official versions, silently killing the pipeline. (I wrote &lt;a href="https://dev.to/microseyuyu/your-fork-will-outlive-your-patience-a-systems-thinking-post-mortem-56pk"&gt;an entire article about this&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;v3's answer: one line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ROS_OS_OVERRIDE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rhel:9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. I'm telling bloom "you're running on RHEL 9." Bloom believes me. It generates RHEL 9 specs. The specs are wrong for openEuler — wrong install paths, wrong dependency names, wrong macros — but bloom &lt;em&gt;runs&lt;/em&gt;. It produces &lt;em&gt;output&lt;/em&gt;. And output can be fixed.&lt;/p&gt;

&lt;p&gt;Is this a lie? Yes. &lt;code&gt;ROS_OS_OVERRIDE&lt;/code&gt; is a documented ROS feature, but it was designed for cross-compilation environments, not for making an unsupported OS masquerade as a supported one. I'm using it for something the ROS maintainers probably didn't intend.&lt;/p&gt;

&lt;p&gt;And it works better than the "honest" approach of teaching the toolchain what openEuler actually is.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix-the-Output Architecture
&lt;/h2&gt;

&lt;p&gt;Here's the entire system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Source Code           Official bloom              My one script
  (ros2.repos)    ---&amp;gt;  (unmodified,          ---&amp;gt;  (fix_specs.py)    ---&amp;gt;  EulerMaker
                         thinks it's RHEL 9)         7 fixes, 1 file         (builds RPMs)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's three arrows. The middle box is someone else's maintained software. The only code I own is the thing on the right.&lt;/p&gt;

&lt;p&gt;Compare to v1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Source Code     ---&amp;gt;  Forked bloom          ---&amp;gt;  Forked rosdep     ---&amp;gt;  Forked rosdistro
  (973 packages,        Forked rospkg               (YAML data,             (more YAML data,
   scraped from          (Python code,               rots silently)           rots silently)
   sitemap)              breaks on pip)

                        ^--- I own ALL of this. Every upstream update is my problem. ---^
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;v1 owned the entire middle of the pipeline. v3 owns a single post-processing script. When upstream ships an update, v1 has to re-patch four repositories. v3 runs &lt;code&gt;pip install --upgrade bloom rosdep&lt;/code&gt; and then checks if &lt;code&gt;fix_specs.py&lt;/code&gt; still produces correct output. Usually it does. The fixes are against &lt;em&gt;output format&lt;/em&gt;, which changes slowly, not against &lt;em&gt;internal implementation&lt;/em&gt;, which changes whenever a maintainer feels like refactoring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The structural difference:&lt;/strong&gt; v1 is coupled to upstream's &lt;em&gt;internals&lt;/em&gt;. v3 is coupled to upstream's &lt;em&gt;outputs&lt;/em&gt;. Outputs are a contract. Internals are not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Seven Band-Aids
&lt;/h2&gt;

&lt;p&gt;Here they are. Every single one. I'm not going to dress them up:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;What It Does&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Adds &lt;code&gt;%bcond_without tests&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Tests don't pass on openEuler yet. Disable them to unblock the initial build.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Adds &lt;code&gt;%global debug_package %{nil}&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Debug packages cause build failures. Not worth debugging until the main build works.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Normalizes &lt;code&gt;Name:&lt;/code&gt; to &lt;code&gt;ros-jazzy-&amp;lt;pkg&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Bloom generates inconsistent package names. EulerMaker rejects them.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Fixes &lt;code&gt;Source0:&lt;/code&gt; path&lt;/td&gt;
&lt;td&gt;Bloom's tarball naming doesn't match openEuler conventions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Rewrites install prefix to &lt;code&gt;/opt/ros/jazzy&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Bloom installs to &lt;code&gt;/usr&lt;/code&gt;. openEuler ROS goes to &lt;code&gt;/opt/ros/jazzy&lt;/code&gt;. Every CMake flag, every Python path, every &lt;code&gt;%files&lt;/code&gt; glob needs rewriting.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Injects &lt;code&gt;PYTHONPATH&lt;/code&gt; into &lt;code&gt;%build&lt;/code&gt;/&lt;code&gt;%install&lt;/code&gt;/&lt;code&gt;%check&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Without this, Python can't find ROS libraries during build. The build environment doesn't set up the path correctly.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Simplifies &lt;code&gt;%files&lt;/code&gt; to &lt;code&gt;/opt/ros/jazzy&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Package everything under the ROS prefix. Bloom's generated &lt;code&gt;%files&lt;/code&gt; section lists paths that don't exist on openEuler.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Look at that table. It's not impressive. There's no clever architecture. There's no abstraction layer. There's no plugin system. It's seven string replacements applied in sequence, each one documented with "why it exists" and "what upstream change would make it unnecessary."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That's the point.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Band-Aids Beat Architecture
&lt;/h2&gt;

&lt;p&gt;I'm going to make an argument that will offend anyone who's read &lt;em&gt;Clean Code&lt;/em&gt;: &lt;strong&gt;a visible band-aid is structurally superior to an invisible "proper" fix.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's the reasoning.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Band-aids are auditable. Proper fixes hide.
&lt;/h3&gt;

&lt;p&gt;Open &lt;code&gt;fix_specs.py&lt;/code&gt;. Read it top to bottom. In under ten minutes, you know every single thing v3 works around. Every platform gap between openEuler and RHEL 9. Every bloom output that needs correction. Every assumption the ROS toolchain makes that doesn't hold.&lt;/p&gt;

&lt;p&gt;Now try doing the same audit on v1's forked bloom. Where are the openEuler-specific changes? Scattered across four repositories, mixed into the existing codebase, buried under commit messages like "fix OS detection" and "add openEuler support." You'd need to diff against upstream to find them all, and upstream has moved since the fork, so the diff includes changes from both sides.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A system whose workarounds are invisible is a system whose costs are invisible.&lt;/strong&gt; And invisible costs compound.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Band-aids are removable. Proper fixes entangle.
&lt;/h3&gt;

&lt;p&gt;Each fix in &lt;code&gt;fix_specs.py&lt;/code&gt; is independent. If openEuler starts shipping &lt;code&gt;python3-flake8-builtins&lt;/code&gt; tomorrow, I delete one function and the rest of the pipeline doesn't notice. If bloom fixes its &lt;code&gt;Name:&lt;/code&gt; generation, I delete another function. No regression risk. No cascading changes.&lt;/p&gt;

&lt;p&gt;Try removing openEuler support from a fork that's had six months of other changes layered on top. The fork has its own git history now. Its own bug fixes. Its own workarounds for things that broke because of the fork. You're not removing a feature — you're performing archaeology.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Band-aids fail loudly. Proper fixes fail silently.
&lt;/h3&gt;

&lt;p&gt;When a band-aid stops working, the spec file is visibly wrong and the build fails immediately. The error message points to the spec. The spec points to &lt;code&gt;fix_specs.py&lt;/code&gt;. The fix function has a docstring explaining why it exists. Diagnosis time: minutes.&lt;/p&gt;

&lt;p&gt;When a fork gets silently overwritten by &lt;code&gt;pip install&lt;/code&gt;, everything looks fine until a build fails with an unrelated error three hours later. Diagnosis time: half a day, minimum, because you're looking in the wrong place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Systems that fail loudly are systems that get fixed. Systems that fail silently are systems that rot.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Uncomfortable Trade-off
&lt;/h2&gt;

&lt;p&gt;I'm not going to pretend v3 has no cost. It does. The cost is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    (New platform issue           (Add fix to
     discovered)            ---&amp;gt;   fix_specs.py)
          ^                             |
          |                             |
          |     ACCUMULATION            v
          |        LOOP              fix_specs.py
          |                          GROWS
          |                             |
          +-----------------------------+
               (More complexity
                to maintain)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The band-aids accumulate. Each new openEuler-vs-RHEL divergence means a new function in &lt;code&gt;fix_specs.py&lt;/code&gt;. The file gets longer. The surface area grows. Eventually, it hits some threshold where the maintenance cost of the post-processing script rivals the maintenance cost of the fork.&lt;/p&gt;

&lt;p&gt;I know this. I chose this consciously. Here's why it's still better:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The accumulation is &lt;em&gt;visible&lt;/em&gt;.&lt;/strong&gt; I can open one file, count the fixes, read their rationale, and make an informed decision about whether the script is still manageable or whether it's time to push fixes upstream. With v1's forks, the equivalent accumulation was happening across four repositories, invisibly, and I didn't realize the cost was unsustainable until the pipeline broke at 3 AM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The accumulation has an exit ramp.&lt;/strong&gt; Each band-aid includes a note: "What would eliminate this fix." Every one of those notes describes an upstream contribution or an openEuler packaging change. When I eventually submit upstream PRs, I know exactly what to submit, because the band-aids are a precise map of the gap between "what exists" and "what's needed."&lt;/p&gt;

&lt;p&gt;v1's forks had no exit ramp. The longer the fork ran, the harder it was to merge back upstream, because the fork and upstream diverged in both directions. That's the difference between linear accumulation (v3) and exponential divergence (v1).&lt;/p&gt;

&lt;h2&gt;
  
  
  The ROS_OS_OVERRIDE Principle
&lt;/h2&gt;

&lt;p&gt;The deeper lesson is not about RPM specs or ROS packaging. It's about a design principle that most software engineers are trained to reject:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you can't change the tool, change its environment, accept its output, and fix the output.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We're taught to fix problems at the source. Don't parse HTML with regex — use a proper parser. Don't post-process compiler output — fix the compiler flags. Don't patch generated code — fix the generator.&lt;/p&gt;

&lt;p&gt;These are good defaults. But they assume you &lt;em&gt;can&lt;/em&gt; fix the source. When you can't — because the source is upstream, because the maintainers are busy, because the fix requires organizational changes that take months — the "proper" approach becomes a blocking dependency on something outside your control.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ROS_OS_OVERRIDE&lt;/code&gt; is the embodiment of this principle. I cannot change how bloom detects operating systems. I cannot add openEuler to rosdistro's supported platform list (not quickly, anyway). But I &lt;em&gt;can&lt;/em&gt; set an environment variable that makes bloom produce &lt;em&gt;usable&lt;/em&gt; output, and then I &lt;em&gt;can&lt;/em&gt; fix that output.&lt;/p&gt;

&lt;p&gt;Is this elegant? No. Is this what the ROS developers intended? Probably not. Does it &lt;em&gt;decouple my project's progress from upstream's review cycle?&lt;/em&gt; Yes. And that decoupling is worth more than any amount of architectural elegance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Engineering Skill
&lt;/h2&gt;

&lt;p&gt;The v1 article was about recognizing structural traps. This one is about something harder: &lt;strong&gt;recognizing when the "right" solution is the wrong strategy.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every senior engineer I've met has an instinct for "doing it properly." It's a good instinct. It produces maintainable code. It produces systems that last. But it also produces a specific failure mode: the project that never ships because someone insisted on getting the architecture right before solving the problem.&lt;/p&gt;

&lt;p&gt;v3 ships. It ships with seven band-aids, a fake OS identity, and a post-processing script that would make any bloom developer cringe. And it's more maintainable, more auditable, and more resilient than the "proper" version ever was.&lt;/p&gt;

&lt;p&gt;The real skill is not knowing how to build the right system. It's knowing when the right system is too expensive, and building the &lt;em&gt;useful&lt;/em&gt; system instead — with full awareness of what you're trading away, documented in a file that anyone can read.&lt;/p&gt;

&lt;p&gt;My band-aids are in &lt;code&gt;fix_specs.py&lt;/code&gt;. Where are yours?&lt;/p&gt;




&lt;p&gt;*The fork trap that v3 replaced: &lt;a href="https://github.com/sebastianhayashi/the_brute_force_probe" rel="noopener noreferrer"&gt;the_brute_force_probe&lt;/a&gt;. The reproducible pipeline: &lt;a href="https://github.com/sebastianhayashi/the_reproducible_pipeline" rel="noopener noreferrer"&gt;the_reproducible_pipeline&lt;/a&gt;. I'm building systems that are honest about their own limitations.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>architecture</category>
      <category>cicd</category>
      <category>programming</category>
    </item>
    <item>
      <title>Your Fork Will Outlive Your Patience. A Systems Thinking Post-Mortem.</title>
      <dc:creator>Sebastian Lim</dc:creator>
      <pubDate>Mon, 16 Feb 2026 13:36:11 +0000</pubDate>
      <link>https://dev.to/microseyuyu/your-fork-will-outlive-your-patience-a-systems-thinking-post-mortem-56pk</link>
      <guid>https://dev.to/microseyuyu/your-fork-will-outlive-your-patience-a-systems-thinking-post-mortem-56pk</guid>
      <description>&lt;p&gt;Every internal fork starts as a one-liner: "we just need to patch this one file." Six months later you're maintaining four parallel repositories, dreading every upstream release, and spending more time keeping your patches alive than building the thing they were supposed to enable.&lt;/p&gt;

&lt;p&gt;I know because I did exactly this. I forked four upstream tools to port 973 ROS packages to an unsupported OS. It worked — 61% of the packages compiled, turtlesim ran, my demo was a success. Then the fork ate me alive.&lt;/p&gt;

&lt;p&gt;This is not a war story. This is a &lt;strong&gt;system dynamics diagnosis&lt;/strong&gt; of why forking upstream tools creates a structural trap that no amount of discipline can outrun.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;I was porting &lt;a href="https://docs.ros.org/en/jazzy/" rel="noopener noreferrer"&gt;ROS 2 Jazzy&lt;/a&gt; (the Robot Operating System) to &lt;a href="https://www.openeuler.org/en/" rel="noopener noreferrer"&gt;openEuler&lt;/a&gt; 24.03 LTS — a Linux distribution that ROS does not officially support. The ROS build toolchain (bloom, rosdep, rospkg, rosdistro) hardcodes its list of supported platforms. openEuler is not on it.&lt;/p&gt;

&lt;p&gt;My options were:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Contribute upstream&lt;/strong&gt; — submit PRs to add openEuler support to the official tools. Slow, dependent on maintainer goodwill, but sustainable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fork everything&lt;/strong&gt; — clone the four repos, add openEuler support myself, build from source. Fast, self-contained, but now I own the maintenance.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I chose option 2. Of course I did. I had a demo to deliver.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix That Fails (R1)
&lt;/h2&gt;

&lt;p&gt;Here's what my fork looked like as a system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        (Problem)                      (Relief)
    TOOLCHAIN DOESN'T    ----------&amp;gt;  TOOLCHAIN WORKS
    RECOGNIZE openEuler                   |
         ^                                |
         |         (Short Term)           |
         |          BALANCING             |
         |            LOOP                |
         |                                v
         +------ &amp;lt;FORK UPSTREAM&amp;gt; ---------+
         |         (Intervention)         |
         |                                |
         |                                |
         |  (Long Term Side-Effect)       |
         |    REINFORCING LOOP (R1)       |
         |    "Fixes that Fail"           |
         |                                |
         |                                v
    +-----------+                  +-----------------+
    | FORK GETS | &amp;lt;--------------- | UPSTREAM MOVES  |
    | OVERWRITTEN|    (Delay)      | (pip install,   |
    | OR STALE  |                  |  new releases)  |
    +-----------+                  +-----------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every &lt;code&gt;pip install&lt;/code&gt; in my build environment could silently overwrite my forked rosdep with the official version. The official version doesn't know openEuler exists. Suddenly my entire pipeline is dead and I'm grepping through pip logs trying to figure out what happened.&lt;/p&gt;

&lt;p&gt;This is textbook &lt;strong&gt;"Fixes that Fail"&lt;/strong&gt; — one of the system archetypes described by Donella Meadows. The fix (forking) addresses the symptom (toolchain doesn't recognize my OS), but it creates a side effect (fragile environment that breaks on any upstream interaction) that makes the original problem recur, harder to diagnose each time.&lt;/p&gt;

&lt;p&gt;The reinforcing loop at the bottom is the killer. The more upstream moves, the more my fork breaks. The more it breaks, the more time I spend re-patching. The more time I spend re-patching, the less time I have to pursue the fundamental solution (contributing upstream). Which means I'm even more dependent on the fork tomorrow than I am today.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Data Decay Loop (R2)
&lt;/h2&gt;

&lt;p&gt;R1 didn't run alone. I also forked &lt;code&gt;rosdistro&lt;/code&gt; — the central database that maps ROS package names to OS-specific dependencies. My fork contained hand-maintained YAML files mapping ROS dependency keys to openEuler package names.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Official rosdistro            Forked rosdistro
    (constantly updated)  ------&amp;gt;  (frozen in time)
          ^                              |
          |                              |
          |       REINFORCING            v
          |         LOOP (R2)        METADATA ROTS
          |      "Data Decay"        (Wrong versions,
          |                           missing packages)
          |                              |
          |                              v
          |                        BUILD FAILURES
          |                        INCREASE
          |                              |
          +------------------------------+
              (Need more manual
               patching of YAML)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every day the official rosdistro receives updates, my fork falls further behind. Every day it falls behind, more builds fail for reasons that have nothing to do with openEuler compatibility — they fail because my metadata is stale.&lt;/p&gt;

&lt;p&gt;I wrote a script (&lt;code&gt;auto_generate_openeuler_yaml.py&lt;/code&gt;) that reads the official YAML and tries to map each dependency to an openEuler package via &lt;code&gt;dnf list&lt;/code&gt;. But this script can only run on an actual openEuler machine. It can't run in CI. It can't run offline. It's a manual process that I have to remember to do, and every time I forget, the data rots a little more.&lt;/p&gt;

&lt;h2&gt;
  
  
  What R1 + R2 Look Like in Practice
&lt;/h2&gt;

&lt;p&gt;Here's the actual data from my system, running on &lt;a href="https://eulermaker.compass-ci.openeuler.openatom.cn/project/overview?osProject=jazzy_ament_package" rel="noopener noreferrer"&gt;EulerMaker&lt;/a&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Architecture&lt;/th&gt;
&lt;th&gt;Success&lt;/th&gt;
&lt;th&gt;Dep Gaps&lt;/th&gt;
&lt;th&gt;Failures&lt;/th&gt;
&lt;th&gt;Interrupted&lt;/th&gt;
&lt;th&gt;Total&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;aarch64&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;606&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;215&lt;/td&gt;
&lt;td&gt;152&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;973&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;x86_64&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;597&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;214&lt;/td&gt;
&lt;td&gt;151&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;973&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;61% success rate. Turtlesim runs. That's the good news.&lt;/p&gt;

&lt;p&gt;The bad news: those 214 dependency gaps and 151 build failures are the &lt;strong&gt;accumulated stock of problems&lt;/strong&gt; that my two reinforcing loops are feeding. Each gap is a place where my forked metadata is wrong or my forked toolchain did something the real toolchain wouldn't. And every time upstream moves, some of those 597 successes will become new failures, because my fork hasn't kept up.&lt;/p&gt;

&lt;p&gt;The system is not failing. The system is &lt;strong&gt;succeeding at producing failures&lt;/strong&gt;, because that's what its structure is designed to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Leverage Point I Missed
&lt;/h2&gt;

&lt;p&gt;In systems thinking, there's a concept called &lt;strong&gt;leverage points&lt;/strong&gt; — places where a small change in structure produces a large change in behavior. Meadows ranked "the rules of the system" as one of the highest leverage points.&lt;/p&gt;

&lt;p&gt;My fork was operating under one implicit rule: &lt;strong&gt;"we maintain our own version of the toolchain."&lt;/strong&gt; This rule forced every interaction with upstream into an adversarial relationship. Upstream updates weren't improvements — they were threats.&lt;/p&gt;

&lt;p&gt;The high-leverage alternative was to change the rule: &lt;strong&gt;"we get our patches accepted upstream."&lt;/strong&gt; Under this rule, every upstream update would be an improvement that includes our platform support. The same force that was destroying my system (upstream momentum) would be sustaining it instead.&lt;/p&gt;

&lt;p&gt;I know why I didn't do this. Contributing upstream is slow, political, and uncertain. Forking is fast, controllable, and certain. But "fast and certain" in the short term turned into "expensive and fragile" in the long term. That's the entire point of the Fixes that Fail archetype — the symptomatic solution is always more attractive in the moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Actually Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A fork is a liability, not an asset.&lt;/strong&gt; The moment you fork, you've created a maintenance obligation that grows with every upstream commit. If you can't get your changes upstream within a bounded timeframe, you are accumulating structural debt that compounds.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data forks are worse than code forks.&lt;/strong&gt; Forking code is bad. Forking data (like my rosdistro YAML files) is worse, because data goes stale silently. Code breaks loudly — a function signature changes and you get a compile error. Data rots quietly — a package version is wrong and you get a mysterious runtime failure three weeks later.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The brute-force approach is valuable — as a probe.&lt;/strong&gt; v1 was not a failure. It was a deliberate brute-force survey that generated an intelligence map: here are the 973 packages, here's which ones work, here's exactly where the gaps are. The failure was in thinking the probe could become the production system. Probes are disposable. Production systems need structural integrity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Know your band-aids.&lt;/strong&gt; I have virtualenv bypasses, RHEL-clone registrations, and frozen YAML snapshots in my system. I know each one is a band-aid. Most teams don't track their band-aids. They accumulate silently until someone asks "why does our build take 45 minutes and fail 30% of the time?" and nobody can answer.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Follow-Up
&lt;/h2&gt;

&lt;p&gt;v1 taught me what a brute-force pipeline looks like when it hits its structural limits. I documented the full system dynamics, including the trap architecture, in the &lt;a href="https://github.com/sebastianhayashi/the_adaptive_verification_engine" rel="noopener noreferrer"&gt;v1 post-mortem repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;v2 was designed to break the cycle: verify before building, not after. Instead of feeding 973 packages into a pipeline and watching 40% of them fail, v2 probes the OS environment first, identifies gaps before consuming build resources, and operates on a verified dependency graph. Details in the &lt;a href="https://github.com/sebastianhayashi/the_adaptive_verification_engine" rel="noopener noreferrer"&gt;v2 Verification Engine repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The structural lesson applies far beyond ROS porting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you're maintaining an internal fork of an OSS library: you're running R1. Get your patches upstream or plan for the maintenance tax.&lt;/li&gt;
&lt;li&gt;If you're patching configuration files that upstream keeps overwriting: you're running R2. Automate the merge or accept the data rot.&lt;/li&gt;
&lt;li&gt;If you're using &lt;code&gt;--skip-broken&lt;/code&gt;, &lt;code&gt;--force&lt;/code&gt;, or &lt;code&gt;|| true&lt;/code&gt; in your build scripts: you're masking symptoms. Each flag is a band-aid. Count them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every fork starts with "just this one patch." Every addiction starts with "just this one hit."&lt;/p&gt;

&lt;p&gt;The system doesn't care about your intentions. It cares about its structure.&lt;/p&gt;




&lt;p&gt;*The v1 post-mortem with system dynamics diagrams: &lt;a href="https://github.com/sebastianhayashi/the_brute_force_probe" rel="noopener noreferrer"&gt;the_brute_force_probe&lt;/a&gt;. The v2 verification engine: &lt;a href="https://github.com/sebastianhayashi/the_adaptive_verification_engine" rel="noopener noreferrer"&gt;the_adaptive_verification_engine&lt;/a&gt;. &lt;/p&gt;

</description>
      <category>devops</category>
      <category>opensource</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>Flaky Tests Are Not a Testing Problem. They're a Feedback Loop You Broke.</title>
      <dc:creator>Sebastian Lim</dc:creator>
      <pubDate>Sun, 15 Feb 2026 15:50:56 +0000</pubDate>
      <link>https://dev.to/microseyuyu/flaky-tests-are-not-a-testing-problem-theyre-a-feedback-loop-you-broke-8j5</link>
      <guid>https://dev.to/microseyuyu/flaky-tests-are-not-a-testing-problem-theyre-a-feedback-loop-you-broke-8j5</guid>
      <description>&lt;p&gt;Every retry rule in your CI pipeline is a painkiller. It suppresses the symptom, the stock of broken code keeps growing underneath, and nobody feels the pain until the whole system is addicted.&lt;/p&gt;

&lt;p&gt;I came across &lt;a href="https://news.ycombinator.com/item?id=46967724" rel="noopener noreferrer"&gt;this post on HN&lt;/a&gt; that perfectly illustrates the pattern: retries everywhere, quarantining tests, adding waits, slowly losing trust in CI signal. The author asked whether flakiness is "a test problem, a product problem, or infrastructure noise."&lt;/p&gt;

&lt;p&gt;It's none of those. It's a &lt;strong&gt;system structure problem&lt;/strong&gt;. And if you look at it through the lens of System Dynamics, the diagnosis becomes obvious.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Addiction Loop (R1)
&lt;/h2&gt;

&lt;p&gt;Every "fix" that masks a failure instead of resolving it feeds a reinforcing loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    (Symptom)                    (Symptom Relief)
    RED PIPELINE  ----------------&amp;gt;  GREEN BUILD
         ^                                |
         |          (Short Term)          |
         |           BALANCING            |
         |             LOOP               |
         |                                v
         +----------- &amp;lt;RETRY&amp;gt; ------------+
         |          (Intervention)        |
         |                                |
         |                                |
         | (Long Term Side-Effect)        |
         |     REINFORCING LOOP (R1)      |
         |     "The Addiction"            |
         |                                |
         |                                v
    +---------+                    +-------------+
    |  MORE   | &amp;lt;----------------- | HIDDEN BUGS |
    | FLAKINESS|      (Delay)       | ACCUMULATE  |
    +---------+                    +-------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at the bottom loop. Every time you hit "retry," you feel good because the light turns green. But you are feeding R1: hidden bugs accumulate, making the system flakier, forcing you to retry even more tomorrow.&lt;/p&gt;

&lt;p&gt;This is textbook &lt;strong&gt;"Shifting the Burden"&lt;/strong&gt; — one of the classic system archetypes identified by Donella Meadows. The short-term fix (retry) actively undermines the long-term solution (actually fixing the bug).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Erosion Loop (R2)
&lt;/h2&gt;

&lt;p&gt;R1 doesn't run alone. It drags a second loop behind it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; Actual Quality           Perceived Quality
    (Lots of Red)  ---------&amp;gt;  (It's just noise)
          ^                           |
          |                           |
          |      REINFORCING          v
          |        LOOP (R2)      LOWER STANDARDS
          |     "The Erosion"     (Normalize Failure)
          |                           |
          |                           |
          +---------------------------+
             (Less debugging,
              more merging)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is &lt;strong&gt;"Drift to Low Performance."&lt;/strong&gt; Because you don't trust the CI signal, you lower your standards. Because you lower standards, you merge worse code. Which makes the signal even less trustworthy. Repeat until your CI pipeline is a decoration.&lt;/p&gt;

&lt;p&gt;The original HN post asked: "how do QA and engineering teams split responsibility?" That's the wrong question. The real question is: &lt;strong&gt;how do you make the pain of instability felt by the person who introduced it?&lt;/strong&gt; Right now, the infrastructure absorbs the pain by retrying, so developers never feel it. They keep submitting flaky code because the system lets them get away with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  I Ran Into the Same Wall
&lt;/h2&gt;

&lt;p&gt;I built a CI pipeline to port an entire ROS 2 Desktop stack onto two non-officially-supported Linux distributions — openEuler (CentOS-based) and openKylin (Ubuntu-based) on RISC-V. Two different base systems, 973 packages, zero upstream CI support.&lt;/p&gt;

&lt;p&gt;My system went through three phases, and they map directly to the dynamics above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v1: The Brute-Force Probe.&lt;/strong&gt; I blindly pulled all 973 packages into the pipeline and let them build. This triggered widespread breakages — but it wasn't a failure, it was a data mining operation. I successfully built 597 packages (proving feasibility), and more importantly, I mapped exactly 214 specific dependency gaps and 151 build failures. The pipeline wasn't meant to pass. It was meant to &lt;strong&gt;make every hidden stock of problems visible.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v2: The Verification Engine.&lt;/strong&gt; Armed with v1's data, I built a system that verifies before building — probing the OS environment to identify dependency gaps &lt;em&gt;before&lt;/em&gt; consuming expensive build resources. Build attempts dropped, success rate went up, because I stopped feeding garbage into the pipeline. (&lt;a href="https://github.com/Sebastianhayashi/the_adaptive_verification_engine" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v3: Incremental Stock Management.&lt;/strong&gt; Instead of tackling everything at once, I identify small batches of problematic dependencies, isolate them into manageable "stocks," and resolve them one group at a time. Subtraction, not addition.&lt;/p&gt;

&lt;h2&gt;
  
  
  My System Is Addicted Too
&lt;/h2&gt;

&lt;p&gt;Here's the part where I punch myself in the face.&lt;/p&gt;

&lt;p&gt;My own CI has the same addiction pattern. I use virtual environments to bypass system dependency conflicts. I have masquerade rules that spoof package identities. If you look at the architecture diagram in my README, you'll spot multiple "intervention" nodes — each one is a band-aid.&lt;/p&gt;

&lt;p&gt;I know this is not sustainable. These are temporary splints, not fixes.&lt;/p&gt;

&lt;p&gt;But here's the difference: &lt;strong&gt;I know these are band-aids.&lt;/strong&gt; Most teams don't. They think retries are "solutions." I know my virtualenv bypass is a temporary splint that I chose consciously, with full awareness of the technical debt I'm taking on. Being aware of the addiction and being consumed by it are two very different things.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Bottleneck
&lt;/h2&gt;

&lt;p&gt;I can identify the stock that's poisoning your pipeline. I can design the feedback loop that makes the right person feel the pain. What I can't do is force an organization to care.&lt;/p&gt;

&lt;p&gt;And that's usually the real bottleneck — not the flaky tests, but the system's refusal to let anyone feel the consequences.&lt;/p&gt;




&lt;p&gt;*If you're dealing with a similar "build-first-verify-never" problem, the &lt;a href="https://github.com/Sebastianhayashi/the_adaptive_verification_engine" rel="noopener noreferrer"&gt;v2 Verification Engine repo&lt;/a&gt; shows this systems thinking approach applied to a real project. I'm looking for exactly these kinds of challenges.&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>codequality</category>
      <category>devops</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
