gitbutlerapp/gitbutler · error

Could not find selected project

Error message

Could not find selected project

What it means

usePathMenuItems resolves the project for the given projectId from the cached projects list and throws when no project matches. The hook needs the project's checkout path (or a worktree path) to build absolute paths for menu items like open-in-program, so a missing project is unrecoverable.

Solutions

  1. Confirm the projectId param exists in the cached projects list (log the list vs the id).
  2. Wait for the list-projects query to finish loading before rendering the component that calls the hook.
  3. Redirect to the project list when the id is no longer valid after a project deletion.
  4. Invalidate/refetch the projects query if project data may be stale after external changes.

Example fix

// before
const selectedProject = projects.find((project) => project.id === projectId);
if (!selectedProject) throw new Error("Could not find selected project");

// after
const { data: projects, isPending } = useQuery(listProjectsQueryOptions);
const selectedProject = projects?.find((project) => project.id === projectId);
if (!isPending && !selectedProject) {
  return null; // let the route redirect instead of throwing during render
}
Defensive patterns

Strategy: validation

Validate before calling

const projects = useQuery(listProjectsQueryOptions).data ?? [];
const selectedProject = projects.find((p) => p.id === projectId);
if (!selectedProject) return null; // or redirect before calling the hook's consumers

Type guard

function findProject(projects: Project[] | undefined, id: string): Project | undefined {
  return projects?.find((p) => p.id === id);
}

Try / catch

try {
  const items = usePathMenuItemsResult;
} catch (e) {
  if (e instanceof Error && e.message.includes("Could not find selected project")) {
    showEmptyState("Project unavailable");
  } else throw e;
}

Prevention

When it happens

Trigger: Invoking usePathMenuItems(projectId, ...) when projects.find((p) => p.id === projectId) is undefined — the projects query cache is empty/stale, the project was deleted elsewhere, or the route id doesn't correspond to any listed project.

Common situations: Opening a file's context menu in a workspace whose project was removed in another window; landing on a stale /project/<id> deep link; projects list still loading on app startup so the cache lookup fails before data arrives.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/85ca8810fb2f6b9f. Report an issue: GitHub.

Appendix: source

Thrown at apps/lite/ui/src/routes/project/$id/workspace/usePathMenuItems.ts:43

	path: string;
	/** The linked worktree the file lives in; the project's own checkout when unset. */
	worktree?: string;
}): Array<NativeMenuItem> => {
	const { data: projects } = useSuspenseQuery(listProjectsQueryOptions);
	const { data: editors } = useQuery(listEditorsQueryOptions);
	const { data: preferredEditor } = useQuery({
		...guiSettingsQueryOptions,
		select: (cfg) => editors?.find((editor) => editor.id === cfg.editorId),
	});
	const { data: worktreePath } = useQuery({
		...worktreesListQueryOptions(projectId),
		enabled: worktree !== undefined,
		select: (listing) =>
			[...listing.active, ...listing.archived].find((entry) => entry.name === worktree)?.path,
	});

	const selectedProject = projects.find((project) => project.id === projectId);
	if (!selectedProject) throw new Error("Could not find selected project");
	// The checkout the path is relative to; unknown until the worktree listing answers.
	const basePath = worktree === undefined ? selectedProject.path : worktreePath;
	const absolutePath = () => window.lite.pathJoin(basePath ?? "", path);

	const { isPending: isOpenInProgramPending, mutate: openInProgram } = useOpenInProgram();
	// The backend resolves the path against the project's checkout, so a linked
	// worktree's file is handed over absolute, which joining leaves untouched.
	const openPath = async (programId: string) => {
		const target = worktree === undefined ? path : await absolutePath();
		openInProgram({ projectId, programId, path: target, lineNr: null });
	};
	const canOpen = !isOpenInProgramPending && basePath !== undefined;

	return [
		preferredEditor
			? nativeMenuItem({
					label: `Open in ${preferredEditor.name}`,
					enabled: canOpen,

View on GitHub (pinned to 58e5313667)