Skip to content

Render broken symlinks & show symlinks in different colors#27

Merged
slinjhu merged 2 commits intoSmooSenseAI:mainfrom
nikitakrh:main
Apr 2, 2026
Merged

Render broken symlinks & show symlinks in different colors#27
slinjhu merged 2 commits intoSmooSenseAI:mainfrom
nikitakrh:main

Conversation

@nikitakrh
Copy link
Copy Markdown
Contributor

Closes #24

Changes

Backend (local_fs.py, models.py):

  • FSItem gets isBrokenSymlink and symlinkTarget fields.
  • scandir now catches OSError on broken symlinks instead of crashing.
  • Broken symlinks are included with isBrokenSymlink=True, working symlinks can be found via isBrokenSymlink == False and symlinkTarget is not None.

Frontend (folderTreeSlice.ts, FolderTreeView.tsx, TreeNodeComponent.tsx, iconUtils.tsx):

  • Propagate the two new fields through the tree state.
  • In the tree node:
    • Broken symlinks get a FileX icon (red) and red text
    • Working symlinks show blue text
    • Both display the target as name (→ target) with a tooltip

Example:
Screenshot 2026-04-01 at 20 45 35

@nikitakrh
Copy link
Copy Markdown
Contributor Author

Some more background: I basically took the bugfix_2.patch @jonded94 posted in the issue #24 and applied it to the most recent main. I added 2 more features which is the displaying of the symlinked path and the blue color for working symlinks.

Happy to answer any questions or get feedback on this PR! 🙏

Comment on lines +33 to +57
try:
stat = entry.stat()
all_items.append(
FSItem(
name=entry.name,
size=stat.st_size,
lastModified=int(1000 * stat.st_mtime),
isDir=entry.is_dir(),
symlinkTarget=os.readlink(entry.path) if entry.is_symlink() else "",
)
)
)
except OSError:
if entry.is_symlink():
all_items.append(
FSItem(
name=entry.name,
size=0,
lastModified=0,
isDir=False,
isBrokenSymlink=True,
symlinkTarget=os.readlink(entry.path),
)
)
else:
logger.warning(f"Failed to stat entry: {entry.path}")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think your version here is a bit too much based on the vibe-coded version I showcased in this comment.

I'd reduce the scope of the try statement to just the entry.stat() to both make intent clearer as well as don't unnecessarily catch other exceptions that shouldn't be caught.

Also, I'd personally always rather see what exceptions happened and therefore include the stack trace (either through exc_info or by using logger.exception). But that is for @slinjhu to decide 😄

Suggested change
try:
stat = entry.stat()
all_items.append(
FSItem(
name=entry.name,
size=stat.st_size,
lastModified=int(1000 * stat.st_mtime),
isDir=entry.is_dir(),
symlinkTarget=os.readlink(entry.path) if entry.is_symlink() else "",
)
)
)
except OSError:
if entry.is_symlink():
all_items.append(
FSItem(
name=entry.name,
size=0,
lastModified=0,
isDir=False,
isBrokenSymlink=True,
symlinkTarget=os.readlink(entry.path),
)
)
else:
logger.warning(f"Failed to stat entry: {entry.path}")
try:
stat = entry.stat()
except OSError:
if entry.is_symlink():
all_items.append(
FSItem(
name=entry.name,
size=0,
lastModified=0,
isDir=False,
isBrokenSymlink=True,
symlinkTarget=os.readlink(entry.path),
)
)
else:
logger.exception(f"Failed to stat entry: {entry.path}")
else:
all_items.append(
FSItem(
name=entry.name,
size=stat.st_size,
lastModified=int(1000 * stat.st_mtime),
isDir=entry.is_dir(),
symlinkTarget=os.readlink(entry.path) if entry.is_symlink() else "",
)
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. In general it is better to keep try-catch scope smaller.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, I applied the suggestion.

Copy link
Copy Markdown
Contributor

@slinjhu slinjhu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @nikitakrh for the great work. I wasn't anticipating broken symlink, thanks for floating up this corner case in realistic data.

@jonded94 made a suggestion to reduce scope of try-catch, and I think it makes sense and increase code readability.

Otherwise your PR looks good to me.

)
)
else:
logger.warning(f"Failed to stat entry: {entry.path}")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change logger.warning to logger.exception so we can see full traceback

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it to exception 🙂

Comment on lines +33 to +57
try:
stat = entry.stat()
all_items.append(
FSItem(
name=entry.name,
size=stat.st_size,
lastModified=int(1000 * stat.st_mtime),
isDir=entry.is_dir(),
symlinkTarget=os.readlink(entry.path) if entry.is_symlink() else "",
)
)
)
except OSError:
if entry.is_symlink():
all_items.append(
FSItem(
name=entry.name,
size=0,
lastModified=0,
isDir=False,
isBrokenSymlink=True,
symlinkTarget=os.readlink(entry.path),
)
)
else:
logger.warning(f"Failed to stat entry: {entry.path}")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. In general it is better to keep try-catch scope smaller.

@nikitakrh nikitakrh requested a review from slinjhu April 2, 2026 06:53
@slinjhu
Copy link
Copy Markdown
Contributor

slinjhu commented Apr 2, 2026

Tests failing due to lack of AWS credentials. After some research I fount issue:

This is a GitHub security restriction: when a PR comes from a fork (nikitakrh's
repo), GitHub intentionally does not pass secrets to the workflow. So ${{ secrets.AWS_ACCESS_KEY_ID }} resolves to
empty, regardless of what's in your env block.

When you push directly to main, the workflow runs in your own repo context and secrets work fine.

@slinjhu slinjhu merged commit 1d4b9d2 into SmooSenseAI:main Apr 2, 2026
6 of 9 checks passed
@slinjhu
Copy link
Copy Markdown
Contributor

slinjhu commented Apr 2, 2026

Released v0.1.47

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Symlinks not being able to resolved effectively crash the folder navigation, instead of just being rendered as a broken link in the UI

3 participants