Skip to content

War stories: what we tried and what went wrong

In rough order of occurrence. Each of these cost real debugging time.

  1. Stale Zigbee group ids lit the wrong room at 3am. Group membership lives in bulb flash. Our new Z2M groups reused ids 1–9; old ZHA-era memberships answered foyer groupcasts — Guest Room chandelier followed the foyer's every move, with EMPTY HA context (radio-level). Foyer AL in sleep mode kept re-lighting it after every manual off. Fix: renumber groups to 101+, wipe bulb group tables. Diagnostic key: logbook events with no context = not HA's doing.
  2. Restart flaps fired door pulses. Contacts re-register unavailable→on at every HA restart; pulse triggers keyed on to: "on" treated that as an opening — a standing-open window lit the backyard at each restart. Fix: from: "off" on every pulse trigger.
  3. AL adapts a ghost after entity renames. AL reads its lights list at startup only. After renaming the group entity, AL computed perfect sleep values for an entity that no longer existed. Reload is NOT enough.
  4. AL flags your own commands as manual control and silently stops adapting — with an async listener, so clearing immediately can lose the race. Clear after ~1s + explicit apply.
  5. Own-motion presence raced the approach trigger. Rooms whose presence group was their own PIRs blocked their own turn-on (guard saw presence freshly on). Walkways needed a fresh-presence grace; mmWave rooms needed it longer (mmWave sees you before the doorway PIR).
  6. The dry-run harness lied. Forged states arrive politely spaced; real bodies trip approach+presence simultaneously. Physical walk tests found what the harness could not.
  7. UC integration hard-hung HA (event loop pinned at 100%, logs frozen, HTTP dead) right after its infrared platform setup. Storage-edit the config entry to disabled + restart. py-spy can't attach in the musl HA image.
  8. MA bind_port foot-gun: putting 443 in the TCP port field makes MA try to bind 443 (traefik owns it) — webserver crashloop. Settings surgery: values.bind_port is the live key, not the sibling.
  9. Satellite1 silent despite healthy everything: XMOS firmware only latches on cold boot. Line-out jack detect also mutes the onboard path. And "no sound" #3: the amp behind the line-out was simply off.
  10. Web player silent on https: NOT mixed content — a stale setSinkId (NotReadableError per track). User fixed by switching codec to FLAC; output-device reset is the general cure.
  11. sendspin-go zombies after server restarts — discover-loop forever, no reconnect. Watchdog on TCP-connection-presence, not process liveness.
  12. "Self-contained" Go binary needed libopus0 on fresh trixie.
  13. Tuya IR blobs are FastLZ-ish compressed uint16 µs timings — decode, convert to PRONTO, and a Zigbee-learned code plays anywhere. The UC dock learner's single-frame normalization is the reason learned power codes fail on standby TVs.
  14. Embedded git repos ate the monorepo twice (ESPHome device-builder turns its config dir into a repo; byos is a clone). gitignore the PATH WITHOUT trailing slash — a gitlink is not a directory. Also: grep -q "path/" substring-matched an existing longer line and skipped writing the ignore entry.
  15. Unquoted heredocs mangle code: bash expanded ${} and backticks inside a template literal being patched via python-in-heredoc. Quote the delimiter or use a real editor tool.
  16. docker exec dies with its ssh session — long builds need docker exec -d with an in-container log, or systemd-run on the host.
  17. OTA "success" that never happened: our update waiter misread update entity attributes and marked 44 devices done in minutes. Waiters must key on terminal states, not attribute presence. (Z2M also quietly serializes concurrent OTA requests.)
  18. byos gotchas: BETTER_AUTH_URL origin check ("invalid origin" on /setup), compose sidecar must join byos_network explicitly (EAI_AGAIN), port 3000 collides with open-webui, and cross-bridge traffic to host ports needs ufw rules — routing sidecar→HA through traefik's 443 was cleaner than another firewall hole.
  19. TRMNL cloud limits: 2KB/12h (free) — a 5KB push 422s and error retries burn the hourly budget into 429 lockout. Degrade paths must catch 422 too. Self-hosting dissolved the whole problem class.
  20. The mystery .48 squatter that blocked av's IP change turned out to be the Sonocotta board on factory firmware — unmasked months later by its MAC during a flash. Label your ESP32s.
  21. A lingering second pipewire seized the audio hardware from the session that owned it (enable-linger for a headless player = two pipewire instances fighting over ALSA). Desktop audio died silently; the USB interface wedged and needed a replug even after the loser was stopped. Rule: the player joins the hardware-owning session as ITS user unit; mask the other user's pipewire (units AND sockets).