fix(onboard): preserve agents.list and bindings on rerun (#84692)#84748
Conversation
|
Codex review: needs maintainer review before merge. Reviewed May 27, 2026, 5:42 AM ET / 09:42 UTC. Summary PR surface: Source +92, Tests +341, Other 0. Total +433 across 13 files. Reproducibility: Do we have a high-confidence way to reproduce the issue? Yes for the core size-drop bypass and dangling-binding misroute path from current-main source plus the PR's terminal proof, but this read-only review did not rerun the exact reporter interactive setup that emptied the arrays. Review metrics: 1 noteworthy metric.
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 detailsBest possible solution: Land after maintainer approval of the stricter config validation/doctor-pruning behavior and required checks; if that upgrade behavior is too disruptive, preserve load compatibility and make dangling bindings a warning plus doctor repair first. Do we have a high-confidence way to reproduce the issue? Do we have a high-confidence way to reproduce the issue? Yes for the core size-drop bypass and dangling-binding misroute path from current-main source plus the PR's terminal proof, but this read-only review did not rerun the exact reporter interactive setup that emptied the arrays. Is this the best way to solve the issue? Is this the best way to solve the issue? Yes, the patch targets both write-time prevention and restart-time validation, with doctor repair for upgrades; the remaining question is maintainer acceptance of the stricter compatibility behavior. AGENTS.md: found and applied where relevant. Codex review notes: model gpt-5.5, reasoning high; reviewed against 329dad23f5e0. Label changesLabel justifications:
Evidence reviewedPR surface: Source +92, Tests +341, Other 0. Total +433 across 13 files. View PR surface stats
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
|
|
ClawSweeper PR egg ✨ Hatched: 🥚 common Clockwork Review Wisp Hatch commandComment Hatchability rules:
Rarity: 🥚 common. What is this egg doing here?
|
…penclaw#84692) ClawSweeper review on PR openclaw#84748 flagged that `src/commands/onboard-non-interactive/remote.ts:44` still hardcoded `allowConfigSizeDrop: true` for ordinary `--mode remote` reruns. Same shape as the local writer this PR already fixed — the io guard's `size-drop:*` block is disabled and a destructive rewrite would commit without rejection. Apply the same reset/import-only policy to remote: - `src/commands/onboard-non-interactive/remote.ts`: `allowConfigSizeDrop` is now `opts.reset === true`. Ordinary remote reruns fail closed on accidental shrink. - `src/commands/onboard-non-interactive.gateway.test.ts`: new regression case "preserves existing agents.list and bindings on remote onboard rerun" — same shape as the local-mode case, asserts the seeded agents/bindings survive the rerun and the write uses `allowConfigSizeDrop: false`.
…penclaw#84692) ClawSweeper review on PR openclaw#84748 flagged that `src/commands/onboard-non-interactive/remote.ts:44` still hardcoded `allowConfigSizeDrop: true` for ordinary `--mode remote` reruns. Same shape as the local writer this PR already fixed — the io guard's `size-drop:*` block is disabled and a destructive rewrite would commit without rejection. Apply the same reset/import-only policy to remote: - `src/commands/onboard-non-interactive/remote.ts`: `allowConfigSizeDrop` is now `opts.reset === true`. Ordinary remote reruns fail closed on accidental shrink. - `src/commands/onboard-non-interactive.gateway.test.ts`: new regression case "preserves existing agents.list and bindings on remote onboard rerun" — same shape as the local-mode case, asserts the seeded agents/bindings survive the rerun and the write uses `allowConfigSizeDrop: false`.
8b6184b to
8923d78
Compare
|
@clawsweeper re-review Rebased onto current |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
) Default ordinary `openclaw onboard` reruns were silently writing the config with `allowConfigSizeDrop: true`, so the generic config-write size-drop guard never fired on a destructive rewrite. Combined with a schema that did not validate `bindings[].agentId` against `agents.list`, a stripped config could be committed, then load cleanly at the next gateway restart while every bound account misrouted. Gate the size-drop bypass behind explicit reset/import flows: - `src/wizard/setup.ts`: `writeWizardConfigFile(config, opts?)` now takes an `allowConfigSizeDrop` flag that defaults to `false`. A new `configResetPerformed` boolean is set only when the user picks the reset action, and is threaded into every write call. Migration import passes `true` explicitly because that flow legitimately shrinks the config. - `src/commands/onboard-non-interactive/local.ts`: `allowConfigSizeDrop` is now `opts.reset === true` instead of hardcoded `true`. - `src/config/zod-schema.ts`: the top-level `superRefine` now rejects any `bindings[].agentId` that is missing from `agents.list`, mirroring the existing broadcast check. Skipped when `agents.list` is empty so legacy configs without a populated agents list still load. Tests: - `src/commands/onboard-config.test.ts`: rerun preserves `agents.list` + `bindings`. - `src/commands/onboard-non-interactive.gateway.test.ts`: non-interactive rerun preserves seeded agents/bindings and writes with `allowConfigSizeDrop: false`. - `src/config/config.schema-regressions.test.ts`: dangling agentId rejected; valid binding accepted; empty agents.list passthrough. - `src/wizard/setup.test.ts`: updated existing write-options assertion to expect the new safe default.
…claw#84692 lint Resolves the two `pnpm lint` errors flagged by ci `check-lint`: - `typescript(no-unnecessary-type-arguments)`: `readTestConfig<OpenClawConfig>()` is the default type parameter, so omit the explicit `<OpenClawConfig>`. - `typescript(prefer-includes)`: replace `/Unknown agent id "ghost"/.test(message)` with `message.includes('Unknown agent id "ghost"')`. Behavior identical; only lint shape changes.
…penclaw#84692) ClawSweeper review on PR openclaw#84748 flagged that `src/commands/onboard-non-interactive/remote.ts:44` still hardcoded `allowConfigSizeDrop: true` for ordinary `--mode remote` reruns. Same shape as the local writer this PR already fixed — the io guard's `size-drop:*` block is disabled and a destructive rewrite would commit without rejection. Apply the same reset/import-only policy to remote: - `src/commands/onboard-non-interactive/remote.ts`: `allowConfigSizeDrop` is now `opts.reset === true`. Ordinary remote reruns fail closed on accidental shrink. - `src/commands/onboard-non-interactive.gateway.test.ts`: new regression case "preserves existing agents.list and bindings on remote onboard rerun" — same shape as the local-mode case, asserts the seeded agents/bindings survive the rerun and the write uses `allowConfigSizeDrop: false`.
d092469 to
c825284
Compare
…penclaw#84692) ClawSweeper review on PR openclaw#84748 flagged that `src/commands/onboard-non-interactive/remote.ts:44` still hardcoded `allowConfigSizeDrop: true` for ordinary `--mode remote` reruns. Same shape as the local writer this PR already fixed — the io guard's `size-drop:*` block is disabled and a destructive rewrite would commit without rejection. Apply the same reset/import-only policy to remote: - `src/commands/onboard-non-interactive/remote.ts`: `allowConfigSizeDrop` is now `opts.reset === true`. Ordinary remote reruns fail closed on accidental shrink. - `src/commands/onboard-non-interactive.gateway.test.ts`: new regression case "preserves existing agents.list and bindings on remote onboard rerun" — same shape as the local-mode case, asserts the seeded agents/bindings survive the rerun and the write uses `allowConfigSizeDrop: false`.
0bad5a1 to
0790dd5
Compare
0790dd5 to
ecf2659
Compare
|
Verification for head Behavior addressed: ordinary
|
Fix non-interactive and wizard onboarding reruns so existing agent lists and bindings are preserved unless the user explicitly resets config. Isolate legacy `plugins.installs` migration into its own write so the config size-drop allowance cannot mask unrelated config loss, while preserving new or repaired install records for the final plugin-index commit. Also keep shrinkwrap generation pinned to pnpm-locked transitive patch versions only when the dependency edge still allows that version, and isolate the tooling Vitest shard that mutates process state. Fixes #84692. Replaces #84748. Co-authored-by: yetval <[email protected]>
|
Thanks @yetval. This is now landed via replacement PR #87328 in commit de5971e. I could not update this fork branch directly because maintainer edits were disabled / the branch was not writable from the maintainer remote, so I recreated the fix on a maintainer branch, preserved your co-author credit in the squash commit, and merged that replacement. For future PRs, enabling "Allow edits by maintainers" lets us push small fixups directly to the contributor branch. |
Fix non-interactive and wizard onboarding reruns so existing agent lists and bindings are preserved unless the user explicitly resets config. Isolate legacy `plugins.installs` migration into its own write so the config size-drop allowance cannot mask unrelated config loss, while preserving new or repaired install records for the final plugin-index commit. Also keep shrinkwrap generation pinned to pnpm-locked transitive patch versions only when the dependency edge still allows that version, and isolate the tooling Vitest shard that mutates process state. Fixes openclaw#84692. Replaces openclaw#84748. Co-authored-by: yetval <[email protected]>
Fix non-interactive and wizard onboarding reruns so existing agent lists and bindings are preserved unless the user explicitly resets config. Isolate legacy `plugins.installs` migration into its own write so the config size-drop allowance cannot mask unrelated config loss, while preserving new or repaired install records for the final plugin-index commit. Also keep shrinkwrap generation pinned to pnpm-locked transitive patch versions only when the dependency edge still allows that version, and isolate the tooling Vitest shard that mutates process state. Fixes openclaw#84692. Replaces openclaw#84748. Co-authored-by: yetval <[email protected]>
Fix non-interactive and wizard onboarding reruns so existing agent lists and bindings are preserved unless the user explicitly resets config. Isolate legacy `plugins.installs` migration into its own write so the config size-drop allowance cannot mask unrelated config loss, while preserving new or repaired install records for the final plugin-index commit. Also keep shrinkwrap generation pinned to pnpm-locked transitive patch versions only when the dependency edge still allows that version, and isolate the tooling Vitest shard that mutates process state. Fixes openclaw#84692. Replaces openclaw#84748. Co-authored-by: yetval <[email protected]>
Summary
Fixes #84692 — re-running
openclaw onboardon an existing installation silently discardsagents.listandbindings, collapsing the on-disk config from ~9.9 KB to ~1.8 KB. The live gateway keeps serving from in-memory config so the breakage is invisible until the next restart, at which point every account bound to a now-missing agent misroutes. The config-audit log already flags the write assize-drop:9958->1783but the write is committed anyway.Two compounding root causes:
writeWizardConfigFileinsrc/wizard/setup.ts), non-interactive local (src/commands/onboard-non-interactive/local.ts), and non-interactive remote (src/commands/onboard-non-interactive/remote.ts) — hardcodeallowConfigSizeDrop: true. The generic config-write guard atsrc/config/io.ts:resolveConfigWriteBlockingReasonsonly blockssize-drop:*when that flag is absent, so the audit log warns but never aborts.bindings[].agentIdagainstagents.list. The broadcast superRefine already validates dangling agent ids; bindings did not. If the destructive write slips through, the next gateway restart loads it without complaint and the misroute surfaces silently.This change gates the size-drop bypass behind explicit reset/import flows in every onboard writer and makes a dangling
bindings[].agentIda load-time schema rejection. Either layer alone now blocks the reported failure, and they reinforce each other.What changed
src/wizard/setup.tswriteWizardConfigFile(config, opts?)now takes anallowConfigSizeDropparameter that defaults tofalse. The hardcodedtrueis gone.configResetPerformedboolean tracks whether the user picked the reset action (or the wizard threw the config away viabaseConfig = {}). Every onboard write threads that boolean into the new opts.allowConfigSizeDrop: trueexplicitly because importing legitimately shrinks the running config.src/commands/onboard-non-interactive/local.tsallowConfigSizeDropis nowopts.reset === trueinstead of hardcodedtrue.src/commands/onboard-non-interactive/remote.ts--mode remotewriter; this was missed in the first commit and surfaced by the ClawSweeper review on this PR.src/config/zod-schema.tsOpenClawConfigSchema.superRefinenow walkscfg.bindingsand emits a custom issue (Unknown agent id "<id>" (not in agents.list).) for everybindings[i].agentIdmissing fromcfg.agents.list. Gated onagents.list.length > 0so legacy configs that have not populatedagents.listyet still validate.src/commands/onboard-config.test.ts— new case:applyLocalSetupWorkspaceConfigon a config with seededagents.list+bindingspreserves both arrays verbatim across a rerun.src/commands/onboard-non-interactive.gateway.test.ts—replaceConfigFilemock now captureswriteOptionsfor assertions. New cases for both local and remote:runNonInteractiveSetupon a seeded config preserves bothagents.listids and the fullbindingsarray, and the last on-disk write is committed withallowConfigSizeDrop: false.src/config/config.schema-regressions.test.ts— three new cases: danglingbindings[].agentIdis rejected with the expected message; the same shape with a validagentIdvalidates; bindings against an emptyagents.liststill pass (legacy passthrough).src/wizard/setup.test.ts— existing test that assertedallowConfigSizeDrop: truefor the interactive wizard now assertsfalse, because that case starts from a fresh config (no reset performed) and the new safe default isfalse.Real behavior proof
Behavior addressed: ordinary
openclaw onboardrerun silently emptiesagents.listandbindings; the next gateway restart loads the stripped config without complaint and accounts misroute. (#84692)Real environment tested: macOS arm64 (Darwin 25.3.0), Node v25.9.0. Real fs (
node:fs/promises) under amktemp -dHOME. Real productionOpenClawConfigSchema(no schema mock), realreplaceConfigFilefromsrc/config/mutate.ts(no fs mock), and realnode openclaw.mjs onboard --non-interactive ...CLI invocations against the same tmp HOME. Repro scriptproof-84692.tsruns via the project's owntsx(node_modules/.bin/tsx proof-84692.ts), and the CLI is run from the builtdist/. Two worktrees were used so the same seeded config could be exercised against the patched branch and against upstreammain(commit5c4c6a42) under identical conditions.Exact steps or command run after this patch:
Evidence after fix: live terminal output from
tsx proof-84692.tson the patched branch. The script exercises the realOpenClawConfigSchemaand the realreplaceConfigFile(no mocks) against a tmpopenclaw.jsonand prints the result of five scenarios:Scenario 5 is the issue reporter's exact failure mode replayed on real fs: with
allowConfigSizeDrop: true(the value every ordinary onboard write was hardcoding before this PR), the populated 911-byte file is overwritten with the 165-byte shrunk payload,agents.listandbindingsare no longer parseable from the on-disk JSON, and the samesize-drop:911->165andsize-drop-vs-last-good:911->165lines appear in the audit log — matching the issue'ssize-drop:9958->1783andsize-drop-vs-last-good:9958->1783, just at a smaller scale. Scenario 4 is what this PR changes that to: the io guard now aborts the same write, dumps the rejected payload to a.rejected.<ISO>.jsonsidecar, and the on-disk bytes do not move (before=911 after=911).Real CLI rerun against the patched branch with the same seeded 8-agent / 8-binding config (full transcript captured locally, paths redacted to
~):previousBytes=1737 nextBytes=2970is the inverse shape of the bug: the on-disk config grew (wizard added defaults), not shrank. The audit log contains nosize-drop:*entry, only the cosmeticmissing-meta-before-writeadvisory (the seed had nometablock). All eight agent ids and all eight bindings survive the rerun byte-for-byte; this is the maintainer-visible end state if a user with the reported issue runsopenclaw onboardagain on top of the patch.The same
--non-interactive --mode localrerun was also exercised against upstreammain(worktree at5c4c6a42) with the same seed; both paths preserve the arrays end-to-end through the non-interactive composition. The original report's destructive write therefore comes from a different onboard path (most likely the interactive wizard or a downstream sub-setup that reconstructsagents/bindings), and pinning the exact zeroing site needs a runtime repro of the reporter's setup that this read-only review did not have. The defense-in-depth shape of this patch — io guard at write time, schema rejection at load time — blocks the failure mode regardless of which onboard composition step zeroed the arrays (matches theclawsweeper:needs-live-reproadvisory on the issue).Observed result after fix:
ok=true).bindings[].agentIdreplaced with"ghost"is now rejected atbindings.0.agentIdwithUnknown agent id "ghost" (not in agents.list).. Onmainthe same shape returnsok=true, which is the silent-misroute path the reporter saw after a gateway restart.agents.liststill validates.allowConfigSizeDrop: false) shrink from 911 bytes to 165 bytes is rejected withsize-drop:911->165; rejected payload is dumped toopenclaw.json.rejected.<ISO>.json; on-diskopenclaw.jsonis unchanged (before=911 after=911).allowConfigSizeDrop: true(pre-patch behavior) commits — file collapses 911 → 165 bytes,agents.listandbindingsno longer parseable. This is the reporter's bug, reproduced byte-for-byte on real fs.size-drop.What was not tested:
openclaw onboardwizard rerun that wiped the reporter's config in the original ticket was not exercised: the interactive wizard prompts for input that can't be supplied non-interactively, and the destructive composition step inside the wizard sub-setups (channels, gateway, skills, hooks, official plugins, plugin-config) was not pinned in this read-only review. Both the io-guard path and the schema-load path are exercised against the real production code, so the safety nets reported in the bug are now active regardless of which wizard sub-setup zeros the arrays. Matches theclawsweeper:needs-live-reproadvisory on the issue.bindings[].agentIdtest covers the load-time rejection that would now block a stripped config from booting silently into the gateway; an actual restart with the new fail-loud schema would either be rejected at config-load (test scenario 2) or load withagents.listpopulated (the only path that produces a valid config).Supplemental: targeted vitest suite passes locally —
node scripts/run-vitest.mjs run src/wizard/setup.test.ts src/commands/onboard-non-interactive.gateway.test.ts src/commands/onboard-config.test.ts src/config/io.write-prepare.test.ts src/config/config.schema-regressions.test.ts src/config/validation.allowed-values.test.ts→Test Files 6 passed (6), Tests 105 passed (105), Duration 1.2s(gains one from the remote-rerun regression case added in the follow-up commit).src/config/io.write-config.test.ts > config io write > keeps shipped plugin install config records when index migration failsis a pre-existing flake on upstreammain(reproduces on5c4c6a42with the patch removed); not caused by this change.Notes
agents.listis empty/undefined. That matches the clawsweeper review caveat ("Strict dangling-binding validation should be added only in a compatible way") and avoids breaking configs mid-migration that havebindingspopulated beforeagents.list.size-dropaudit log line is unchanged. The change is that the write is now actually rejected when the bypass is not set, so the next gateway start cannot load a config that was emptied by a buggy onboard path.Closes #84692