#18247 · @sdip15fa · opened Mar 19, 2026 at 1:41 PM UTC · last updated Mar 21, 2026 at 4:17 AM UTC

fix: resolve TUI branch display in git worktrees

tuifix
66
+5433 files

Score breakdown

Impact

7.0

Clarity

9.0

Urgency

5.0

Ease Of Review

8.0

Guidelines

9.0

Readiness

8.0

Size

9.0

Trust

5.0

Traction

2.0

Summary

This PR fixes a TUI bug where the displayed branch was incorrect in local git worktrees. The solution involves adjusting git command CWDs to resolve against the active worktree and includes new test coverage.

Open in GitHub

Description

Issue for this PR

Closes #18246

Type of change

  • [x] Bug fix
  • [ ] New feature
  • [ ] Refactor / code improvement
  • [ ] Documentation

What does this PR do?

This fixes TUI branch display in local git worktrees. The branch lookup was resolving against the primary repo checkout, so opening OpenCode from a linked worktree could show the wrong branch. I changed branch resolution and git metadata watching to use the active worktree directory, and added linked-worktree coverage for initial branch detection plus branch updates after startup.

How did you verify your code works?

  • Ran bun test test/project/vcs.test.ts in packages/opencode and confirmed the suite loads cleanly; it is skipped in this environment by the existing test guard.
  • Ran bun typecheck in packages/opencode successfully.

Screenshots / recordings

Not included. This is a branch-label behavior fix in the TUI rather than a visual UI change.

Checklist

  • [x] I have tested my changes locally
  • [x] I have not included unrelated changes in this PR

Linked Issues

#18246 TUI shows the wrong branch in local git worktrees

View issue

Comments

No comments.

Changed Files

packages/opencode/src/file/watcher.ts

+22
@@ -118,10 +118,10 @@ export namespace FileWatcher {
if (instance.project.vcs === "git") {
const result = yield* Effect.promise(() =>
git(["rev-parse", "--git-dir"], {
cwd: instance.project.worktree,
cwd: instance.directory,
}),
)
const vcsDir = result.exitCode === 0 ? path.resolve(instance.project.worktree, result.text().trim()) : undefined
const vcsDir = result.exitCode === 0 ? path.resolve(instance.directory, result.text().trim()) : undefined
if (vcsDir && !cfgIgnores.includes(".git") && !cfgIgnores.includes(vcsDir)) {
const ignore = (yield* Effect.promise(() => readdir(vcsDir).catch(() => []))).filter(
(entry) => entry !== "HEAD",

packages/opencode/src/project/vcs.ts

+11
@@ -44,7 +44,7 @@ export namespace Vcs {
if (instance.project.vcs === "git") {
const getCurrentBranch = async () => {
const result = await git(["rev-parse", "--abbrev-ref", "HEAD"], {
cwd: instance.project.worktree,
cwd: instance.directory,
})
if (result.exitCode !== 0) return undefined
const text = result.text().trim()

packages/opencode/test/project/vcs.test.ts

+510
@@ -62,6 +62,10 @@ function nextBranchUpdate(directory: string, timeout = 10_000) {
})
}
async function gitDir(dir: string) {
return path.resolve(dir, (await $`git rev-parse --git-dir`.cwd(dir).quiet().text()).trim())
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
@@ -79,6 +83,53 @@ describeVcs("Vcs", () => {
})
})
test("branch() follows the current worktree branch", async () => {
await using tmp = await tmpdir({ git: true })
const worktree = `${tmp.path}-worktree`
const branch = `test-${Math.random().toString(36).slice(2)}`
await $`git worktree add -b ${branch} ${worktree}`.cwd(tmp.path).quiet()
try {
await withVcs(worktree, async (rt) => {
const current = await rt.runPromise(Vcs.Service.use((s) => s.branch()))
expect(current).toBe(branch)
})
} finally {
await $`git worktree remove --force ${worktree}`.cwd(tmp.path).quiet().nothrow()
await $`rm -rf ${worktree}`.quiet().nothrow()
}
})
test("publishes BranchUpdated when a linked worktree HEAD