Skip to content

Duplicate is_site_meter bricks boot: config rejection exits before the HTTP server binds #988

Description

@frahlg

Incident (2026-08-29, field report ~10 min after v2.3.0 stable published)

A site on v1.15.0 installed the Easee cloud driver the day before, ending up with is_site_meter: true on both pixii and easee-cloud. v1.15.0 ran fine — SiteMeterDriver() returns the first match, so the duplicate was silently ignored in favor of pixii.

#844 (in v2.0.0) turned the duplicate into a hard Validate() error. On update to v2.3.0 the box crash-looped at boot:

level=ERROR msg="load config" err="exactly one driver may set is_site_meter: true (found 2)"

os.Exit(1) fires before the boot-phase HTTP listener binds, so the box was "stone dead" from the operator's view — host pinged, no UI, no Settings to fix the config with. Recovery required SSH and hand-editing YAML. The updater health-gate correctly judged the deploy failed, but a later restart came up on the new image anyway (separate issue).

Why #844's severity is wrong at boot

#844's intent was right: a silently ignored second site meter means the operator thinks meter B is the site boundary while dispatch trusts meter A. But a boot-time hard failure converts a working site into an unreachable one, on the exact class of box (validated config written by an older version) that can least afford it. Validation strictness must depend on the door:

  • Write path (Settings save, bootstrap POST /api/config): keep the hard error. The operator is present, sees the message, and can fix it before anything persists.
  • Load path (boot, hot reload): tolerate and demote — keep the first declared is_site_meter (identical to the pre-fix(config): reject >3 fuse phases and duplicate site meters #844 behavior every older box already ran with), clear the flag on the rest, log ERROR, and surface a load warning so the ambiguity is visible instead of silent.

This keeps #844's visibility win, removes the brick.

Not in scope

  • Zero site meters still fails at load — that severity predates 1.0, no working box ever booted without one.
  • The update/rollback recovery gap (restart re-applied the rolled-back version) is filed separately.

🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions