Skip to content

fix(config): load custom parameter configs after restart - #568

Merged
buxuku merged 2 commits into
mainfrom
fix/custom-parameter-migration-load-failure
Oct 26, 2026
Merged

buxuku merged 2 commits into
mainfrom
fix/custom-parameter-migration-load-failure

Conversation

@buxuku

@buxuku buxuku commented Oct 26, 2026

Copy link
Copy Markdown
Owner

Summary

Since 3.9.0 the custom parameter dialog can fail with "无法读取参数配置" after the app is restarted:

Error invoking remote method 'config-manager:get': Error: Configuration migration failed: Migration failed: object is not iterable (cannot read property Symbol(Symbol.iterator))

Reproduction: create a provider, add one request-body parameter, quit and relaunch. Retrying never helps, and every provider's parameters are affected, not only the new one.

Root cause

  • The main-process bundle is built by nextron's default Babel preset, which includes @babel/plugin-transform-runtime with corejs: 3. In the shipped bundle Map is core-js-pure's wrapper class, not the native one.
  • performMigrations() copied the stored configurations with structuredClone(this.configurations) (3ce55dd, first released in v3.9.0). structuredClone turns that wrapper into a plain {}.
  • migrationManager.migrateConfigurations() then calls Object.fromEntries({}), which throws the error above.
  • Up to 3.8 a failed migration was swallowed. Since 3.9 it is fatal on purpose (fail closed), so config-manager:get rejects for every provider.
  • It triggers for any stored config whose metadata.version is not 1.2.0: every config created in the UI (the renderer saves configVersion: '1.0.0') and everything saved by 3.8 or earlier.

Why the tests missed it: the persistence unit test ran the manager with the native Map, and the e2e test seeds a 1.2.0 config and only reloads the renderer, so the main process never restarts.

What changed

Two commits:

  1. style(config): the mechanical Prettier reformat of getTaskSnapshot, which the lint-staged pre-commit hook applies to any commit touching configurationManager.ts. Kept apart so the fix stays focused; no behavior change.
  2. fix(config):
    • performMigrations() copies the entries one by one into a new Map (each entry is plain JSON, so structuredClone is fine per entry). Behaviour is otherwise unchanged: migrate a copy, swap it in only after it was persisted.
    • scripts/test-parameter-persistence.cjs runs the manager with the same @babel/runtime-corejs3/core-js-stable/map the production bundle imports, and gains a relaunch scenario: save a 1.0.0 config with a body parameter, restart the manager from the same files, load it, expect 1.2.0, parameter intact and the migrated version persisted.

I went through the other structuredClone calls in main/. They clone IPC payloads or JSON-shaped state; none receives a Map or Set.

Existing installs recover on upgrade: the failed migration throws before anything is written, so the stored configs are intact. Checked with a profile left stuck by the old build; it loads and migrates with the fix.

Verification

  • Test first: without the fix, the new scenario and the existing 0.9.0 migration assertion both fail with the exact error above; with it they pass.
  • Real Electron 30.5.1, with the real ConfigurationManager compiled by nextron's Babel config, run as separate processes (save, quit, relaunch). Before: the exact error from the report, again on every relaunch. After: it loads, is migrated once, and later relaunches are no-ops. The same steps on the v3.8.0 sources also load (control).
  • typecheck, test:parameter-persistence, test:custom-parameters, test:task-submission and the parameter editor jest tests (useParameterConfig, CustomParameterEditor) pass.

Not in this PR

  • The renderer's emptyConfig() uses configVersion: '1.0.0' while the current version is 1.2.0. The first save of a new provider's parameters is therefore migrated on the next launch, and each migration writes a pre-migration-backup-*.json with the full configs (headers included) that is never pruned.
  • Initialization failures in background.ts only go to console.error, not the log files, which made this hard to diagnose from user reports.
  • The e2e test does not restart the main process, so this path is only covered by the unit test.

Mechanical reformat only, no behavior change. The lint-staged pre-commit
hook applies it to any commit that touches this file, so it is kept apart
from the fix that follows.
After a restart the custom parameter dialog failed with "Error invoking
remote method 'config-manager:get': Configuration migration failed:
Migration failed: object is not iterable (cannot read property
Symbol(Symbol.iterator))", and retrying never helped.

The main-process bundle is built by nextron's Babel preset with
@babel/plugin-transform-runtime (corejs: 3), so `Map` there is
core-js-pure's wrapper class. performMigrations() copied the stored
configurations with structuredClone(map) (3ce55dd, first released in
3.9.0), which turns that wrapper into a plain `{}`; migrateConfigurations()
then threw in Object.fromEntries({}). Since 3.9.0 a failed migration is
fatal, so every provider's parameters failed to load. Any config whose
metadata.version is not 1.2.0 triggers it: every config created in the UI
(configVersion '1.0.0') and everything saved by 3.8 or earlier.

Copy the entries one by one into a new Map instead; each entry is plain
JSON. Behaviour is otherwise unchanged: migrate a copy and swap it in only
after it was persisted. The failed attempts never wrote anything, so
existing installs recover on upgrade.

The persistence test now runs the manager with the same core-js-pure Map
as the production bundle and gains a relaunch scenario (save a 1.0.0
config, restart, load). Without the fix, that scenario and the existing
0.9.0 migration assertion both fail with the error above.
@buxuku
buxuku merged commit f7ce204 into main Oct 26, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant