Default new worktrees to the upstream branch (#2848)

* fix(workspaces): default new worktrees to upstream refs

Use the daemon-resolved upstream as the untouched starting ref so unpushed local commits do not leak into new workspaces. Preserve exact base identity for fork remotes while keeping local-first behavior across version drift.

* fix(workspaces): preserve exact base refs in git actions

Resolve accepted display aliases back to the stored ref before comparisons and updates. Fail closed when a non-origin remote base has no explicit mutable merge target.

* fix(workspaces): reject conflicting qualified base refs

Display aliases remain accepted for compatibility, but a qualified ref carries remote identity and must not be canonicalized to a different stored upstream.

* test(server): make upstream fixtures portable

Keep the checkout-status fixture aligned with the optional upstream field, and select main explicitly when cloning a bare test remote whose default HEAD varies by platform.

* fix(workspaces): preserve exact upstream refs

Qualified remote refs now fail closed when pruned instead of switching lineage. Metadata accepts Git-valid upstream names rather than applying a narrower character whitelist.

* fix(workspaces): preserve qualified local base refs

Local tracking branches are exact upstream identities too. Keep heuristics only for legacy unqualified metadata so origin cannot replace a stored refs/heads base.
This commit is contained in:
Mohamed Boudra
2026-08-04 09:01:38 +02:00
committed by GitHub
parent 74dea38456
commit fb657ebc20
17 changed files with 1028 additions and 308 deletions
@@ -1,11 +1,12 @@
import { describe, expect, it } from "vitest";
import type { ForgeSearchItem } from "@getpaseo/protocol/messages";
import type { CheckoutStatusPayload } from "@/git/checkout-status-cache";
import {
type BranchPickerDetail,
branchPickerOptionId,
buildBranchPickerItems,
buildPickerOptionData,
defaultBasePickerItem,
pickerItemToCheckoutRequest,
resolveCheckoutRequest,
type PickerItem,
} from "./new-workspace-picker-item";
@@ -104,7 +105,7 @@ describe("pickerItemToCheckoutRequest", () => {
});
describe("buildBranchPickerItems", () => {
it("hides the origin duplicate when local and origin match", () => {
it("keeps a single origin row when local and origin match", () => {
expect(
buildBranchPickerItems([
{
@@ -120,14 +121,14 @@ describe("buildBranchPickerItems", () => {
{
kind: "branch",
name: "main",
refName: "refs/heads/main",
accessibilityLabel: "main, local branch, up to date with origin main",
refName: "refs/remotes/origin/main",
accessibilityLabel: "main, origin branch",
committerDate: 10,
},
]);
});
it("creates compact local and origin rows when the refs differ", () => {
it("puts the origin row first and disambiguates the local row when the refs differ", () => {
expect(
buildBranchPickerItems([
{
@@ -143,17 +144,16 @@ describe("buildBranchPickerItems", () => {
{
kind: "branch",
name: "main",
refName: "refs/heads/main",
divergenceLabel: "+3 −2",
accessibilityLabel: "main, local branch, 3 commits ahead and 2 behind origin main",
refName: "refs/remotes/origin/main",
accessibilityLabel: "main, origin branch",
committerDate: 10,
},
{
kind: "branch",
name: "origin/main",
refName: "refs/remotes/origin/main",
divergenceLabel: "+2 −3",
accessibilityLabel: "origin main, 2 commits ahead and 3 behind local main",
name: "main (local)",
refName: "refs/heads/main",
divergenceLabel: "+3 −2",
accessibilityLabel: "main, local branch, 3 commits ahead and 2 behind origin main",
committerDate: 10,
},
]);
@@ -172,9 +172,30 @@ describe("buildBranchPickerItems", () => {
).toEqual([
{
kind: "branch",
name: "origin/release",
name: "release",
refName: "refs/remotes/origin/release",
accessibilityLabel: "origin release, origin branch",
accessibilityLabel: "release, origin branch",
committerDate: 5,
},
]);
});
it("keeps the plain name for a local-only branch", () => {
expect(
buildBranchPickerItems([
{
name: "scratch",
committerDate: 5,
hasLocal: true,
hasRemote: false,
},
]),
).toEqual([
{
kind: "branch",
name: "scratch",
refName: "refs/heads/scratch",
accessibilityLabel: "scratch, local branch",
committerDate: 5,
},
]);
@@ -194,15 +215,15 @@ describe("buildBranchPickerItems", () => {
{
kind: "branch",
name: "main",
refName: "refs/heads/main",
accessibilityLabel: "main, local branch",
refName: "refs/remotes/origin/main",
accessibilityLabel: "main, origin branch",
committerDate: 10,
},
{
kind: "branch",
name: "origin/main",
refName: "refs/remotes/origin/main",
accessibilityLabel: "origin main, origin branch",
name: "main (local)",
refName: "refs/heads/main",
accessibilityLabel: "main, local branch",
committerDate: 10,
},
]);
@@ -221,38 +242,101 @@ describe("buildBranchPickerItems", () => {
});
});
describe("resolveCheckoutRequest", () => {
const checkoutStatus = {
currentBranch: "feature/current",
} as CheckoutStatusPayload;
const mainRow: BranchPickerDetail = {
name: "main",
committerDate: 10,
hasLocal: true,
hasRemote: true,
localAhead: 2,
localBehind: 0,
};
it("branches from the loaded current branch when nothing was picked", () => {
expect(resolveCheckoutRequest(null, checkoutStatus)).toEqual({
action: "branch-off",
refName: "feature/current",
describe("buildPickerOptionData", () => {
it("marks origin and keeps an ahead local main explicit", () => {
const baseItem = defaultBasePickerItem({
currentBranch: "main",
upstreamRef: "refs/remotes/origin/main",
});
const data = buildPickerOptionData({ branchDetails: [mainRow], prItems: [], baseItem });
expect(data.options.map((option) => option.label)).toEqual(["main", "main (local)"]);
expect(data.selectedOptionId).toBe(branchPickerOptionId("refs/remotes/origin/main"));
});
it("prefers an explicit picker selection", () => {
expect(
resolveCheckoutRequest(
{
kind: "branch",
name: "feature/picked",
refName: "refs/heads/feature/picked",
accessibilityLabel: "feature/picked, local branch",
},
checkoutStatus,
),
).toEqual({
action: "branch-off",
refName: "refs/heads/feature/picked",
it("adds and disambiguates a fork upstream absent from branch suggestions", () => {
const baseItem = defaultBasePickerItem({
currentBranch: "main",
upstreamRef: "refs/remotes/upstream/main",
});
const data = buildPickerOptionData({
branchDetails: [{ ...mainRow, localAhead: 0, localBehind: 0 }],
prItems: [],
baseItem,
});
expect(data.options.map((option) => option.label)).toEqual(["main (upstream)", "main"]);
expect(data.selectedOptionId).toBe(branchPickerOptionId("refs/remotes/upstream/main"));
});
it("preserves the server-default intent for a detached checkout", () => {
it("marks a visible row on an old daemon when local and origin are in sync", () => {
const baseItem = defaultBasePickerItem({ currentBranch: "main" });
const data = buildPickerOptionData({
branchDetails: [{ ...mainRow, localAhead: 0, localBehind: 0 }],
prItems: [],
baseItem,
});
expect(data.options.map((option) => option.label)).toEqual(["main (local)", "main"]);
expect(data.selectedOptionId).toBe(branchPickerOptionId("refs/heads/main"));
});
it("keeps an explicit local selection marked", () => {
const baseItem: PickerItem = {
kind: "branch",
name: "main (local)",
refName: "refs/heads/main",
accessibilityLabel: "main, local branch",
};
const data = buildPickerOptionData({
branchDetails: [mainRow],
prItems: [],
baseItem,
});
const selected = data.itemById.get(data.selectedOptionId) ?? null;
expect(pickerItemToCheckoutRequest(selected)).toEqual({
action: "branch-off",
refName: "refs/heads/main",
});
});
});
describe("defaultBasePickerItem", () => {
it("uses origin when the current branch tracks origin", () => {
expect(
resolveCheckoutRequest(null, { ...checkoutStatus, currentBranch: null }),
).toBeUndefined();
defaultBasePickerItem({
currentBranch: "main",
upstreamRef: "refs/remotes/origin/main",
}),
).toMatchObject({ refName: "refs/remotes/origin/main", name: "main" });
});
it("uses the exact non-origin upstream", () => {
expect(
defaultBasePickerItem({
currentBranch: "main",
upstreamRef: "refs/remotes/upstream/main",
}),
).toMatchObject({ refName: "refs/remotes/upstream/main", name: "main" });
});
it("keeps old-daemon behavior local", () => {
expect(defaultBasePickerItem({ currentBranch: "main" })).toMatchObject({
refName: "refs/heads/main",
name: "main",
});
});
it("has no default for detached HEAD", () => {
expect(defaultBasePickerItem({ currentBranch: null })).toBeNull();
});
});
@@ -1,6 +1,7 @@
import type { CreatePaseoWorktreeInput } from "@getpaseo/client/internal/daemon-client";
import type { ForgeSearchItem } from "@getpaseo/protocol/messages";
import type { CheckoutStatusPayload } from "@/git/checkout-status-cache";
import type { ComboboxOptionModel } from "@/components/ui/combobox-options";
import { getForgePresentation } from "@/git/forge";
export interface BranchPickerDetail {
name: string;
@@ -31,6 +32,8 @@ export type PickerCheckoutRequest = Pick<
>;
const BRANCH_OPTION_PREFIX = "branch:";
const PR_OPTION_PREFIX = "github-pr:";
const REMOTE_TRACKING_PREFIX = "refs/remotes/";
export function branchPickerOptionId(refName: string): string {
return `${BRANCH_OPTION_PREFIX}${refName}`;
@@ -82,11 +85,27 @@ export function buildBranchPickerItems(details: readonly BranchPickerDetail[]):
const localBehind = detail.localBehind;
const hasDivergence =
hasLocal && hasRemote && localAhead !== undefined && localBehind !== undefined;
const refsDiffer = hasDivergence && (localAhead > 0 || localBehind > 0);
// One row when the two refs point at the same commit, two rows when they differ or
// when the divergence is unknown.
const showsBothRefs = hasLocal && hasRemote && (!hasDivergence || refsDiffer);
if (hasLocal) {
const localItem: Extract<PickerItem, { kind: "branch" }> = {
// The origin row goes first because it is the default base, and it never states
// divergence: it is the reference point the local row is measured against.
if (hasRemote) {
items.push({
kind: "branch",
name: detail.name,
refName: `refs/remotes/origin/${detail.name}`,
accessibilityLabel: `${detail.name}, origin branch`,
committerDate: detail.committerDate,
});
}
if (hasLocal && (showsBothRefs || !hasRemote)) {
const localItem: Extract<PickerItem, { kind: "branch" }> = {
kind: "branch",
name: showsBothRefs ? `${detail.name} (local)` : detail.name,
refName: `refs/heads/${detail.name}`,
accessibilityLabel: `${detail.name}, local branch`,
committerDate: detail.committerDate,
@@ -101,33 +120,64 @@ export function buildBranchPickerItems(details: readonly BranchPickerDetail[]):
}
items.push(localItem);
}
const refsDiffer = hasDivergence && (localAhead > 0 || localBehind > 0);
if (hasRemote && (!hasLocal || !hasDivergence || refsDiffer)) {
const remoteAhead = hasDivergence ? localBehind : 0;
const remoteBehind = hasDivergence ? localAhead : 0;
const remoteItem: Extract<PickerItem, { kind: "branch" }> = {
kind: "branch",
name: `origin/${detail.name}`,
refName: `refs/remotes/origin/${detail.name}`,
accessibilityLabel: `origin ${detail.name}, origin branch`,
committerDate: detail.committerDate,
};
if (hasDivergence) {
remoteItem.divergenceLabel = divergenceLabel(remoteAhead, remoteBehind);
remoteItem.accessibilityLabel = `origin ${detail.name}, ${divergenceAccessibility(
remoteAhead,
remoteBehind,
`local ${detail.name}`,
)}`;
}
items.push(remoteItem);
}
}
return items;
}
export interface BaseRefCheckoutStatus {
currentBranch: string | null;
upstreamRef?: string | null;
}
// Display only. The exact ref is what every request carries; this is just how a ref reads in
// a row label, so "refs/remotes/origin/other-name" shows as "other-name".
function branchNameFromRef(refName: string): string {
if (refName.startsWith("refs/heads/")) return refName.slice("refs/heads/".length);
if (refName.startsWith(REMOTE_TRACKING_PREFIX)) {
const remainder = refName.slice(REMOTE_TRACKING_PREFIX.length);
const separator = remainder.indexOf("/");
return separator === -1 ? remainder : remainder.slice(separator + 1);
}
return refName;
}
// Where a ref lives, for disambiguating two rows that would otherwise read the same.
function refQualifier(refName: string): string | null {
if (refName.startsWith("refs/heads/")) return "local";
if (refName.startsWith(REMOTE_TRACKING_PREFIX)) {
const remainder = refName.slice(REMOTE_TRACKING_PREFIX.length);
const separator = remainder.indexOf("/");
return separator === -1 ? null : remainder.slice(0, separator);
}
return null;
}
// The one owner of "what do we branch off when the user picked nothing". The checkmarked
// row, the trigger label, and the created ref all read this; computing it twice is how the
// picker once showed local main while branching off something else.
//
// The upstream wins when the branch has one, because branching off the local ref silently
// carries unpushed commits into the new workspace. The daemon sends the resolved ref rather
// than a remote name, so a fork tracking upstream/main branches from upstream/main.
export function defaultBasePickerItem(status: BaseRefCheckoutStatus): PickerItem | null {
const currentBranch = status.currentBranch;
if (!currentBranch) return null;
// COMPAT(checkoutUpstreamRef): added in v0.2.6, remove after 2027-02-01 once the daemon
// floor sends upstreamRef. Daemons that predate it omit the field, which lands on the
// local ref — the base those daemons always used.
const refName = status.upstreamRef ?? `refs/heads/${currentBranch}`;
// The upstream branch can be named differently from the local one, so the row reads the
// ref rather than the branch the user happens to be on.
const name = branchNameFromRef(refName);
return {
kind: "branch",
name,
refName,
accessibilityLabel: status.upstreamRef ? `${name}, upstream branch` : `${name}, local branch`,
};
}
export function pickerItemToCheckoutRequest(
item: PickerItem | null,
): PickerCheckoutRequest | undefined {
@@ -159,15 +209,98 @@ export function pickerItemToCheckoutRequest(
}
}
export function resolveCheckoutRequest(
selectedItem: PickerItem | null,
checkoutStatus: CheckoutStatusPayload,
): PickerCheckoutRequest | undefined {
const selectedCheckoutRequest = pickerItemToCheckoutRequest(selectedItem);
if (selectedCheckoutRequest) return selectedCheckoutRequest;
if (!checkoutStatus.currentBranch) return undefined;
export function prPickerOptionId(number: number): string {
return `${PR_OPTION_PREFIX}${number}`;
}
export function pickerOptionId(item: PickerItem): string {
return item.kind === "branch"
? branchPickerOptionId(item.refName)
: prPickerOptionId(item.item.number);
}
function formatPrLabel(item: Pick<ForgeSearchItem, "forge" | "number" | "title">): string {
const presentation = getForgePresentation(item.forge ?? "github");
return `${presentation.numberPrefix}${item.number} ${item.title}`;
}
export function pickerItemLabel(item: PickerItem): string {
return item.kind === "branch" ? item.name : formatPrLabel(item.item);
}
export interface PickerOptionData {
options: ComboboxOptionModel[];
itemById: Map<string, PickerItem>;
selectedOptionId: string;
}
interface TimedOption {
option: ComboboxOptionModel;
timestamp: number;
}
export function buildPickerOptionData(input: {
branchDetails: readonly BranchPickerDetail[];
prItems: readonly ForgeSearchItem[];
baseItem: PickerItem | null;
}): PickerOptionData {
const itemById = new Map<string, PickerItem>();
const timedOptions: TimedOption[] = [];
for (const branch of buildBranchPickerItems(input.branchDetails)) {
if (branch.kind !== "branch") continue;
const id = branchPickerOptionId(branch.refName);
itemById.set(id, branch);
timedOptions.push({
option: { id, label: pickerItemLabel(branch) },
timestamp: branch.committerDate ?? 0,
});
}
for (const pr of input.prItems) {
if (!pr.headRefName) continue;
const id = prPickerOptionId(pr.number);
itemById.set(id, { kind: "github-pr", item: pr });
const updatedAtMs = pr.updatedAt ? Date.parse(pr.updatedAt) : 0;
const timestamp = Number.isNaN(updatedAtMs) ? 0 : Math.floor(updatedAtMs / 1000);
timedOptions.push({ option: { id, label: formatPrLabel(pr) }, timestamp });
}
const baseItem = input.baseItem;
if (!baseItem) {
timedOptions.sort((a, b) => b.timestamp - a.timestamp);
return { options: timedOptions.map((t) => t.option), itemById, selectedOptionId: "" };
}
const selectedOptionId = pickerOptionId(baseItem);
if (!itemById.has(selectedOptionId)) {
// The label lives on the item, so the row and the trigger read it from the same place.
const labeledItem = disambiguate(baseItem, timedOptions);
itemById.set(selectedOptionId, labeledItem);
timedOptions.push({
option: { id: selectedOptionId, label: pickerItemLabel(labeledItem) },
// The base sorts first: it is what a workspace is created from unless you pick something.
timestamp: Number.POSITIVE_INFINITY,
});
}
timedOptions.sort((a, b) => b.timestamp - a.timestamp);
return { options: timedOptions.map((t) => t.option), itemById, selectedOptionId };
}
// Two rows reading "main" would be a coin flip for the user, so the added row says where its
// ref lives. Only when it collides — a lone row needs no qualifier.
function disambiguate(item: PickerItem, existing: readonly TimedOption[]): PickerItem {
if (item.kind !== "branch") return item;
if (!existing.some((entry) => entry.option.label === item.name)) return item;
const qualifier = refQualifier(item.refName);
if (!qualifier) return item;
const name = `${item.name} (${qualifier})`;
return {
action: "branch-off",
refName: checkoutStatus.currentBranch,
...item,
name,
// Accessibility labels are built as `${name}${suffix}`, so re-prefixing keeps the spoken
// label and the visible one the same string.
accessibilityLabel: `${name}${item.accessibilityLabel.slice(item.name.length)}`,
};
}
@@ -61,7 +61,6 @@ import { useKeyboardActionHandler } from "@/hooks/use-keyboard-action-handler";
import type { KeyboardActionId } from "@/keyboard/keyboard-action-dispatcher";
import { useFormPreferences } from "@/hooks/use-form-preferences";
import { useShortcutKeys } from "@/hooks/use-shortcut-keys";
import { getForgePresentation } from "@/git/forge";
import type { CreateAgentInitialValues } from "@/hooks/use-agent-form-state";
import { generateMessageId } from "@/types/stream";
import { toErrorMessage } from "@/utils/error-messages";
@@ -90,12 +89,14 @@ import {
remapDraftCwdToWorkspace,
} from "./new-workspace-fork-context";
import {
branchPickerOptionId,
buildBranchPickerItems,
resolveCheckoutRequest,
buildPickerOptionData,
defaultBasePickerItem,
pickerItemLabel,
pickerItemToCheckoutRequest,
type BranchPickerDetail,
type PickerCheckoutRequest,
type PickerItem,
type PickerOptionData,
} from "./new-workspace-picker-item";
import {
clearPickerPrAttachmentForTargetChange,
@@ -169,12 +170,6 @@ interface NewWorkspaceScreenProps {
draftId?: string;
}
interface PickerOptionData {
options: ComboboxOptionType[];
itemById: Map<string, PickerItem>;
}
const PR_OPTION_PREFIX = "github-pr:";
const PROJECT_ICON_FALLBACK_FONT_SIZE = 10;
// Stable reference so the keyboard-action handler doesn't re-register each render.
const PROJECT_PICK_ACTIONS: readonly KeyboardActionId[] = ["workspace.project.pick"];
@@ -475,10 +470,6 @@ function ProjectOptionItem({
);
}
function prOptionId(number: number): string {
return `${PR_OPTION_PREFIX}${number}`;
}
function NewWorkspacePickerOption({
option,
selected,
@@ -590,57 +581,10 @@ function AddProjectPickerAction({ onPress }: { onPress: () => void }) {
);
}
function formatPrLabel(item: Pick<ForgeSearchItem, "forge" | "number" | "title">): string {
const presentation = getForgePresentation(item.forge ?? "github");
return `${presentation.numberPrefix}${item.number} ${item.title}`;
}
function pickerItemLabel(item: PickerItem): string {
return item.kind === "branch" ? item.name : formatPrLabel(item.item);
}
function pickerItemTriggerLabel(item: PickerItem): string {
return item.kind === "branch" ? item.name : formatPrLabel(item.item);
}
function newWorkspaceHostOptionTestID(serverId: string): string {
return `new-workspace-host-picker-option-${serverId}`;
}
function computePickerOptionData(
branchDetails: readonly BranchPickerDetail[],
prItems: ReadonlyArray<ForgeSearchItem>,
): PickerOptionData {
const idMap = new Map<string, PickerItem>();
interface TimedOption {
option: ComboboxOptionType;
timestamp: number;
}
const timedOptions: TimedOption[] = [];
for (const branch of buildBranchPickerItems(branchDetails)) {
if (branch.kind !== "branch") continue;
const id = branchPickerOptionId(branch.refName);
const option = { id, label: branch.name };
idMap.set(id, branch);
timedOptions.push({ option, timestamp: branch.committerDate ?? 0 });
}
for (const pr of prItems) {
if (!pr.headRefName) continue;
const id = prOptionId(pr.number);
const option = { id, label: formatPrLabel(pr) };
idMap.set(id, { kind: "github-pr", item: pr });
const updatedAtMs = pr.updatedAt ? Date.parse(pr.updatedAt) : 0;
const timestamp = Number.isNaN(updatedAtMs) ? 0 : Math.floor(updatedAtMs / 1000);
timedOptions.push({ option, timestamp });
}
timedOptions.sort((a, b) => b.timestamp - a.timestamp);
return { options: timedOptions.map((t) => t.option), itemById: idMap };
}
function IsolationPickerTrigger({
pickerAnchorRef,
onPress,
@@ -1655,7 +1599,6 @@ export function NewWorkspaceScreen({
cwd: selectedSourceDirectory ?? "",
});
const currentBranch = checkoutStatus?.currentBranch ?? null;
const worktreeSupport = selectedProject
? getWorktreeSupportForHostProject({ project: selectedProject, serverId: selectedServerId })
: "unsupported";
@@ -1709,25 +1652,23 @@ export function NewWorkspaceScreen({
return githubPrSearchQuery.data?.items ?? [];
}, [forgeSearchAuthenticated, githubPrSearchQuery.data?.items]);
const { options, itemById }: PickerOptionData = useMemo(
() => computePickerOptionData(branchDetails, prItems),
[branchDetails, prItems],
const baseItem = useMemo(
() => selectedItem ?? (checkoutStatus ? defaultBasePickerItem(checkoutStatus) : null),
[checkoutStatus, selectedItem],
);
const { options, itemById, selectedOptionId }: PickerOptionData = useMemo(
() =>
buildPickerOptionData({
branchDetails,
prItems,
baseItem,
}),
[baseItem, branchDetails, prItems],
);
const triggerLabel = useMemo(() => {
if (selectedItem) return pickerItemTriggerLabel(selectedItem);
return currentBranch ?? "main";
}, [currentBranch, selectedItem]);
const selectedOptionId = useMemo(() => {
if (!selectedItem) {
if (!currentBranch) return "";
const exactLocalId = branchPickerOptionId(`refs/heads/${currentBranch}`);
return itemById.has(exactLocalId) ? exactLocalId : branchPickerOptionId(currentBranch);
}
return selectedItem.kind === "branch"
? branchPickerOptionId(selectedItem.refName)
: prOptionId(selectedItem.item.number);
}, [currentBranch, itemById, selectedItem]);
const displayItem = itemById.get(selectedOptionId);
return displayItem ? pickerItemLabel(displayItem) : "main";
}, [itemById, selectedOptionId]);
const selectPickerItem = useCallback(
(item: PickerItem) => {
const nextAttachments = syncPickerPrAttachment({
@@ -1929,15 +1870,17 @@ export function NewWorkspaceScreen({
}
const connectedClient = withConnectedClient();
const createsWorktree = !supportsWorkspaceMultiplicity || effectiveIsolation === "worktree";
const checkoutRequest = createsWorktree
? resolveCheckoutRequest(
selectedItem,
await ensureCheckoutStatus({
queryClient,
client: connectedClient,
serverId: selectedServerId,
cwd: selectedSourceDirectory,
}),
const checkoutStatusForCreate = createsWorktree
? await ensureCheckoutStatus({
queryClient,
client: connectedClient,
serverId: selectedServerId,
cwd: selectedSourceDirectory,
})
: null;
const checkoutRequest = checkoutStatusForCreate
? pickerItemToCheckoutRequest(
selectedItem ?? defaultBasePickerItem(checkoutStatusForCreate),
)
: undefined;
const normalizedWorkspace = supportsWorkspaceMultiplicity