feat(config): flag stale default-branch cache in wt config state (#3471) - #3478
Conversation
`worktrunk.default-branch` is cached without re-validating against origin/HEAD on every command (a deliberate perf choice), so a default-branch rename followed by `git remote set-head origin -a` is never noticed once the cache is warm — `wt switch --create` keeps branching from the stale base (#3471). Surface the drift on inspection instead of on every command (the key doubles as a user override, so a per-command warning would be noise): `wt config state`'s DEFAULT BRANCH section now compares the cached value against git's local `<remote>/HEAD` and, when they differ, prints a warning plus set/clear remediation hints. The check is local-only (reads the symref, no `git ls-remote`) and fires only when both resolve. `Repository::remote_head()` exposes the primary remote's local HEAD as `(remote, branch)`; the state JSON gains a `remote_head_branch` field so scripts can detect the drift too.
Isolated `)?;` closings on the multi-line writeln! calls left the error -propagation region on a line with no hit region, tripping codecov/patch. Bind the messages and emit with a single-line writeln!, matching the render_shell_status pattern. Output is byte-identical.
worktrunk-bot
left a comment
There was a problem hiding this comment.
The drift check is clean — remote_head() reuses primary_remote()/local_default_branch(), stays local-only (no ls-remote), and correctly returns None (no false warning) when <remote>/HEAD is unset. One convention nit inline, plus one design point for a human call.
Override vs. drift framing (for @max-sixty to weigh): since worktrunk.default-branch doubles as an intentional override, a user who deliberately ran default-branch set develop while origin/HEAD → main now sees, on every wt config state, "Cached branch differs from origin/HEAD (main)" plus a hint to set main — i.e. advice to clobber their deliberate choice. The code comment acknowledges the doubling; the open question is whether the wording ("differs" / "To adopt it") should soften when the value looks like an intentional pin. Not blocking — just flagging the UX for the maintainer's call.
| "Cached branch differs from <bold>{remote}/HEAD</> (<bold>{remote_head}</>)" | ||
| )); | ||
| let hint = hint_message(cformat!( | ||
| "To adopt it, run <underline>wt config state default-branch set {remote_head}</>; to re-detect, run <underline>wt config state default-branch clear</>" |
There was a problem hiding this comment.
The writing-user-outputs skill flags this pattern: hints render as a separate message from the warning above, so "it" (the remote's HEAD branch named on the warning line) has no local referent — Avoid pronouns with cross-message referents … Don't use pronouns like "it" that refer to something mentioned in the error message. Naming {remote}/HEAD directly makes the hint self-contained (remote is already in scope):
| "To adopt it, run <underline>wt config state default-branch set {remote_head}</>; to re-detect, run <underline>wt config state default-branch clear</>" | |
| "To adopt <bold>{remote}/HEAD</>, run <underline>wt config state default-branch set {remote_head}</>; to re-detect, run <underline>wt config state default-branch clear</>" |
Release v0.68.0. Highlights: on-demand picker preview tabs (#3439), bare-repo project config read from the object store when the default branch is checked out nowhere (#3462), shell detection via the process tree (#3455), and `wt config state` flagging a stale default-branch cache (#3478). Full details in CHANGELOG.md. > _This was written by Claude Code on behalf of max_
Closes #3471.
What
wt config state's DEFAULT BRANCH section now calls out when the cachedworktrunk.default-branchhas drifted from git's local<remote>/HEAD— the scenario from #3471, where a GitHub default-branch rename plusgit remote set-head origin -aleaves an existing clone silently branchingwt switch --createoff the stale base.The check is local-only (reads the
<remote>/HEADsymref — nogit ls-remote, no network) and fires only when both the cache and<remote>/HEADresolve and differ. No drift → just the value, as before.Why on inspection, not per-command
As noted in the triage comment,
worktrunk.default-branchdoubles as the storage for an explicit user override (wt config state default-branch set). A per-command warning would fire on every command for anyone who intentionally pinned a non-origin/HEADdefault. Surfacing it only when the user inspects state (matching the warning-placement guidance for "issues that persist until the user fixes them") sidesteps that entirely, which is what yourwt config show/or similarsuggestion pointed at.Placement note: I put this in
wt config staterather thanwt config showbecauseconfig stateis where the default-branch cache is actually displayed (the DEFAULT BRANCH section), and the existingStaleDefaultBrancherror already routes users towt config state default-branch set.config showhas no default-branch section today. Happy to move or mirror it intoconfig showif you'd prefer it there.Changes
Repository::remote_head()— new local-only accessor returning the primary remote's HEAD as(remote, branch); never queries the remote or infers, never persists.wt config state(table): drift warning + set/clear hints in the DEFAULT BRANCH section.wt config state get --format=json: newremote_head_branchfield so scripts can detect drift too.Tests
test_state_show_default_branch_drift_warns— cache stale vsorigin/HEAD→ warning renders.test_state_show_default_branch_no_drift_when_matching— cache matches → no warning.test_state_get_json_reports_remote_head_branch— JSON carries both values.Verified end-to-end against a scratch repo reproducing the exact #3471 steps.