fix(config): three independent deprecation-layer correctness fixes - #2783
Merged
Conversation
Three migrations could silently drop user config when other migrations ran: - migrate_commit_generation_doc / migrate_select_table removed the deprecated section before checking whether the value was a table. If it was malformed (e.g. `commit-generation = "string"`), `modified` stayed `false` and the function returned without re-emitting the doc — but if any sibling migration applied, the mutated (now empty) doc was serialized, silently dropping the malformed value. - migrate_ci_doc removed the entire `[ci]` table after copying only `platform`, silently dropping any other keys in that section. Peek before removing; remove only the migrated key from `[ci]` and drop the section only when it becomes empty. Add regression tests for malformed sibling-migration cases and for `[ci]` with extra keys, and tighten the existing malformed-value tests to assert the value is preserved rather than just absent from the new section. Co-Authored-By: Claude <noreply@anthropic.com>
…rewriting `copy_approved_commands_to_approvals_file` returned `Option<PathBuf>`, collapsing read/parse/save errors into the same `None` used for the benign "nothing to copy" no-op. `wt config update` then rewrote config.toml, silently dropping the legacy approved-commands when the copy actually failed (e.g. unwritable approvals.toml path). Change the return type to `Result<Option<PathBuf>>` and propagate the error from `update`, so a failed copy aborts the whole update with the underlying error rather than dropping approvals. `Ok(None)` is kept for the genuine no-op cases. Add a Unix regression test that makes the directory read-only and asserts the failure is now `Err`. Co-Authored-By: Claude <noreply@anthropic.com>
`replace_deprecated_vars_from_strings` modified the *decoded* TOML string
value and then did `content.replace(decoded, migrated)` against the raw file
text. When the source used escapes (e.g. `pre-start = "echo \"{{ repo_root
}}\""`), the decoded value (`echo "{{ repo_root }}"`) is not present
verbatim in the file, so the replace found nothing and the migration was
silently skipped — while detection still warned, so `config update` could
report a deprecated var yet write the file back unchanged.
Replace the raw-text path with `replace_deprecated_vars_in_doc`, which walks
the parsed `toml_edit` tree and rewrites each string value in place; toml_edit
re-serializes with correct escaping. `compute_migrated_content` now parses
once and threads one document through var + structural migration. Decor is
preserved per-string so unchanged strings keep their original representation.
Regression tests cover the escaped-quote case at both the helper and
`compute_migrated_content` level.
Co-Authored-By: Claude <noreply@anthropic.com>
worktrunk-bot
previously approved these changes
May 17, 2026
worktrunk-bot
left a comment
Collaborator
There was a problem hiding this comment.
All three fixes look right and the tests pin the previously-broken behavior. One non-blocking nit on the new permission test (inline).
worktrunk-bot
left a comment
Collaborator
There was a problem hiding this comment.
codecov/patch is failing at 91.54% vs 97.05% threshold — 17 uncovered lines on src/config/deprecation.rs. Most are easy to close; a few are defensive branches that may or may not be worth testing. Dismissing my approval per the CLAUDE.md codecov rule until this is resolved.
Easy to cover with one or two extra cases:
InlineTablearm inwalk_value(lines 262–267) — never exercised. Adding a config that puts a deprecated var inside an inline table (e.g.entry = { cmd = "{{ repo_root }}" }) would hit it.with_contextonload_from_config_filefailure (lines 1119–1123) — needs a test where the load itself fails. Writing a config with a malformed[projects."..."]block (e.g.approved-commands = "not a list") would surface the error from the strict serde path.compute_migrated_contentno-op return (line 1380, thecontent.to_string()branch) — a test passing a fully-clean config (no deprecations) would hit it.
Defensive / probably-unreachable (worth a quick decision):
ArrayOfTablesarm inwalk_item(lines 238–243) and the trailing_ => {}— worktrunk configs don't use[[arr]], but the existingcollect_strings_from_edit_itemwalker has the same arm. Either keep symmetric and add a#[cfg(test)]exercise via a synthetic[[arr]]doc, or drop the arm (and the symmetric one incollect_strings_from_edit_itemfor consistency)._ => Noneininto_table(line 453) — guarded byis_table_like, so never hit. Could be replaced withunreachable!()(and theexpectcall sites simplified), which removes the branch from coverage entirely.- Test helper's invalid-TOML fallback (line 1719) — all current callers pass valid TOML. Could collapse
replace_deprecated_varsto call.parse().unwrap()and drop the early return.
Happy to push a coverage-fix commit if you'd like — let me know.
worktrunk-bot
dismissed
their stale review
May 17, 2026 21:49
codecov/patch failing (91.54% vs 97.05% threshold); see follow-up comment for the uncovered lines
Adds unit tests for the deprecation infrastructure paths flagged by codecov/patch on the config-fix branch — the general tree-walker arms (array-of-tables, inline-table, non-string scalar), the into_table non-table contract, the compute_migrated_content no-op branch, the replace_deprecated_vars parse-error guard, and the approvals-copy read/parse failure path. Single-deprecation-specific branches (empty [ci] cleanup) are intentionally left out of scope. Co-Authored-By: Claude <noreply@anthropic.com>
for more information, see https://pre-commit.ci
worktrunk-bot
approved these changes
May 17, 2026
Root ignores directory permissions, so the 0o555 read-only tempdir is a no-op and the Err assertion would spuriously fail on root environments (Claude Code web, Docker). Probe-and-skip, matching the established pattern in tests/integration_tests/approval_save.rs. Co-Authored-By: Claude <noreply@anthropic.com>
max-sixty
enabled auto-merge (squash)
May 17, 2026 22:09
3 tasks
max-sixty
added a commit
that referenced
this pull request
May 19, 2026
…s scalar
Three data-loss edges in `compute_migrated_content`:
- `commit = "x"` + `[commit-generation]`: `doc.contains_key("commit")` was
true so no table was created, then `doc["commit"].as_table_mut()` was None,
so the migrated `[commit.generation]` was never inserted — but the source
was already removed. Same shape for `switch = "x"` + `[select]`.
- `args = [1, "--ok"]`: the string filter silently discarded the non-string
element while removing `args`.
Add `can_host_subtable` (parent absent or table) and gate source removal on
it for both top-level and project-level commit-generation and for select.
Require every `args` element be a string before merging; otherwise preserve
`args` unchanged. Regression tests for all three.
Reconstructed against current main: the original clawpatch implementation
sat on a divergent #2783 baseline and its 4-commit Group C stack would not
3-way-merge cleanly on top of #2806/#2783 main without hand-merging
config-migration logic — the exact code where silent corruption is the
defect. Re-derived as one focused change.
Co-Authored-By: Claude <noreply@anthropic.com>
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.
Summary
Three independent correctness fixes in the config-deprecation layer, found during a static review pass. All touch only
src/config/deprecation.rs(plussrc/commands/config/update.rsfor the approvals-copy fix), so they review and land cleanly on their own.[ci]keys — structural migration peeked-and-removed in a way that could drop a deprecated section that failed to parse as a table, and stripped the whole[ci]table instead of just the migratedplatformkey. Now it peeks before removing, only removesplatform, and drops[ci]only when it is left empty — so unrelated[ci]keys and unparseable sections survive migration instead of being silently discarded.config updateaborts before rewriting —copy_approved_commands_to_approvals_fileswallowed I/O errors; a failed copy (e.g. read-only directory) let the rewrite proceed and silently drop approvals. It now returns aResultand the caller propagates with?, so a copy failure aborts the update instead of losing data.toml_edittree, not raw text — variable renaming did a rawstr::replaceon the file text, which corrupted occurrences inside escaped strings. Migration now rewrites the parsedtoml_editdocument and serializes from the tree.Test plan
cargo checkclean on this branch offmain🤖 Generated with Claude Code