Skip to content

profiling.sampling: run script.py does not put the script's directory on sys.path, so sibling imports fail #158540

Description

@GrahamDumpleton

Bug report

Background

I have been writing a set of guided workshops on Tachyon as a way of learning it (https://github.com/GrahamDumpleton/tachyon-workshops), using an AI agent to generate the workshop content. One of the example programs being profiled in a workshop is a script that imports a helper module from its own directory. The agent found that the script ran fine as python dir/script.py, but failed with an import error as python -m profiling.sampling run dir/script.py. I am reporting it in case it is unintended; it may be a known difference.

What happens

When a script is run with the run subcommand from a directory other than the script's own, sys.path[0] is the current working directory, not the script's directory. A script that imports a module beside it works with python and fails with ModuleNotFoundError under the profiler.

$ mkdir -p w/sub && cd w
$ printf 'import sys\nprint("path0:", repr(sys.path[0]), "argv0:", sys.argv[0])\nimport helper\n' > sub/where.py
$ printf 'print("helper imported")\n' > sub/helper.py

$ python sub/where.py
path0: '/home/tester/w/sub' argv0: sub/where.py
helper imported

$ python -m profiling.sampling run sub/where.py
path0: '/home/tester/w' argv0: /home/tester/w/sub/where.py
Traceback (most recent call last):
  File "<frozen runpy>", line 201, in _run_module_as_main
  File "<frozen runpy>", line 87, in _run_code
  File ".../lib/python3.15/profiling/sampling/_sync_coordinator.py", line 256, in <module>
    main()
    ~~~~^^
  File ".../lib/python3.15/profiling/sampling/_sync_coordinator.py", line 249, in main
    _execute_script(script_path, script_args, cwd)
    ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.15/profiling/sampling/_sync_coordinator.py", line 196, in _execute_script
    exec(code, main_module.__dict__)
    ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/tester/w/sub/where.py", line 3, in <module>
    import helper
ModuleNotFoundError: No module named 'helper'
Captured 43 samples in 0.04 seconds

Running from inside the script's directory works, since the working directory and the script's directory are then the same:

$ cd sub && python -m profiling.sampling run where.py
path0: '/home/tester/w/sub' argv0: /home/tester/w/sub/where.py
helper imported

Tested on 3.15.0rc2 (python-build-standalone build installed by uv, and the python:3.15-rc-slim image) on Linux aarch64 in a container, as a non-root user.

Why I think it is a problem

The documentation for run says it "launches a Python script or module" and gives python -m profiling.sampling run script.py as the way to profile a script, with nothing about the script seeing a different sys.path than it would under python script.py. A script that works on its own and fails under the profiler is a surprise, and for the usual layout of a small program, a script with a helper module or package beside it, it means the profiler can only be run from inside that directory.

Two smaller differences show in the same output, which may or may not matter: sys.argv[0] is made absolute (/home/tester/w/sub/where.py rather than sub/where.py), and the script runs with sys.path[0] set to the working directory even when that is not where the script is, which is the python -m rule applied to a script.

Where it comes from (from reading the source, not verified by me)

The agent's reading of Lib/profiling/sampling/_sync_coordinator.py: _setup_environment changes to the working directory and inserts it at the front of sys.path ("Add current directory to sys.path if not present (for module imports)"), which is right for -m but is done for scripts too. _execute_script then makes the script path absolute, sets sys.argv, and executes the compiled source with exec in a fresh __main__ module, without inserting os.path.dirname(script_path) into sys.path as the interpreter does for a script. A one-line insert of the script's directory at sys.path[0] in the script case looks like the fix, but I cannot say whether there is a reason it was left out.

CPython versions tested on:

3.15

Operating systems tested on:

Linux

Activity

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

    type-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions