Skip to content

fix: ignore branch markers whose branch no longer exists - #4685

Closed
2023Anita wants to merge 3 commits into
max-sixty:mainfrom
2023Anita:cursor/ignore-stale-branch-markers-87a0
Closed

2023Anita wants to merge 3 commits into
max-sixty:mainfrom
2023Anita:cursor/ignore-stale-branch-markers-87a0

Conversation

@2023Anita

@2023Anita 2023Anita commented Nov 4, 2026 •

Copy link
Copy Markdown

Summary

wt list showed a stale 🤖/💬 when a branch was deleted outside wt (git worktree remove, git branch -D) and the same name was created again. The marker lives at worktrunk.state.<branch>.marker, keyed only by branch name, so the key survived the deletion. #4684 already clears that key when wt itself deletes the branch.

This is the outside-wt recreate case #4684 left open, and only that:

  • wt list skips a marker when git's branch reflog says the name was created after the marker's set_at (delete-and-recreate).
  • The creation time comes from git log -g --date=unix (the oldest branch: Created from … entry), so it works on any ref backend that git records a reflog for. No reflog file parsing.
  • A set_at of 0 (the documented hand-written form) keeps the marker and returns before running git log -g, so hand-written markers cost no extra subprocess.
  • The config key is left in place. wt config state marker get still returns it, and marker clear still removes it.
  • No new command or flag, and no new guide prose. worktrunk.state.<branch>.vars.* is unchanged.

Limits

  • There is no missing-branch check. Git refuses branch -D on a branch that a worktree has checked out. An unborn --orphan branch has no ref yet. A deleted-but-not-recreated name keeps its marker.
  • When git has no creation entry the marker still shows, since that is not evidence the name was reused. That covers an expired reflog, an unborn --orphan branch, and bare repos, where core.logAllRefUpdates is off by default. In a bare repo the fix only applies when reflogs are enabled.

Whether this or a wt config state prune command (suggestion 3) is the better approach is still open on #4683 for a maintainer.

Reference #4683

Test

Integration: test_list_hides_marker_recreated_outside_wt sets a marker, deletes the branch with plain git, recreates it, and checks wt list no longer shows the old marker.

Unit tests cover a recreated branch, unknown branch age (set_at: 0, an expired reflog, and reflogs off), an unborn --orphan branch, a bare repo with and without a recorded reflog, and picking the oldest Created from entry from git log -g output.

A marker is stored only under the branch name. Deleting that branch
outside wt leaves the key, so creating the name again showed the old
marker in wt list. Skip it when no local branch has the name, and when
the branch reflog says this name was created after the marker was set.
The key stays in git config.

Refs max-sixty#4683

Co-authored-by: Lixin2026 <2023Anita@users.noreply.github.com>

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Bare repos still show the old marker. In a bare repo git leaves core.logAllRefUpdates off by default, so logs/refs/heads/<branch> is never written. branch_created_at then returns None and the marker shows. Repos using the reftable backend behave the same way, because their reflogs live inside the tables. I reproduced it with this branch's build: in a bare repo with linked worktrees, git worktree add -b feature, wt config state marker set 🤖, then git worktree remove + git branch -D feature, then recreate feature. wt list still shows 🤖 on the new feature. The bare layout is one Worktrunk supports directly, so the #4683 repro still happens there. The new help sentence says it doesn't. The inline suggestion drops that sentence. AGENTS.md → Documentation also says "A correctness fix that preserves usage needs no new guide prose".

The fix parses git's on-disk reflog format. branch_created_at hard-codes the files-backend path, and creation_timestamp_from_reflog_line re-implements git's reflog line format (zero OID, tz validation, committer-name splitting). AGENTS.md → "Other Tools Own Their Conventions" asks to query the tool instead of encoding its file format. Git answers this directly, and on every ref backend: git log -g --date=unix --format='%gd %gs' refs/heads/<branch> prints entries newest first, so the last one is <branch>@{<unix>} branch: Created from …. That costs a subprocess per worktree row, which is the trade the PR avoided. Whether that cost is acceptable is part of the decision below.

The missing-branch check can't fire for wt list. compute_user_marker runs only for worktree rows, and git refuses branch -D on a branch checked out in any worktree. So a row's branch always exists, apart from an unborn --orphan branch, which has no ref yet and would wrongly lose its marker. All of the PR's effect comes from the reflog comparison.

This needs a maintainer decision. Altogether this is roughly 90 lines of production code plus a format parser, and it only helps non-bare repos on the files backend. The earlier comment on #4683 left the choice between suggestion 2 (this PR) and suggestion 3 (wt config state prune) to a maintainer. A prune command, or documenting marker clear --branch, works the same way on every layout. I'm holding the approval for that choice.

Comment thread src/cli/config.rs Resolved
Ask git for the last `branch: Created from` reflog entry instead of
parsing the files-backend log, so the marker comparison uses whatever
creation time git recorded. Drop the help sentence that claimed a later
branch of the same name always hides the marker.

Co-authored-by: Lixin2026 <2023Anita@users.noreply.github.com>

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The PR description no longer matches the code. It still says wt list "skips a marker when no local branch has that name", and that unit tests cover "a missing branch" and "an empty reflog". This push removed that check and those tests. A deleted-but-not-recreated name now keeps its marker (gone.user_marker(Some("feature")) asserts Some("🤖")), and bare repos keep it unless core.logAllRefUpdates is on. A maintainer deciding between this and a prune command will read the description first, so please update it to match.

The approach question from the earlier review is still open on #4683, so I'm not giving an approval yet.

Comment thread src/git/repository/config.rs Resolved
A hand-written marker stores set_at 0, and that value always keeps the
marker, so git log -g was an extra subprocess on every wt list and
statusline row. Return before asking git.

Co-authored-by: Lixin2026 <2023Anita@users.noreply.github.com>
@max-sixty

Copy link
Copy Markdown
Owner

Thanks for working on this. Closing: it needs a git log -g per marked row on every render, including the statusline, and matches git's reflog message text, which is more than this case is worth. wt now clears a marker whenever it deletes a branch (#4684), and we'll also clear it when wt switch --create creates one. A branch deleted and recreated with plain git keeps its marker until wt config state marker clear. @worktrunk-bot please open that PR, referencing #4683.

This was written by Claude Code on behalf of max-sixty

@worktrunk-bot

Copy link
Copy Markdown
Collaborator

Opened #4704.

max-sixty pushed a commit that referenced this pull request Nov 5, 2026
A branch deleted outside `wt` (for example with `git branch -D`) leaves
its `worktrunk.state.<branch>.marker` key in git config. A later `wt
switch --create` with the same name then showed the old 🤖/💬. Now `wt
switch --create` clears that branch's marker once `git worktree add -b`
succeeds. #4684 already clears the marker on every `wt` deletion path.
This is the follow-up the maintainer asked for when closing #4685.

- The deletion helper from #4684 moves to
`Repository::clear_branch_marker`, and both call sites use it. It is
still best effort: a failed unset is logged at debug level, and only
marker JSON is removed, so a `vars` entry named `marker` that shares the
key is left alone.
- Only `--create` clears the marker. Switching to an existing branch
keeps its marker, and so does a DWIM checkout from a remote branch or a
PR/MR ref.
- A branch deleted and recreated with plain `git` still keeps its marker
until `wt config state marker clear`. The marker help, its generated
docs, and the help snapshot now say so.

`test_switch_create_clears_stale_marker` sets a marker on a branch name
that has no branch and on an existing branch. It then runs `wt switch
--create` for the first and plain `wt switch` for the second. It fails
without the change, because the stale marker survives, and passes with
it. The existing `deletion_*` unit tests pass on the moved helper.

Refs #4683

Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
@2023Anita

Copy link
Copy Markdown
Author

Updated the description to match the current code: the missing-branch check is removed, bare repos are only covered when reflogs are on, and the approach choice vs. a prune command remains with the maintainer on #4683.

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.

4 participants