Summary
Driver Lua source paths are resolved once, at startup. Both the config watcher and POST /api/drivers/{name}/restart reuse the cached absolute path, so when a driver's source legitimately moves — the exact flow the device repository is built for — the driver dies instead of re-resolving, and only a full process restart recovers. On a site whose site meter is that driver, dispatch is inhibited for the whole window.
Hit on real hardware today (v1.15.0, the FoxESS site) while swapping the driver from a local override file to the managed signed beta artifact.
Reproduction (observed live)
- Driver configured
lua: drivers/foxess_h3_smart.lua; the file exists in the user drivers dir (local override).
- Install the managed artifact for the same logical path:
POST /api/device_repository/drivers/foxess_h3_smart/install {"channel":"beta","version":"0.9.0"} → active symlink appears in driver-repository/active/, ftw_signed: true.
- Move the user file aside (the point of the swap) and
POST /api/drivers/foxess/restart →
load lua: read /app/data/drivers/foxess_h3_smart.lua: no such file or directory — the restart reuses the path resolved at startup instead of re-running resolution, which would have found the managed symlink.
- Touch
config.yaml to nudge the watcher → log shows driver removed | name=foxess and then site dispatch inhibited — reverting controllable drivers to autonomous | reason=site_meter_stale. No driver added follows. The managed symlink existed and was valid the whole time.
POST /api/restart → startup resolution runs (config.ResolveDriverPaths: user dir → managed active dir → bundled), logs driver added | path=/app/data/driver-repository/active/foxess_h3_smart.lua, full recovery.
Site-meter outage in step 3–5: about 4 minutes, dispatch correctly inhibited, inverter in native self-use throughout (the safety net behaved exactly right — this is a liveness bug, not a safety bug).
Where
config.ResolveDriverPaths mutates Drivers[i].Lua in place and runs from main.go at startup only. The watcher's reload and the driver restart endpoint both act on the already-resolved absolute path. The fall-through order (user dir → managed active dir → bundled) is correct and did the right thing the one time it ran.
Suggested direction
Re-run path resolution wherever a driver is (re)loaded — the restart endpoint and the reload path — rather than only at startup. Naturally adjacent to #761's single shared configreload.Apply: one apply path that re-resolves would fix both entry points at once. Worth a test that swaps a driver source between the three resolution tiers at runtime, since the device-repository install flow makes that transition a first-class operation now (it is precisely how a site adopts a channel driver over a local file).
Happy to validate a fix on the same hardware.
🤖 Generated with Claude Code
Summary
Driver Lua source paths are resolved once, at startup. Both the config watcher and
POST /api/drivers/{name}/restartreuse the cached absolute path, so when a driver's source legitimately moves — the exact flow the device repository is built for — the driver dies instead of re-resolving, and only a full process restart recovers. On a site whose site meter is that driver, dispatch is inhibited for the whole window.Hit on real hardware today (v1.15.0, the FoxESS site) while swapping the driver from a local override file to the managed signed beta artifact.
Reproduction (observed live)
lua: drivers/foxess_h3_smart.lua; the file exists in the user drivers dir (local override).POST /api/device_repository/drivers/foxess_h3_smart/install {"channel":"beta","version":"0.9.0"}→ active symlink appears indriver-repository/active/,ftw_signed: true.POST /api/drivers/foxess/restart→load lua: read /app/data/drivers/foxess_h3_smart.lua: no such file or directory— the restart reuses the path resolved at startup instead of re-running resolution, which would have found the managed symlink.config.yamlto nudge the watcher → log showsdriver removed | name=foxessand thensite dispatch inhibited — reverting controllable drivers to autonomous | reason=site_meter_stale. Nodriver addedfollows. The managed symlink existed and was valid the whole time.POST /api/restart→ startup resolution runs (config.ResolveDriverPaths: user dir → managed active dir → bundled), logsdriver added | path=/app/data/driver-repository/active/foxess_h3_smart.lua, full recovery.Site-meter outage in step 3–5: about 4 minutes, dispatch correctly inhibited, inverter in native self-use throughout (the safety net behaved exactly right — this is a liveness bug, not a safety bug).
Where
config.ResolveDriverPathsmutatesDrivers[i].Luain place and runs frommain.goat startup only. The watcher's reload and the driver restart endpoint both act on the already-resolved absolute path. The fall-through order (user dir → managed active dir → bundled) is correct and did the right thing the one time it ran.Suggested direction
Re-run path resolution wherever a driver is (re)loaded — the restart endpoint and the reload path — rather than only at startup. Naturally adjacent to #761's single shared
configreload.Apply: one apply path that re-resolves would fix both entry points at once. Worth a test that swaps a driver source between the three resolution tiers at runtime, since the device-repository install flow makes that transition a first-class operation now (it is precisely how a site adopts a channel driver over a local file).Happy to validate a fix on the same hardware.
🤖 Generated with Claude Code