Keep daemon status working with newer config files#1865
Conversation
Config files can be shared across checkouts with different schema versions. Strip unrecognized keys on load so read paths like daemon status do not fail before they can report state; known malformed fields still fail validation.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5616c6af6a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
|
|
||
| const migrated = stripRemovedConfigFields(parsed); | ||
| const migrated = stripUnrecognizedConfigFields(stripRemovedConfigFields(parsed)); |
There was a problem hiding this comment.
Preserve future config keys before saving
When a newer config contains unknown fields, this now returns a parsed config with those fields stripped; the same loader feeds write paths such as DaemonConfigStore.persistConfig (loadPersistedConfig → merge → savePersistedConfig) and paseo daemon set-password. In that downgrade/newer-config scenario, saving any unrelated setting rewrites config.json without the future sections, so the compatibility fix can silently destroy settings written by the newer daemon; the tolerant read path should either retain unknown data for saves or be limited to read-only status flows.
Useful? React with 👍 / 👎.
|
| Filename | Overview |
|---|---|
| packages/server/src/server/persisted-config.ts | All Zod sub-schemas changed from .strict() to .strip(); both mcp and browserTools retain .passthrough() (unchanged, pre-existing). stripRemovedConfigFields pre-processes explicit field removals and now carries a stale comment. |
| packages/server/src/server/persisted-config.test.ts | Existing "rejects unknown logging config fields" test correctly updated to "strips unknown logging config fields"; new regression test covers unknown root, daemon, and nested relay fields through the full loadPersistedConfig path. |
| docs/data-model.md | Two-sentence doc note added accurately describing the new strip-on-read / strict-on-valid-fields behavior; no issues. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Read config.json from disk] --> B[JSON.parse]
B --> C[stripRemovedConfigFields\nexplicitly remove known-removed fields]
C --> D[PersistedConfigSchema.safeParse\nwith .strip on every sub-schema]
D -->|unknown keys silently dropped| E{Valid known fields?}
E -->|Yes| F[Return PersistedConfig\nunknown keys gone]
E -->|No - bad value on known field| G[Throw descriptive error]
H[savePersistedConfig] --> I[PersistedConfigSchema.safeParse]
I -->|TS-typed input| J{Valid known fields?}
J -->|Yes| K[writePrivateFileAtomicSync]
J -->|No| L[Throw error]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[Read config.json from disk] --> B[JSON.parse]
B --> C[stripRemovedConfigFields\nexplicitly remove known-removed fields]
C --> D[PersistedConfigSchema.safeParse\nwith .strip on every sub-schema]
D -->|unknown keys silently dropped| E{Valid known fields?}
E -->|Yes| F[Return PersistedConfig\nunknown keys gone]
E -->|No - bad value on known field| G[Throw descriptive error]
H[savePersistedConfig] --> I[PersistedConfigSchema.safeParse]
I -->|TS-typed input| J{Valid known fields?}
J -->|Yes| K[writePrivateFileAtomicSync]
J -->|No| L[Throw error]
Comments Outside Diff (1)
-
packages/server/src/server/persisted-config.ts, line 356-359 (link)The comment above
stripRemovedConfigFieldssays the function exists "so the strict schema does not reject" unknown keys, but the schema now uses.strip()everywhere, so it would silently discard those fields on its own. The comment no longer explains the actual reason this pre-processing step exists (explicit documentation of intentionally removed fields + COMPAT reference), which could mislead a future maintainer into believing the schema is still strict and this step is the only guard.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Reviews (1): Last reviewed commit: "refactor(config): use schema stripping f..." | Re-trigger Greptile
Linked issue
None found.
Type of change
What does this PR do
Persisted config objects now strip unrecognized keys through Zod instead of failing on them. That keeps read paths like
paseo daemon statusfrom breaking when a config file contains settings written by a newer checkout, while malformed known fields still fail validation.This also documents the config parsing behavior and adds a regression test with unknown root, daemon, and nested daemon fields.
How did you verify it
Unrecognized key; after the fix it passes.npx vitest run packages/server/src/server/persisted-config.test.ts --bail=1npm run lint -- packages/server/src/server/persisted-config.ts packages/server/src/server/persisted-config.test.tsnpm run typecheck:servernpm run format:files -- docs/data-model.md packages/server/src/server/persisted-config.ts packages/server/src/server/persisted-config.test.tsnpm run typecheck.Checklist
npm run typecheckpassesnpm run lintpassesnpm run formatran (Biome)