Skip to content

bug(web): Intermediate directory nodes in buildFileTree receive child leaf paths instead of directory paths #1707

Description

@riteshvish02

Describe the Bug

In packages/web/src/features/git/utils.ts, buildFileTree constructs a nested FileTreeNode hierarchy from a flat list of { type, path } objects.

When creating intermediate directory nodes (lines 68-74), the new node's path property is assigned item.path (the full path of the current leaf file being processed), instead of the path of that directory:

// packages/web/src/features/git/utils.ts:67-75
if (!next) {
    next = {
        name: part,
        path: item.path, // <--- Bug: assigns leaf file's path to intermediate directory node
        type: nodeType,
        children: [],
    };
    current.children.push(next);
}

Impact

  1. Broken Folder State & Expansion in UI: In packages/web/src/app/(app)/browse/components/fileTreePanel.tsx, folder expand/collapse state is tracked via openPaths.has(node.path) and route sync via pathParts.slice(0, i + 1).join('/'). Because directory nodes receive the child file's path instead of their own directory path, clicking folders or deep-linking to nested paths causes folder state tracking and auto-expansion to fail.
  2. Public API Corruption: The getTree endpoint (/api/git/tree) returns FileTreeNode with corrupted directory paths to API consumers.

Reproduction

const flatList = [
    { type: 'blob', path: 'src/components/buttons/PrimaryButton.tsx' }
];

const tree = buildFileTree(flatList);
const srcDir = tree.children[0];
console.log(srcDir.name); // "src"
console.log(srcDir.path); // Expected "src", but Got "src/components/buttons/PrimaryButton.tsx"

Proposed Fix

Reconstruct the directory node's path using parts.slice(0, i + 1).join('/'):

if (!next) {
    next = {
        name: part,
        path: parts.slice(0, i + 1).join('/'),
        type: nodeType,
        children: [],
    };
    current.children.push(next);
}

For intermediate directory levels (i < parts.length - 1), this evaluates to the proper directory path (e.g. "src", "src/components"), and for leaf files (i === parts.length - 1), it matches item.path.

Activity

  1. itsmunzir commented on Oct 8, 2026

    @itsmunzir

    I will take this. The intermediate nodes will carry their own directory path (the joined parts up to that level) and the new test will assert folder-node paths on a nested tree.

  2. brendan-kellam commented on Oct 8, 2026

    @brendan-kellam
    Contributor

    Thanks for the detailed write-up @riteshvish02!

    I looked into this, and the buildFileTree behavior you describe is real if you call it directly with a list that only has files, e.g. [{ type: 'blob', path: 'src/a/b.ts' }]. In practice it doesn't affect the browse UI or /api/tree. The only caller, getTreeApi.ts, runs git ls-tree with the -t flag, which lists every folder before its contents. So each folder node is created from its own entry with the correct path, before any file underneath it is processed.

    To confirm, I called /api/tree on app.sourcebot.dev for sourcebot-dev/sourcebot with several paths inputs: nested folders, a file path, leading and trailing slashes, multiple paths, and an empty list. Across about 700 nodes, every folder's path matched its actual location.

    Closing as not reproducible, but feel free to reopen if you see this in the UI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions