Skip to content

fix(config): show reflects the #3461 object-store project-config fallback#3477

Merged
max-sixty merged 3 commits into
mainfrom
fix/issue-3476-config-show-object-store
Jul 15, 2026
Merged

fix(config): show reflects the #3461 object-store project-config fallback#3477
max-sixty merged 3 commits into
mainfrom
fix/issue-3476-config-show-object-store

Conversation

@worktrunk-bot

Copy link
Copy Markdown
Collaborator

Problem

wt config show didn't reflect the object-store project-config fallback that #3461 introduced. In the exact state that fix targets — a bare repo whose default branch is checked out in no worktree — config show reported no project config while wt switch resolved and ran the default branch's committed hooks. The command meant to be the user's window into effective config contradicted what runs.

Root cause: config show read project_config_path() and load_project_config() independently. load_project_config() already falls back to the committed default-branch config via git show <default>:.config/wt.toml, but project_config_path() still returns the (missing) on-disk path with no knowledge of that fallback.

  • JSON emitted an internally inconsistent object: "exists": false alongside a populated "config": { … }, with "path" pointing at a file that isn't the real source.
  • Text keyed off project_config_path() alone: it printed Not found and returned without ever showing the config that is actually loaded.

This is a diagnostics inconsistency only — hooks still run correctly. But #3461's motivating scenario is an agent-driven workflow that parks the primary off-branch, so it's exactly the users who'd inspect config show to understand hook behavior, and it told them the opposite of reality.

Solution

Both render paths now consult the object-store fallback when no on-disk file resolves:

  • JSON: when config resolved but not from an existing on-disk file, path surfaces the revision spec (main:.config/wt.toml) and exists is keyed off the loaded config rather than path.exists(), so path/exists/config agree.
  • Text: resolves the effective source the same way ProjectConfig::load does — on-disk file when present, else the committed default-branch config from the object store — labeled @ main:.config/wt.toml (from object store), and dumps the config through the existing deprecation/validation rendering.

Non-fallback cases are unchanged: exists still equals "config resolved" for on-disk configs (an empty file loads to a default Some, a parse error already errors the command), so no existing snapshot or JSON assertion shifts.

Testing

  • New regression test test_bare_repo_config_show_reflects_object_store_fallback sets up the config-present parked state (bare repo, main carries .config/wt.toml, primary parked on feature-x, invoked from a linked worktree whose branch git rm'd the config) and asserts both JSON (path/exists/config agree) and text ((from object store) label, config dumped, never Not found). This case was previously uncovered.
  • Confirmed the test fails on the unfixed code: {"config":{"post-start":"echo hi"},"exists":false,"path":".../other/.config/wt.toml"}.
  • Full config_show (162) and bare_repository modules pass — no snapshot churn.
  • cargo clippy --bins --lib clean.

Maintainer call on presentation

The original issue flagged the exact source label as a judgment call. This PR proposes @ main:.config/wt.toml (from object store) for text and the raw revision spec for JSON path; both are easy to adjust if you'd prefer different wording. The correctness change — showing the config that actually loads — is the substance; the label is cosmetic.


Closes #3476 — automated triage

wt config show read project_config_path() and load_project_config()
independently. In the #3461 state — a bare repo whose default branch
is checked out in no worktree — the config resolves from the object
store, but project_config_path() returns the (missing) on-disk path,
so config show reported the config as absent while its hooks run:

- JSON emitted "exists": false alongside a populated "config", with
  "path" pointing at a file that isn't the source.
- Text keyed off project_config_path() alone and printed "Not found",
  never showing the config that is actually loaded.

Both paths now consult the object-store fallback when no on-disk file
resolves: JSON surfaces the revision spec as "path" and keys "exists"
off the loaded config; text renders the source labeled "(from object
store)" and dumps the config. Adds a regression test asserting config
show output in the config-present parked case (previously uncovered).

Closes #3476

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self-review. The correctness change is sound — both render paths now consult the object-store fallback, the "No project config" vs Not found distinction survives the restructure, and exists = project_config.is_some() stays consistent with the on-disk cases (empty file → default Some, missing → None). One coverage nit worth pre-empting the codecov/patch gate: the .or_else(|| on_disk.clone()) fallback in the JSON path is unreachable and will show as an uncovered patch line — inline suggestion below.

Comment thread src/commands/config/show.rs Outdated
Comment on lines +117 to +120
_ if config.is_some() => repo
.default_branch_project_config_content()
.map(|(_, spec)| spec)
.or_else(|| on_disk.clone()),

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This arm is entered only when config.is_some() and no on-disk file resolved — which in this codebase means the config was loaded from the object store (ProjectConfig::load returns Some only via on-disk or default_branch_project_config_content). So within a single invocation default_branch_project_config_content() here is guaranteed to return Some, and .or_else(|| on_disk.clone()) never runs. It's dead defensively, and since the new test (like every reachable state) always takes the Some path, the closure body will show as an uncovered patch line on codecov/patch. Dropping it also removes the only way path/exists could disagree, which is the invariant the PR is establishing.

Suggested change
_ if config.is_some() => repo
.default_branch_project_config_content()
.map(|(_, spec)| spec)
.or_else(|| on_disk.clone()),
_ if config.is_some() => repo
.default_branch_project_config_content()
.map(|(_, spec)| spec),

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in 74f851f. Dropped .or_else(|| on_disk.clone()) — as noted, the arm is entered only when config.is_some() with no on-disk file, i.e. config came from the object store, so default_branch_project_config_content() is guaranteed Some and the fallback was dead. Removing it also removes the only way path/exists could disagree, which is the invariant this PR establishes.

config_show module still green (134 passed).

The lone `)?;` of the multi-line `No project config` writeln landed on
its own line, so the infallible error branch of `?` (writing to a String
never fails) showed as an uncovered patch line. Collapse the macro so the
`?` shares a line with the executed write.
The .or_else(|| on_disk.clone()) arm is dead: it runs only when
config.is_some() and no on-disk file resolved, which means config came
from the object store, so default_branch_project_config_content() is
guaranteed Some. Dropping it removes an uncovered codecov/patch line and
the only way path/exists could disagree — the invariant this PR
establishes.
@max-sixty max-sixty merged commit 3b6630b into main Jul 15, 2026
39 checks passed
@max-sixty max-sixty deleted the fix/issue-3476-config-show-object-store branch July 15, 2026 09:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automated-fix Automated CI fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

wt config show doesn't reflect the #3461 object-store project-config fallback

2 participants