plugin install/update: intermittent 'Error: could not load state' on v2.4.6 — installationstate.Save() lacks the atomic-write fix from #4907
Version
v2.4.6 (official release zip via turbot/tap Homebrew, native arm64, macOS 15). Same code present in develop as of 2026-09-12.
What happened
Upgraded 2.4.4→2.4.6 via Homebrew. After two days without steampipe usage, the first session under 2.4.6 ran a loop of steampipe plugin install <name> for 24 plugins: 23 of 24 failed consecutively with Error: could not load state, starting immediately after the FDW extension auto-updated to 2.2.6 in the same session. All succeeded on re-run ~10 minutes later. A single later recurrence also self-healed.
The state file itself was fine
~/.steampipe/internal/update_check.json was valid JSON with correct field types throughout (verified before and after the storm). Its mtime predates one observed failure and nothing modified it before the recovery — the CLI failed installationstate.Load() on a file that parses cleanly, then succeeded against the identical file.
Log lines from the failure window:
2026-09-12 09:55:13.964 UTC [WARN] steampipe [82e6]: Error waiting for notification: read tcp 127.0.0.1:57968->127.0.0.1:9193: use of closed network connection
2026-09-12 09:55:15.179 UTC [WARN] steampipe [82e6]: could not parse install date for :Suspected mechanism (not conclusively reproduced)
pkg/installationstate/state.go Save() is non-atomic: os.Remove(stateFilePath) followed by os.WriteFile(...) (state.go:62). pkg/task/runner.go runs the task runner asynchronously in-process and ends with currentState.Save() (runner.go:132), concurrent with the main command's installationstate.Load() (cmd/plugin.go:270 / :429).
I could not reproduce with synthetic churn of update_check.json (rm+rewrite loop) across 127 plugin install and 85 plugin update runs (0 hits) — so if this is the race, the window is narrower than a bare remove/rewrite cycle, and the incident may require the first-run-after-upgrade conditions (FDW refresh, DB service startup, and pending update notifications all coincided here).
Related prior fix — same bug class, sibling file
#4907 fixed identical corruption in pkg/pluginmanager/state.go (#4760) with a package-level mutex + write-to-temp + os.Rename (merged 2025-11-17, included in 2.4.6). pkg/installationstate/state.go still has the unprotected pattern — in the v2.4.6 tree: pluginmanager/state.go:84,104-113 (mutex + rename) vs installationstate/state.go:62 (bare remove + write).
Suggested fix
Apply the same two-layer pattern from #4907 to installationstate.Save(): mutex + temp file + os.Rename. Additionally, the could not load state wrap at cmd/plugin.go:270/429 discards the underlying error from Load() — including it would make this failure class diagnosable from user output.
Source: turbot/steampipe