fix: prefer native worktree identity for session badges

CLI transcripts persist native worktree-state entries for resume, so the desktop git-info endpoint should treat that state as authoritative for worktree identity. Desktop launch metadata can be stale after placeholder cleanup or reopen, and should only fill gaps.

The displayed business branch still prefers the desktop launch branch because CLI originalBranch means the source checkout before worktree creation, not necessarily the selected base ref.

Constraint: CLI worktree-state carries runtime identity; Desktop repository metadata carries launch intent.

Rejected: Let desktop repository metadata override worktree-state | stale placeholder metadata can resurrect the wrong slug, path, or source cwd after reload.

Confidence: high

Scope-risk: narrow

Directive: Worktree identity fields should prefer CLI worktree-state; only branch display may prefer desktop launch intent.

Tested: bun test src/server/__tests__/sessions.test.ts --test-name-pattern worktree-state|worktree identity|git-info

Tested: cd desktop && bun run lint

Tested: git diff --check
This commit is contained in:
程序员阿江(Relakkes) 2026-05-08 18:37:42 +08:00
parent f46b0c80cd
commit f92083ac0c
2 changed files with 59 additions and 4 deletions

View File

@ -1677,6 +1677,58 @@ describe('Sessions API', () => {
})
})
it('GET /api/sessions/:id/git-info should prefer CLI worktree-state identity over desktop metadata', async () => {
const workDir = await createCleanGitRepo(tmpDir)
const sessionId = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
const activeWorktree = path.join(workDir, '.claude', 'worktrees', 'desktop-main-12345678')
git(workDir, 'worktree', 'add', '-b', 'worktree-desktop-main-12345678', activeWorktree, 'main')
await writeSessionFile(sanitizePath(activeWorktree), sessionId, [
makeSnapshotEntry(),
{
type: 'session-meta',
isMeta: true,
workDir: activeWorktree,
repository: {
requestedWorkDir: '/stale/source',
repoRoot: '/stale/source',
branch: 'main',
worktree: true,
baseRef: 'main',
worktreePath: '/stale/source/.claude/worktrees/stale',
worktreeBranch: 'worktree-stale',
worktreeSlug: 'stale',
},
timestamp: '2026-01-01T00:00:00.000Z',
},
makeWorktreeStateEntry(sessionId, activeWorktree, {
originalCwd: await fs.realpath(workDir),
}),
makeUserEntry('Hello from persisted worktree state'),
])
const res = await fetch(`${baseUrl}/api/sessions/${sessionId}/git-info`)
expect(res.status).toBe(200)
const body = (await res.json()) as {
branch: string | null
worktree: {
path: string | null
plannedPath: string | null
sourceWorkDir: string | null
slug: string | null
branch: string | null
} | null
}
expect(body.branch).toBe('main')
expect(body.worktree).toMatchObject({
path: activeWorktree,
plannedPath: activeWorktree,
sourceWorkDir: await fs.realpath(workDir),
slug: 'desktop-main-12345678',
branch: 'worktree-desktop-main-12345678',
})
})
it('DELETE /api/sessions/:id should delete the session', async () => {
const sessionId = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
await writeSessionFile('-tmp-api-test', sessionId, [makeSnapshotEntry()])

View File

@ -541,15 +541,18 @@ async function getGitInfo(sessionId: string): Promise<Response> {
const launchInfo = await sessionService.getSessionLaunchInfo(sessionId).catch(() => null)
const repository = launchInfo?.repository
const worktreeSession = launchInfo?.worktreeSession
// The visible business branch comes from Desktop's launch choice when present.
// CLI originalBranch is the source checkout before creating the worktree, which
// can differ from the selected base ref.
const sessionBranch = repository?.branch || worktreeSession?.originalBranch || null
const worktree = repository?.worktree || worktreeSession
? {
enabled: true,
path: worktreeSession?.worktreePath || workDir,
plannedPath: repository?.worktreePath || worktreeSession?.worktreePath || null,
sourceWorkDir: repository?.requestedWorkDir || repository?.repoRoot || worktreeSession?.originalCwd || null,
slug: repository?.worktreeSlug || worktreeSession?.worktreeName || null,
branch: repository?.worktreeBranch || worktreeSession?.worktreeBranch || null,
plannedPath: worktreeSession?.worktreePath || repository?.worktreePath || null,
sourceWorkDir: worktreeSession?.originalCwd || repository?.requestedWorkDir || repository?.repoRoot || null,
slug: worktreeSession?.worktreeName || repository?.worktreeSlug || null,
branch: worktreeSession?.worktreeBranch || repository?.worktreeBranch || null,
}
: null