Repository: getsentry/sentry-python

Issue:
Invoking sentry when in a path that does not exist causes a FileNotFoundError
### How do you use Sentry?

Self-hosted/on-premise

### Version

1.29.2

### Steps to Reproduce

Go from sentry-sdk version 1.29.0 to 1.29.2 (this is where we noticed)

[sentry-sdk](https://pypi.python.org/pypi/sentry-sdk)==1.29.0
[sentry-sdk](https://pypi.python.org/pypi/sentry-sdk)==1.29.2

This is the line mentioned in the trace: https://github.com/getsentry/sentry-python/commit/bc209e52081bc0faa5fe58d81673fb2ecfd283a0#diff-fb869cde7d11be4df35c1f0d0f45f5824cc0e76e6ada504a48e8ceefe363f739R250

### Expected Result

It would work, like it did earlier, and not break the functionality of the tool being used.

I understand that this isn't a common situation, but I think it's unfair that a tool that's supposed to help troubleshoot is breaking functionality.

### Actual Result

Running a command that uses sentry from a path that got cleaned up:

```shell
07:37:20      import sentry_sdk
07:37:20    File "/usr/local/lib/python3.11/site-packages/sentry_sdk/__init__.py", line 1, in <module>
07:37:20      from sentry_sdk.hub import Hub, init
07:37:20    File "/usr/local/lib/python3.11/site-packages/sentry_sdk/hub.py", line 10, in <module>
07:37:20      from sentry_sdk.client import Client
07:37:20    File "/usr/local/lib/python3.11/site-packages/sentry_sdk/client.py", line 34, in <module>
07:37:20      from sentry_sdk.profiler import has_profiling_enabled, setup_profiler
07:37:20    File "/usr/local/lib/python3.11/site-packages/sentry_sdk/profiler.py", line 251, in <module>
07:37:20      CWD = os.getcwd()
07:37:20            ^^^^^^^^^^^
07:37:20  FileNotFoundError: [Errno 2] No such file or directory
```


Discussion:
Hey @korhojoa, thanks for reporting, we will fix this.

@Zylphrex could you please take a look? I'm assuming we need to handle potential `FileNotFoundError`s, but I'm not sure what the behavior should be if one happens.

Resolving patch:
diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py
--- a/sentry_sdk/client.py
+++ b/sentry_sdk/client.py
@@ -258,15 +258,15 @@ def _capture_envelope(envelope):
             SDK_INFO["name"] = sdk_name
             logger.debug("Setting SDK name to '%s'", sdk_name)
 
+            if has_profiling_enabled(self.options):
+                try:
+                    setup_profiler(self.options)
+                except Exception as e:
+                    logger.debug("Can not set up profiler. (%s)", e)
+
         finally:
             _client_init_debug.set(old_debug)
 
-        if has_profiling_enabled(self.options):
-            try:
-                setup_profiler(self.options)
-            except ValueError as e:
-                logger.debug(str(e))
-
         self._setup_instrumentation(self.options.get("functions_to_trace", []))
 
     @property
diff --git a/sentry_sdk/profiler.py b/sentry_sdk/profiler.py
--- a/sentry_sdk/profiler.py
+++ b/sentry_sdk/profiler.py
@@ -248,13 +248,10 @@ def teardown_profiler():
 MAX_STACK_DEPTH = 128
 
 
-CWD = os.getcwd()
-
-
 def extract_stack(
     raw_frame,  # type: Optional[FrameType]
     cache,  # type: LRUCache
-    cwd=CWD,  # type: str
+    cwd,  # type: str
     max_stack_depth=MAX_STACK_DEPTH,  # type: int
 ):
     # type: (...) -> ExtractedStack


Test patch:
diff --git a/tests/test_profiler.py b/tests/test_profiler.py
--- a/tests/test_profiler.py
+++ b/tests/test_profiler.py
@@ -500,7 +500,10 @@ def test_extract_stack_with_max_depth(depth, max_stack_depth, actual_depth):
     # increase the max_depth by the `base_stack_depth` to account
     # for the extra frames pytest will add
     _, frame_ids, frames = extract_stack(
-        frame, LRUCache(max_size=1), max_stack_depth=max_stack_depth + base_stack_depth
+        frame,
+        LRUCache(max_size=1),
+        max_stack_depth=max_stack_depth + base_stack_depth,
+        cwd=os.getcwd(),
     )
     assert len(frame_ids) == base_stack_depth + actual_depth
     assert len(frames) == base_stack_depth + actual_depth
@@ -527,8 +530,9 @@ def test_extract_stack_with_max_depth(depth, max_stack_depth, actual_depth):
 def test_extract_stack_with_cache(frame, depth):
     # make sure cache has enough room or this test will fail
     cache = LRUCache(max_size=depth)
-    _, _, frames1 = extract_stack(frame, cache)
-    _, _, frames2 = extract_stack(frame, cache)
+    cwd = os.getcwd()
+    _, _, frames1 = extract_stack(frame, cache, cwd=cwd)
+    _, _, frames2 = extract_stack(frame, cache, cwd=cwd)
 
     assert len(frames1) > 0
     assert len(frames2) > 0
@@ -667,7 +671,16 @@ def test_thread_scheduler_single_background_thread(scheduler_class):
 )
 @mock.patch("sentry_sdk.profiler.MAX_PROFILE_DURATION_NS", 1)
 def test_max_profile_duration_reached(scheduler_class):
-    sample = [("1", extract_stack(get_frame(), LRUCache(max_size=1)))]
+    sample = [
+        (
+            "1",
+            extract_stack(
+                get_frame(),
+                LRUCache(max_size=1),
+                cwd=os.getcwd(),
+            ),
+        ),
+    ]
 
     with scheduler_class(frequency=1000) as scheduler:
         transaction = Transaction(sampled=True)
@@ -711,8 +724,18 @@ def ensure_running(self):
 
 
 sample_stacks = [
-    extract_stack(get_frame(), LRUCache(max_size=1), max_stack_depth=1),
-    extract_stack(get_frame(), LRUCache(max_size=1), max_stack_depth=2),
+    extract_stack(
+        get_frame(),
+        LRUCache(max_size=1),
+        max_stack_depth=1,
+        cwd=os.getcwd(),
+    ),
+    extract_stack(
+        get_frame(),
+        LRUCache(max_size=1),
+        max_stack_depth=2,
+        cwd=os.getcwd(),
+    ),
 ]
 
 
@@ -805,7 +828,7 @@ def ensure_running(self):
                 "stacks": [[0], [1, 0]],
                 "thread_metadata": thread_metadata,
             },
-            id="two identical stacks",
+            id="two different stacks",
         ),
     ],
 )