Skip to content

Safe mode starts with a configuration that does not load, and offers a one-click repair - #284

Merged
fylorn merged 1 commit into
mainfrom
fix/safe-mode-bad-config
Oct 4, 2026
Merged

fylorn merged 1 commit into
mainfrom
fix/safe-mode-bad-config

Conversation

@fylorn

@fylorn fylorn commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

Why

Safe mode exists for a broken configuration, but twcore serve --safe loaded 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 with client_probes.titling: passthrough (removed in 0.60.0).

What

  • Stand-in: when serve --safe cannot 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_rejected says 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_in sets the rejection and clears seen, 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 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, 400 control.config_not_repairable when nothing is fixable.
  • Message codes: serde's two commonest field errors get their own codes with arguments, config.unknown_variant { field, value, expected } and config.unknown_field { field, expected }, instead of English text inside config.unparsable. New control.config_not_repairable.
  • CONTROL_API_VERSION 37.

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 through reload_from_disk.
  • End to end with the built binary: serve --safe on a broken config, twcore call /status, /config/repair GET and POST, file and history checked.
  • cargo fmt --check, cargo clippy --workspace --all-targets -D warnings, cargo test --workspace pass locally.

🤖 Generated with Claude Code

…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>
@fylorn
fylorn merged commit 2a33f9f into main Oct 4, 2026
4 checks passed
@fylorn
fylorn deleted the fix/safe-mode-bad-config branch October 4, 2026 07:19
@fylorn fylorn mentioned this pull request Oct 4, 2026
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