<?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: Florian Horner</title>
    <description>The latest articles on DEV Community by Florian Horner (@florianhorner).</description>
    <link>https://dev.to/florianhorner</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%2F3845437%2Fe32ca2c4-1ad5-4bdc-adc9-1573730d5ba3.png</url>
      <title>DEV Community: Florian Horner</title>
      <link>https://dev.to/florianhorner</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/florianhorner"/>
    <language>en</language>
    <item>
      <title>I'm a seller, not a developer — I triaged a Rust crash in a 1,300-star project</title>
      <dc:creator>Florian Horner</dc:creator>
      <pubDate>Wed, 08 Apr 2026 17:23:22 +0000</pubDate>
      <link>https://dev.to/florianhorner/im-a-seller-not-a-developer-i-triaged-a-rust-crash-in-a-1300-star-project-2ln0</link>
      <guid>https://dev.to/florianhorner/im-a-seller-not-a-developer-i-triaged-a-rust-crash-in-a-1300-star-project-2ln0</guid>
      <description>&lt;p&gt;My smart home went dark on a weeknight.&lt;/p&gt;

&lt;p&gt;I got home, tapped the light switch in Home Assistant — nothing. Every Govee light in my apartment — bedroom, living room, kitchen — showed “unavailable.” The service bridging my Govee devices to HA was stuck in a crash loop. I checked the logs and found this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;thread 'main' panicked at 'byte index 1 is not a char boundary; `用` is 3 bytes long'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I don't write Rust. I sell cloud infrastructure at AWS. But my lights were off, the maintainer was asleep in a different timezone, and the issue tracker was filling up with other users hitting the same crash. So I opened Claude Code and started reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  The crash
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/wez/govee2mqtt" rel="noopener noreferrer"&gt;govee2mqtt&lt;/a&gt; is a ~1,300-star Rust project that bridges Govee smart home devices to Home Assistant via MQTT. It's the most complete integration available — and it's maintained by one person, &lt;a href="https://github.com/wez" rel="noopener noreferrer"&gt;wez&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That morning, Govee pushed an API change. Preset names for multi-head lamps switched from English camelCase strings like &lt;code&gt;"colorMode"&lt;/code&gt; to Chinese characters like &lt;code&gt;"用于三灯头中的第二个"&lt;/code&gt; — "for the second of three light heads." Within hours, &lt;a href="https://github.com/wez/govee2mqtt/issues/604" rel="noopener noreferrer"&gt;users started reporting crashes&lt;/a&gt;. The issue thread grew quickly — people whose entire smart home setups had stopped working.&lt;/p&gt;

&lt;p&gt;But the reports were scattered. Some said "my H6076 crashed." Others said "all my devices are unavailable." One person was trying to exclude devices via a TOML config that the HA add-on variant doesn't even support. From the outside, these looked like different problems. They weren't.&lt;/p&gt;

&lt;p&gt;The crash happened during device enumeration, which meant it took down the entire bridge — not just the lamps with Chinese preset names, but &lt;em&gt;every&lt;/em&gt; Govee device managed by the service. The watchdog restarted the add-on, it queried the API, hit the same string, panicked again. Infinite crash loop. No workaround from the HA add-on UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the bug
&lt;/h2&gt;

&lt;p&gt;I pointed Claude Code at the panic message. It walked me through how UTF-8 encoding works — ASCII characters take one byte, but Chinese characters take three. Slicing a string at byte position 1 lands in the middle of a multi-byte character. Rust panics rather than silently giving you garbage data.&lt;/p&gt;

&lt;p&gt;Claude traced the stack to a function called &lt;code&gt;camel_case_to_space_separated()&lt;/code&gt; in &lt;code&gt;hass.rs&lt;/code&gt; — it turns strings like &lt;code&gt;"powerSwitch"&lt;/code&gt; into &lt;code&gt;"Power Switch"&lt;/code&gt; for display in Home Assistant. The problem was this line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;camel&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nf"&gt;.to_ascii_uppercase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;camel[..1]&lt;/code&gt; slices the first &lt;em&gt;byte&lt;/em&gt;. For ASCII, one byte equals one character. For &lt;code&gt;用&lt;/code&gt;, three bytes equals one character. Slice at byte 1 — panic.&lt;/p&gt;

&lt;p&gt;The fix: replace byte slicing with Rust's character iterator (&lt;code&gt;chars().next()&lt;/code&gt;), which yields complete characters regardless of byte width, and add an empty-string guard the original never had. The conceptual change was small — stop treating bytes as characters — but it required rewriting how the function bootstraps its first iteration.&lt;/p&gt;

&lt;p&gt;I understood the bug before I understood Rust syntax. I still don't know what &lt;code&gt;let-else&lt;/code&gt; means in Rust's type system. But I knew what the fix was doing because Claude had already explained the problem in terms I could follow. That was enough to review the code, evaluate the approach, and move to the part where I could actually add value.&lt;/p&gt;

&lt;h2&gt;
  
  
  The triage
&lt;/h2&gt;

&lt;p&gt;Here's what I brought to this that had nothing to do with code.&lt;/p&gt;

&lt;p&gt;I sell enterprise cloud infrastructure. My day job is managing complex, multi-stakeholder escalations — customer is down, five teams are pointing fingers, someone needs to cut through the noise and drive resolution. That muscle kicked in immediately.&lt;/p&gt;

&lt;p&gt;The issue thread was chaos. Multiple users, multiple device models, overlapping symptoms, people trying workarounds that couldn't work. From each user's perspective, their individual problem was unique: "my floor lamp crashed," "my lights are unavailable," "the add-on keeps restarting." From my perspective — after reading the reports and the crash log — they were all the same bug.&lt;/p&gt;

&lt;p&gt;So I wrote a &lt;a href="https://github.com/wez/govee2mqtt/issues/604" rel="noopener noreferrer"&gt;structured incident report&lt;/a&gt; in the issue thread:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Affected devices:&lt;/strong&gt; Specific device names, models, and SKUs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Symptoms:&lt;/strong&gt; Total bridge failure, not just individual device failure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crash log:&lt;/strong&gt; Full stack trace with the exact file and line number&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Root cause:&lt;/strong&gt; Chinese preset names from Govee API hitting byte-indexed string slicing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Impact assessment:&lt;/strong&gt; Infinite crash loop — watchdog restart hits the same panic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why workarounds fail:&lt;/strong&gt; The HA add-on ignores &lt;code&gt;config_path&lt;/code&gt;, so you can't exclude devices via TOML. Cache purge buttons are unavailable because the bridge never starts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suggested fix:&lt;/strong&gt; Use &lt;code&gt;.chars()&lt;/code&gt; instead of byte indexing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That comment did two things. First, it turned a scattered thread into a single, coherent problem statement. Anyone landing on the issue — including the maintainer — could understand the full scope in 60 seconds. Second, it killed the noise. People stopped opening duplicate issues and started consolidating in one thread.&lt;/p&gt;

&lt;p&gt;I also researched every possible workaround and documented which ones actually worked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Remove devices from Govee account&lt;/strong&gt; — works, but you lose app control&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Switch from HA add-on to Docker&lt;/strong&gt; — works, supports TOML config filtering, but requires more setup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wait for the fix&lt;/strong&gt; — the only real answer for most users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSH in and patch the binary&lt;/strong&gt; — fragile, not recommended&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And it didn’t stop with one comment. In the days that followed, I went through new incident reports daily — other users hitting the same crash in different contexts, posting in different threads — and linked them all back to the central issue. Every scattered conversation funneled into one place. By the time the maintainer looked at it, the full picture was already assembled.&lt;/p&gt;

&lt;p&gt;Most of this wasn't coding. It was the same work I do in enterprise escalations: consolidate the signal, kill the noise, document the workarounds, and make the resolution path obvious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the merge frictionless
&lt;/h2&gt;

&lt;p&gt;At work, when I need an executive to approve something, I don't send a 40-page deck. I send one slide with the problem, the fix, and the risk of not acting. Same principle here. wez maintains this project solo, in his free time. Every minute he spends understanding a PR is a minute he's not spending on his actual job. My goal was to make the merge a one-click decision.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/theg1nger" rel="noopener noreferrer"&gt;theg1nger&lt;/a&gt; opened &lt;a href="https://github.com/wez/govee2mqtt/pull/606" rel="noopener noreferrer"&gt;PR #606&lt;/a&gt; with the same fix approach I'd identified — converging independently on the same solution. I reviewed their PR and suggested adding regression tests: Chinese characters that triggered the original crash, empty strings, emoji. They added them. The PR was now clean: small diff, clear description, regression tests, no scope creep. One button to merge.&lt;/p&gt;

&lt;p&gt;During this same window, an AI-generated PR (#612, submitted via OpenClaw) appeared. It had the same code fix — but no context, no tests, no triage, no workaround documentation. It was closed. The contrast was instructive: a code diff without understanding isn't a contribution. It's a task for the maintainer to evaluate, verify, and clean up — the opposite of reducing their burden.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fork
&lt;/h2&gt;

&lt;p&gt;But PRs in a one-maintainer project don't merge on your schedule. Meanwhile, people's lights were broken. So I built a fork.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/florianhorner/govee2mqtt" rel="noopener noreferrer"&gt;govee2mqtt-extended&lt;/a&gt; included the fix plus pre-built Home Assistant add-on images. Users could install the patched version directly from the HA add-on store — no Rust toolchain, no Docker setup, no command line. Click install, restart, lights work.&lt;/p&gt;

&lt;p&gt;Multiple users confirmed it working within hours. The fork picked up stars and I added features beyond upstream while maintaining compatibility — things I could now contribute because the codebase was no longer opaque to me. It served as the stopgap until wez merged PR #606 about two weeks later. The fork had done its job: nobody's smart home had to stay broken while the upstream process ran its course.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Day 0:&lt;/strong&gt; Bug reported, crash affecting multiple users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 2:&lt;/strong&gt; Root cause identified, triage complete, workarounds documented&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 5:&lt;/strong&gt; Fork with fix + pre-built addon available to users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~Day 14:&lt;/strong&gt; Fix merged upstream&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;About escalation culture vs. open source:&lt;/strong&gt; At work, when a customer is down, I escalate. There's a severity process, an on-call rotation, an expectation that critical bugs get fixed in hours. None of that exists in open source. The maintainer owes you nothing — they built this for free. The "escalation path" is making the merge so frictionless that saying yes is easier than saying no: clean code, good tests, clear description, zero scope creep, and a triage comment that saves the maintainer from reading a scattered issue thread. I had to invert everything I do at work. Instead of pushing urgency upward, I had to pull obstacles downward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;About what AI actually contributed:&lt;/strong&gt; Claude Code didn't ship this fix. It translated Rust into something I could reason about. I still can't write Rust from scratch — I don't understand the type system, I don't know the borrow checker, I couldn't set up a Cargo project from memory. But I could read the crash log, follow the stack trace, understand why byte indexing fails on UTF-8, and evaluate whether the proposed fix was correct. Claude was the technical translator. The triage, the coordination, the workaround research, the fork distribution, the PR review — that was me applying skills I already had to a domain I'd never worked in.&lt;/p&gt;

&lt;p&gt;The barrier to open source contribution is lower than it’s ever been. Not because AI writes the code for you — another AI submitted the same fix with no tests, no triage, no context, and the PR was closed. The code alone wasn’t enough. What shipped it was everything I did around it: the incident consolidation, the workaround documentation, the test suggestions, the fork that got people’s lights back on while the upstream process ran its course.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/wez/govee2mqtt/issues/604" rel="noopener noreferrer"&gt;Issue #604&lt;/a&gt; — the original crash report&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/wez/govee2mqtt/pull/606" rel="noopener noreferrer"&gt;PR #606&lt;/a&gt; — the merged fix&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/florianhorner/govee2mqtt" rel="noopener noreferrer"&gt;govee2mqtt-extended fork&lt;/a&gt; — the stopgap with pre-built addon images&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rust</category>
      <category>ai</category>
      <category>claudecode</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
