Empty traceback frames
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0fac4a287..1897c96c1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Use `Console(stderr=True)` in `rich.traceback.install` to support io redirection.
 - Fixes superfluous spaces in html output https://github.com/Textualize/rich/issues/2832
 - Fixed duplicate output in Jupyter https://github.com/Textualize/rich/pulls/2804
+- Fixes traceback failing when a frame filename is unreadable https://github.com/Textualize/rich/issues/2821
 
 ### Added
 
diff --git a/rich/syntax.py b/rich/syntax.py
index d732a2bf0..cc0660c23 100644
--- a/rich/syntax.py
+++ b/rich/syntax.py
@@ -494,7 +494,10 @@ def tokens_to_spans() -> Iterable[Tuple[str, Optional[Style]]]:
 
                     # Skip over tokens until line start
                     while line_no < _line_start:
-                        _token_type, token = next(tokens)
+                        try:
+                            _token_type, token = next(tokens)
+                        except StopIteration:
+                            break
                         yield (token, None)
                         if token.endswith("\n"):
                             line_no += 1
@@ -671,6 +674,8 @@ def _get_syntax(
             line_offset = max(0, start_line - 1)
         lines: Union[List[Text], Lines] = text.split("\n", allow_blank=ends_on_nl)
         if self.line_range:
+            if line_offset > len(lines):
+                return
             lines = lines[line_offset:end_line]
 
         if self.indent_guides and not options.ascii_only:
diff --git a/rich/text.py b/rich/text.py
index 9c6bda26b..6c39cfa61 100644
--- a/rich/text.py
+++ b/rich/text.py
@@ -53,11 +53,7 @@ class Span(NamedTuple):
     """Style associated with the span."""
 
     def __repr__(self) -> str:
-        return (
-            f"Span({self.start}, {self.end}, {self.style!r})"
-            if (isinstance(self.style, Style) and self.style._meta)
-            else f"Span({self.start}, {self.end}, {self.style!r})"
-        )
+        return f"Span({self.start}, {self.end}, {self.style!r})"
 
     def __bool__(self) -> bool:
         return self.end > self.start
diff --git a/rich/traceback.py b/rich/traceback.py
index eb0a7a923..341f7f411 100644
--- a/rich/traceback.py
+++ b/rich/traceback.py
@@ -342,6 +342,7 @@ def from_exception(
             locals_hide_dunder=locals_hide_dunder,
             locals_hide_sunder=locals_hide_sunder,
         )
+
         return cls(
             rich_traceback,
             width=width,
@@ -663,7 +664,13 @@ def render_locals(frame: Frame) -> Iterable[ConsoleRenderable]:
                     style="pygments.text",
                 )
             else:
-                text = Text.assemble("in ", (frame.name, "pygments.function"))
+                text = Text.assemble(
+                    "in ",
+                    (frame.name, "pygments.function"),
+                    (":", "pygments.text"),
+                    (str(frame.lineno), "pygments.number"),
+                    style="pygments.text",
+                )
             if not frame.filename.startswith("<") and not first:
                 yield ""
             yield text
@@ -673,6 +680,10 @@ def render_locals(frame: Frame) -> Iterable[ConsoleRenderable]:
             if not suppressed:
                 try:
                     code = read_code(frame.filename)
+                    if not code:
+                        # code may be an empty string if the file doesn't exist, OR
+                        # if the traceback filename is generated dynamically
+                        continue
                     lexer_name = self._guess_lexer(frame.filename, code)
                     syntax = Syntax(
                         code,

Fixes https://github.com/Textualize/rich/issues/2821