Safe mode starts with a configuration that does not load, and offers a one-click repair - #284
Merged
Merged
Conversation
…a one-click repair Safe mode exists for a broken configuration, but `twcore serve --safe` loaded the configuration before anything else and exited when it did not load, like a normal start. The desktop app saw core fail five times, switched to safe mode, saw it exit again and showed "Stopped" with no reason; the line at fault was only in the log. - `serve --safe`: when the configuration does not load, core serves the control plane with a stand-in (`tw_config::stand_in`): the control key from the file, defaults for everything else. `Status.config_rejected` says which line is wrong from the start. A file too broken to find the control key in still exits, since nobody could connect. - `ConfigManager::standing_in`: the rejection is set and `seen` cleared, so saving the same broken text again does not look like a return to the version in service. - One-click repair (`tw_config::repair`): a value outside its choices goes back to the default (its line is removed), an unknown field is removed. Offered only when the result loads; the original value is masked. `GET /config/repair` lists the fixes, `POST /config/repair` writes them through the normal write path (history, rollback). - serde's two commonest field errors get their own codes with arguments (`config.unknown_variant`, `config.unknown_field`) instead of an English sentence in `config.unparsable`; `control.config_not_repairable` is new. - CONTROL_API_VERSION 37. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Safe mode exists for a broken configuration, but
twcore serve --safeloaded the configuration first and exited when it did not load, like a normal start. The desktop app saw core fail five times, switched to safe mode, saw it exit again and showed "Stopped" with no reason. Found when Lite 2026.10.2 met a config withclient_probes.titling: passthrough(removed in 0.60.0).What
serve --safecannot load the configuration, it serves the control plane with a stand-in (tw_config::stand_in): the control key from the file, defaults for everything else.Status.config_rejectedsays which line is wrong from the start, so a UI that connects later can show it. A file too broken to find the control key in still exits (nobody could connect).ConfigManager::standing_insets the rejection and clearsseen, so saving the same broken text again does not look like a return to the version in service.tw_config::repair): a value outside its choices goes back to the default (its key is removed), an unknown field is removed; emptied parents go too (tw_yaml::remove_key), comments and layout stay. Offered only when the result loads (a half-fixed config is not offered). Shown values are masked like excerpts.GET /config/repair→ConfigRepair { base_version, fixes: [ConfigFix] }POST /config/repair(ConfigRepairRequest { base_version }) recomputes from the file on disk and writes through the normal write path (history, rollback, plugin guard); 409 when stale, 400control.config_not_repairablewhen nothing is fixable.config.unknown_variant { field, value, expected }andconfig.unknown_field { field, expected }, instead of English text insideconfig.unparsable. Newcontrol.config_not_repairable.CONTROL_API_VERSION37.Tests
tw-config: stand-in, field-error codes, repair (single, several, list items, emptied sections, comments kept, half-fixed refused, masking, paths).tw-control/tests/safe_mode.rs: status from the start, plan → repair → swapped in, stale refused, unfixable refused, same broken text does not clear the rejection, a fixed file loads throughreload_from_disk.serve --safeon a broken config,twcore call /status,/config/repairGET and POST, file and history checked.cargo fmt --check,cargo clippy --workspace --all-targets -D warnings,cargo test --workspacepass locally.🤖 Generated with Claude Code