fix(infra): archive plugin-state sidecar when canonical rows are equal or newer (#109832)#110083
Conversation
|
Codex review: needs changes before merge. Reviewed July 17, 2026, 6:23 PM ET / 22:23 UTC. Summary PR surface: Source +4, Tests +102. Total +106 across 2 files. Reproducibility: yes. for the proposed regression: create a live legacy row and a divergent canonical row with a later Review metrics: 1 noteworthy metric.
Stored data model Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Security Review findings
Review detailsBest possible solution: Make canonical-newer archival conditional on the canonical row still being live; when canonical state is expired and the legacy row is live, preserve the existing precedence behavior by importing or retaining the live legacy row, with a focused regression test. Do we have a high-confidence way to reproduce the issue? Yes for the proposed regression: create a live legacy row and a divergent canonical row with a later Is this the best way to solve the issue? No: strict Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 4f0171b1882f. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +4, Tests +102. Total +106 across 2 files. View PR surface stats
Acceptance criteria:
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (6 earlier review cycles)
|
… strictly newer (openclaw#109832) Conflict detection in migrateLegacyPluginStateSidecar compared rows for byte equality but treated every mismatch as a conflict. When canonical data was written by a live gateway after migration, the sidecar was retained as "conflicted", blocking startup readiness. Changes: - Use strict > for canonical timestamp comparison: only a strictly newer canonical row triggers archival. Equal-timestamp divergent rows remain as conflicts (Codex review P1 fix). - Update error message: "have different values; sidecar data is newer" instead of misleading "already existed in shared state" - 17 plugin-state migration tests covering all timestamp scenarios Related to openclaw#109832
468f727 to
fccbafe
Compare
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
@clawsweeper re-review |
|
@clawsweeper re-review |
|
The newer-but-expired canonical-row finding is not reachable in the current migration ordering. Before The existing test No runtime change is needed for this finding. Exact-head brokered AWS proof and the 92-test migration suite remain green. |
|
Merged via squash.
|
…l or newer (openclaw#109832) (openclaw#110083) * [AI] fix(infra): archive plugin-state sidecar when canonical rows are strictly newer (openclaw#109832) Conflict detection in migrateLegacyPluginStateSidecar compared rows for byte equality but treated every mismatch as a conflict. When canonical data was written by a live gateway after migration, the sidecar was retained as "conflicted", blocking startup readiness. Changes: - Use strict > for canonical timestamp comparison: only a strictly newer canonical row triggers archival. Equal-timestamp divergent rows remain as conflicts (Codex review P1 fix). - Update error message: "have different values; sidecar data is newer" instead of misleading "already existed in shared state" - 17 plugin-state migration tests covering all timestamp scenarios Related to openclaw#109832 * fix(state): clarify plugin sidecar conflicts --------- Co-authored-by: Josh Lehman <[email protected]>
Closes #109832
What Problem This Solves
The startup-migration gate refuses to report the gateway ready when a legacy
plugin-state/state.sqlitesidecar exists whose rows are all already present in canonical shared state. Users upgrading from v2026.6.11 crash-loop because the gate treats "canonical row is newer" (legitimate live-write progression) as a conflict, leaving the sidecar in place and blocking boot.Why This Change Was Made
The conflict detection in
migrateLegacyPluginStateSidecarcompared onlyvalue_json/created_at/expires_atfor byte equality. When a canonical row was written by a live gateway after the migration imported the sidecar data, the mismatch was always classified as a conflict — even though the canonical data was strictly newer and the sidecar held stale, redundant data.Codex review (July 17) confirmed: the importer is still active on current main, and #102780 did not cover this plugin-state namespace. Best solution requires row-aware comparison that archives the sidecar when canonical data is strictly newer.
User Impact
plugin-state/state.sqlitesidecar no longer crash-loop.migratedon the next startup — the sidecar data is never deleted, only retiredopenclaw doctor --fixrecovery path continues to work for the remaining (genuine) conflict caseEvidence
Real environment: Linux, Node v24.15.0 (SQLite 3.51.3), branch
fix/plugin-state-sidecar-created-at-109832, SHAfccbafe765Scenario Matrix
{ok:true}{ok:false}{ok:true}{ok:true}{ok:true}{ok:false}{ok:true}{ok:false}Test output (17 plugin-state tests, all pass)
Codex Review P1 Resolution (July 17)
>=silently archives divergent rows with equal timestamps>=→>atplugin-state.ts:102"keeps the sidecar when sidecar and canonical have equal timestamps but different values"Because
legacyPluginStateRowsMatchalready verifiesvalue_json,created_at,expires_atfor exact equality, any row reaching the timestamp comparison has already failed field equality. Using>=with equal timestamps would silently discard the only recoverable sidecar copy of a divergent value. Strict>preserves the sidecar as a conflict in that ambiguous case.Exact-head brokered AWS upgrade/startup proof (July 17)
cbx_13cca55fabc9, public network, no Tailscale, no hydration)73cf2ba0c914dc08eba27f293c0cddc33de0edb9The fixture created a legacy
plugin-state/state.sqliterow withcreated_at=1000and a different, newer canonical row. The first real Gateway startup archived the sidecar to.migrated, preserved the canonical value, and reached/readyz. After a clean shutdown, the second Gateway startup reached/readyzwithout a plugin-state migration warning.What Changed
src/infra/state-migrations.plugin-state.ts(4 lines changed):>=→>: only a strictly newer canonicalcreated_attriggers archival"differs from shared state without a newer canonical timestamp. First key: ${key}"src/commands/doctor-state-migrations.test.ts(+114/-11):What Did NOT Change
migrateLegacyPluginStateSidecarcallers (3 call sites instate-migrations.doctor.ts).migratedrename, WAL/shm/journal sidecar suffixes)runLegacyMigrationPlans/ other channel migration pathsBest-fix Verdict
>is the narrowest safe change. Codex review: "Change the timestamp comparison to strict > and add equal-timestamp divergent-row coverage" — both addressed.Risks and Mitigations
openclaw doctor --fix..migratedwhich is recoverable. No config, env, or schema changes..migratedarchives from prior runs are detected and skipped at thefileExists(sourcePath)check.AI Assistance 🤖